// ***************************************************************************** // Copyright (C) 2019 Red Hat, Inc. and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { Plugin, PLUGIN_RPC_CONTEXT, TimelineCommandArg, TimelineExt, TimelineMain } from '../common'; import { RPCProtocol } from '../common/rpc-protocol'; import { Disposable, ThemeIcon, URI } from './types-impl'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { CommandRegistryImpl } from './command-registry'; import type { InternalTimelineOptions, Timeline, TimelineItem, TimelineOptions, TimelineProvider } from '@theia/timeline/lib/common/timeline-model'; import * as theia from '@theia/plugin'; import { CancellationToken } from '@theia/core/lib/common/cancellation'; import { UriComponents } from '../common/uri-components'; import { convertIconPath } from './type-converters'; import { PluginIconPath } from './plugin-icon-path'; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // copied and modified from https://github.com/microsoft/theia/blob/afacd2bdfe7060f09df9b9139521718915949757/src/vs/workbench/api/common/extHostTimeline.ts export class TimelineExtImpl implements TimelineExt { declare readonly _serviceBrand: undefined; private readonly proxy: TimelineMain; private providers = new Map(); private itemsBySourceAndUriMap = new Map>>(); constructor(readonly rpc: RPCProtocol, private readonly commands: CommandRegistryImpl) { this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.TIMELINE_MAIN); commands.registerArgumentProcessor({ processArgument: arg => { if (!TimelineCommandArg.is(arg)) { return arg; } else { return this.itemsBySourceAndUriMap.get(arg.source)?.get(arg.uri?.toString())?.get(arg.timelineHandle); } } }); } async $getTimeline(id: string, uri: UriComponents, options: theia.TimelineOptions, internalOptions?: InternalTimelineOptions): Promise { const provider = this.providers.get(id); return provider?.provideTimeline(URI.revive(uri), options, internalOptions); } registerTimelineProvider(plugin: Plugin, scheme: string | string[], provider: theia.TimelineProvider): Disposable { const timelineDisposables = new DisposableCollection(); const convertTimelineItem = this.convertTimelineItem(plugin, provider.id, timelineDisposables).bind(this); let disposable: Disposable | undefined; if (provider.onDidChange) { disposable = Disposable.from(provider.onDidChange(e => this.proxy.$fireTimelineChanged({ uri: undefined, reset: true, ...e, id: provider.id }), this)); } const itemsBySourceAndUriMap = this.itemsBySourceAndUriMap; return this.registerTimelineProviderCore({ ...provider, scheme: scheme, onDidChange: undefined, async provideTimeline(uri: URI, options: TimelineOptions, internalOptions?: InternalTimelineOptions): Promise { if (internalOptions?.resetCache) { timelineDisposables.dispose(); const items = itemsBySourceAndUriMap.get(provider.id); if (items) { items.clear(); } } const result = await provider.provideTimeline(uri, options, CancellationToken.None); if (!result) { return undefined; } const convertItem = convertTimelineItem(uri, internalOptions); return { ...result, source: provider.id, items: result.items.map(convertItem) }; }, dispose(): void { for (const sourceMap of itemsBySourceAndUriMap.values()) { const source = sourceMap.get(provider.id); if (source) { source.clear(); } } if (disposable) { disposable.dispose(); } timelineDisposables.dispose(); } }); } private convertTimelineItem(plugin: Plugin, source: string, disposables: DisposableCollection): (uri: URI, options?: InternalTimelineOptions) => (item: theia.TimelineItem) => TimelineItem { return (uri: URI, options?: InternalTimelineOptions) => { let items: Map | undefined; if (options?.cacheResults) { let itemsByUri = this.itemsBySourceAndUriMap.get(source); if (itemsByUri === undefined) { itemsByUri = new Map(); this.itemsBySourceAndUriMap.set(source, itemsByUri); } const uriKey = getUriKey(uri); items = itemsByUri.get(uriKey); if (items === undefined) { items = new Map(); itemsByUri.set(uriKey, items); } } return (item: theia.TimelineItem): TimelineItem => { const { iconPath, ...props } = item; const handle = `${source}|${item.id ?? item.timestamp}`; if (items) { items.set(handle, item); } return { ...props, uri: uri.toString(), id: props.id ?? undefined, handle: handle, source: source, command: item.command ? this.commands.converter.toSafeCommand(item.command, disposables) : undefined, icon: ThemeIcon.get(iconPath) ?? PluginIconPath.toUrl(convertIconPath(iconPath), plugin) }; }; }; } private registerTimelineProviderCore(provider: TimelineProvider): Disposable { const existing = this.providers.get(provider.id); if (existing) { throw new Error(`Timeline Provider ${provider.id} already exists.`); } this.proxy.$registerTimelineProvider({ id: provider.id, label: provider.label, scheme: provider.scheme }); this.providers.set(provider.id, provider); return Disposable.create(() => { for (const sourceMap of this.itemsBySourceAndUriMap.values()) { const items = sourceMap.get(provider.id); if (items) { items.clear(); } } this.providers.delete(provider.id); this.proxy.$unregisterTimelineProvider(provider.id); provider.dispose(); }); } } function getUriKey(uri: URI | undefined): string | undefined { return uri?.toString(); }