diff --git a/CHANGELOG.md b/CHANGELOG.md index 665dc4ceedc62..f1a14b317e024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ ## v1.40.0 - -- Show command shortcuts in toolbar item tooltips. #12660 (https://github.com/eclipse-theia/theia/pull/12660) - Contributed on behalf of STMicroelectronics +- [workspace] Implement CanonicalUriProvider API #12743 (https://github.com/eclipse-theia/theia/pull/12743 - Contributed on behalf of STMicroelectronics +- Show command shortcuts in toolbar item tooltips. #12660 (https://github.com/eclipse-theia/theia/pull/12660) - Contributed on behalf of STMicroelectronics - [cli] added `check:theia-extensions` which checks the uniqueness of Theia extension versions [#12596](https://github.com/eclipse-theia/theia/pull/12596) - Contributed on behalf of STMicroelectronics [Breaking Changes:](#breaking_changes_1.40.0) diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 206e27e4b6dcf..7e976a93f2303 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -694,6 +694,9 @@ export interface WorkspaceMain { $getWorkspace(): Promise; $requestWorkspaceTrust(options?: theia.WorkspaceTrustRequestOptions): Promise; $resolveProxy(url: string): Promise; + $registerCanonicalUriProvider(scheme: string): Promise; + $unregisterCanonicalUriProvider(scheme: string): void; + $getCanonicalUri(uri: string, targetScheme: string, token: theia.CancellationToken): Promise; } export interface WorkspaceExt { @@ -703,6 +706,10 @@ export interface WorkspaceExt { $onTextSearchResult(searchRequestId: number, done: boolean, result?: SearchInWorkspaceResult): void; $onWorkspaceTrustChanged(trust: boolean | undefined): void; $registerEditSessionIdentityProvider(scheme: string, provider: theia.EditSessionIdentityProvider): theia.Disposable; + registerCanonicalUriProvider(scheme: string, provider: theia.CanonicalUriProvider): theia.Disposable; + $disposeCanonicalUriProvider(scheme: string): void; + getCanonicalUri(uri: theia.Uri, options: theia.CanonicalUriRequestOptions, token: CancellationToken): theia.ProviderResult; + $provideCanonicalUri(uri: string, targetScheme: string, token: CancellationToken): Promise; } export interface TimelineExt { diff --git a/packages/plugin-ext/src/main/browser/workspace-main.ts b/packages/plugin-ext/src/main/browser/workspace-main.ts index ea577435acee7..f6fe0a9e73b9b 100644 --- a/packages/plugin-ext/src/main/browser/workspace-main.ts +++ b/packages/plugin-ext/src/main/browser/workspace-main.ts @@ -22,10 +22,10 @@ import { URI as Uri } from '@theia/core/shared/vscode-uri'; import { UriComponents } from '../../common/uri-components'; import { FileSearchService } from '@theia/file-search/lib/common/file-search-service'; import URI from '@theia/core/lib/common/uri'; -import { WorkspaceService, WorkspaceTrustService } from '@theia/workspace/lib/browser'; +import { WorkspaceService, WorkspaceTrustService, CanonicalUriService } from '@theia/workspace/lib/browser'; import { Resource } from '@theia/core/lib/common/resource'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; -import { Emitter, Event, ResourceResolver, CancellationToken } from '@theia/core'; +import { Emitter, Event, ResourceResolver, CancellationToken, isUndefined } from '@theia/core'; import { PluginServer } from '../../common/plugin-protocol'; import { FileSystemPreferences } from '@theia/filesystem/lib/browser'; import { SearchInWorkspaceService } from '@theia/search-in-workspace/lib/browser/search-in-workspace-service'; @@ -55,6 +55,8 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable { private workspaceService: WorkspaceService; + protected readonly canonicalUriService: CanonicalUriService; + private workspaceTrustService: WorkspaceTrustService; private fsPreferences: FileSystemPreferences; @@ -63,6 +65,8 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable { protected workspaceSearch: Set = new Set(); + protected readonly canonicalUriProviders = new Map(); + constructor(rpc: RPCProtocol, container: interfaces.Container) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.WORKSPACE_EXT); this.storageProxy = rpc.getProxy(MAIN_RPC_CONTEXT.STORAGE_EXT); @@ -73,6 +77,7 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable { this.pluginServer = container.get(PluginServer); this.requestService = container.get(RequestService); this.workspaceService = container.get(WorkspaceService); + this.canonicalUriService = container.get(CanonicalUriService); this.workspaceTrustService = container.get(WorkspaceTrustService); this.fsPreferences = container.get(FileSystemPreferences); @@ -285,6 +290,34 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable { async $requestWorkspaceTrust(_options?: theia.WorkspaceTrustRequestOptions): Promise { return this.workspaceTrustService.requestWorkspaceTrust(); } + + async $registerCanonicalUriProvider(scheme: string): Promise { + this.canonicalUriProviders.set(scheme, + this.canonicalUriService.registerCanonicalUriProvider(scheme, { + provideCanonicalUri: async (uri, targetScheme, token) => { + const canonicalUri = await this.proxy.$provideCanonicalUri(uri.toString(), targetScheme, CancellationToken.None); + return isUndefined(uri) ? undefined : new URI(canonicalUri); + }, + dispose: () => { + this.proxy.$disposeCanonicalUriProvider(scheme); + }, + })); + } + + $unregisterCanonicalUriProvider(scheme: string): void { + const disposable = this.canonicalUriProviders.get(scheme); + if (disposable) { + this.canonicalUriProviders.delete(scheme); + disposable.dispose(); + } else { + console.warn(`No canonical uri provider registered for '${scheme}'`); + } + } + + async $getCanonicalUri(uri: string, targetScheme: string, token: theia.CancellationToken): Promise { + const canonicalUri = await this.canonicalUriService.provideCanonicalUri(new URI(uri), targetScheme, token); + return isUndefined(canonicalUri) ? undefined : canonicalUri.toString(); + } } /** diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index f62f102e83a24..522b24d95000e 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -747,6 +747,12 @@ export function createAPIFactory( * that currently use this proposed API. */ onWillCreateEditSessionIdentity: () => Disposable.NULL, + registerCanonicalUriProvider(scheme: string, provider: theia.CanonicalUriProvider): theia.Disposable { + return workspaceExt.registerCanonicalUriProvider(scheme, provider); + }, + getCanonicalUri(uri: theia.Uri, options: theia.CanonicalUriRequestOptions, token: CancellationToken): theia.ProviderResult { + return workspaceExt.getCanonicalUri(uri, options, token); + } }; const onDidChangeLogLevel = new Emitter(); diff --git a/packages/plugin-ext/src/plugin/plugin-manager.ts b/packages/plugin-ext/src/plugin/plugin-manager.ts index 541ce9a4f350f..0fe8976a33568 100644 --- a/packages/plugin-ext/src/plugin/plugin-manager.ts +++ b/packages/plugin-ext/src/plugin/plugin-manager.ts @@ -403,6 +403,7 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { if (typeof pluginMain[plugin.lifecycle.startMethod] === 'function') { await this.localization.initializeLocalizedMessages(plugin, this.envExt.language); const pluginExport = await pluginMain[plugin.lifecycle.startMethod].apply(getGlobal(), [pluginContext]); + console.log(`calling activation function on ${id}`); this.activatedPlugins.set(plugin.model.id, new ActivatedPlugin(pluginContext, pluginExport, stopFn)); } else { // https://github.com/TypeFox/vscode/blob/70b8db24a37fafc77247de7f7cb5bb0195120ed0/src/vs/workbench/api/common/extHostExtensionService.ts#L400-L401 diff --git a/packages/plugin-ext/src/plugin/workspace.ts b/packages/plugin-ext/src/plugin/workspace.ts index 31047f0fdab37..ea30c844dc6ea 100644 --- a/packages/plugin-ext/src/plugin/workspace.ts +++ b/packages/plugin-ext/src/plugin/workspace.ts @@ -42,6 +42,7 @@ import { toWorkspaceFolder } from './type-converters'; import { MessageRegistryExt } from './message-registry'; import * as Converter from './type-converters'; import { FileStat } from '@theia/filesystem/lib/common/files'; +import { isUndefinedOrNull, isUndefined } from '../common/types'; export class WorkspaceExtImpl implements WorkspaceExt { @@ -60,6 +61,8 @@ export class WorkspaceExtImpl implements WorkspaceExt { private didGrantWorkspaceTrustEmitter = new Emitter(); public readonly onDidGrantWorkspaceTrust: Event = this.didGrantWorkspaceTrustEmitter.event; + private canonicalUriProviders = new Map(); + constructor(rpc: RPCProtocol, private editorsAndDocuments: EditorsAndDocumentsExtImpl, private messageService: MessageRegistryExt) { @@ -449,9 +452,44 @@ export class WorkspaceExtImpl implements WorkspaceExt { } } + registerCanonicalUriProvider(scheme: string, provider: theia.CanonicalUriProvider): theia.Disposable { + if (this.canonicalUriProviders.has(scheme)) { + throw new Error(`Canonical URI provider for scheme: '${scheme}' already exists locally`); + } + + this.canonicalUriProviders.set(scheme, provider); + this.proxy.$registerCanonicalUriProvider(scheme).catch(e => { + console.error(`Canonical URI provider for scheme: '${scheme}' already exists globally`); + this.canonicalUriProviders.delete(scheme); + }); + const result = Disposable.create(() => { this.proxy.$unregisterCanonicalUriProvider(scheme); }); + return result; + } + + $disposeCanonicalUriProvider(scheme: string): void { + if (!this.canonicalUriProviders.delete(scheme)) { + console.warn(`No canonical uri provider registered for '${scheme}'`); + } + } + + async getCanonicalUri(uri: theia.Uri, options: theia.CanonicalUriRequestOptions, token: theia.CancellationToken): Promise { + const canonicalUri = await this.proxy.$getCanonicalUri(uri.toString(), options.targetScheme, token); + return isUndefined(canonicalUri) ? undefined : URI.parse(canonicalUri); + } + + async $provideCanonicalUri(uri: string, targetScheme: string, token: CancellationToken): Promise { + const parsed = URI.parse(uri); + const provider = this.canonicalUriProviders.get(parsed.scheme); + if (!provider) { + console.warn(`No canonical uri provider registered for '${parsed.scheme}'`); + return undefined; + } + const result = await provider.provideCanonicalUri(parsed, { targetScheme: targetScheme }, token); + return isUndefinedOrNull(result) ? undefined : result.toString(); + } + /** @stubbed */ $registerEditSessionIdentityProvider(scheme: string, provider: theia.EditSessionIdentityProvider): theia.Disposable { return Disposable.NULL; } - } diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 13cee3a27044f..a8e0bc8b3b075 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -22,6 +22,7 @@ *--------------------------------------------------------------------------------------------*/ import './theia-extra'; +import './theia.proposed.canonicalUriProvider'; import './theia.proposed.customEditorMove'; import './theia.proposed.diffCommand'; import './theia.proposed.documentPaste'; diff --git a/packages/plugin/src/theia.proposed.canonicalUriProvider.d.ts b/packages/plugin/src/theia.proposed.canonicalUriProvider.d.ts new file mode 100644 index 0000000000000..2eea47e1d4b73 --- /dev/null +++ b/packages/plugin/src/theia.proposed.canonicalUriProvider.d.ts @@ -0,0 +1,64 @@ +// ***************************************************************************** +// Copyright (C) 2023 STMicroelectronics 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 +// ***************************************************************************** +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// code copied and modified from https://github.com/microsoft/vscode/blob/1.79.0/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts + +export module '@theia/plugin' { + + // https://github.com/microsoft/vscode/issues/180582 + + export namespace workspace { + /** + * + * @param scheme The URI scheme that this provider can provide canonical URIs for. + * A canonical URI represents the conversion of a resource's alias into a source of truth URI. + * Multiple aliases may convert to the same source of truth URI. + * @param provider A provider which can convert URIs of scheme @param scheme to + * a canonical URI which is stable across machines. + */ + export function registerCanonicalUriProvider(scheme: string, provider: CanonicalUriProvider): Disposable; + + /** + * + * @param uri The URI to provide a canonical URI for. + * @param token A cancellation token for the request. + */ + export function getCanonicalUri(uri: Uri, options: CanonicalUriRequestOptions, token: CancellationToken): ProviderResult; + } + + export interface CanonicalUriProvider { + /** + * + * @param uri The URI to provide a canonical URI for. + * @param options Options that the provider should honor in the URI it returns. + * @param token A cancellation token for the request. + * @returns The canonical URI for the requested URI or undefined if no canonical URI can be provided. + */ + provideCanonicalUri(uri: Uri, options: CanonicalUriRequestOptions, token: CancellationToken): ProviderResult; + } + + export interface CanonicalUriRequestOptions { + /** + * + * The desired scheme of the canonical URI. + */ + targetScheme: string; + } +} diff --git a/packages/workspace/src/browser/canonical-uri-service.ts b/packages/workspace/src/browser/canonical-uri-service.ts new file mode 100644 index 0000000000000..5dffd03c69463 --- /dev/null +++ b/packages/workspace/src/browser/canonical-uri-service.ts @@ -0,0 +1,57 @@ +// ***************************************************************************** +// Copyright (C) 2023 STMicroelectronics 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 { CancellationToken, URI } from '@theia/core/lib/common'; +import { injectable } from '@theia/core/shared/inversify'; +import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol'; + +export interface CanonicalUriProvider extends Disposable { + provideCanonicalUri(uri: URI, targetScheme: string, token: CancellationToken): Promise; +} + +@injectable() +export class CanonicalUriService { + private providers = new Map(); + + registerCanonicalUriProvider(scheme: string, provider: CanonicalUriProvider): Disposable { + if (this.providers.has(scheme)) { + throw new Error(`Canonical URI provider for scheme: '${scheme}' already exists`); + } + + this.providers.set(scheme, provider); + return Disposable.create(() => { this.removeCanonicalUriProvider(scheme); }); + } + + private removeCanonicalUriProvider(scheme: string): void { + const provider = this.providers.get(scheme); + if (!provider) { + throw new Error(`No Canonical URI provider for scheme: '${scheme}' exists`); + } + + this.providers.delete(scheme); + provider.dispose(); + } + + async provideCanonicalUri(uri: URI, targetScheme: string, token: CancellationToken = CancellationToken.None): Promise { + const provider = this.providers.get(uri.scheme); + if (!provider) { + console.warn(`No Canonical URI provider for scheme: '${uri.scheme}' exists`); + return undefined; + } + + return provider.provideCanonicalUri(uri, targetScheme, token); + } +} diff --git a/packages/workspace/src/browser/index.ts b/packages/workspace/src/browser/index.ts index ad1c39060c916..ff3eef9cc7466 100644 --- a/packages/workspace/src/browser/index.ts +++ b/packages/workspace/src/browser/index.ts @@ -16,6 +16,7 @@ export * from './workspace-commands'; export * from './workspace-service'; +export * from './canonical-uri-service'; export * from './workspace-frontend-contribution'; export * from './workspace-frontend-module'; export * from './workspace-preferences'; diff --git a/packages/workspace/src/browser/workspace-frontend-module.ts b/packages/workspace/src/browser/workspace-frontend-module.ts index ef6ff08a2c6a5..9b6fc6c5d7551 100644 --- a/packages/workspace/src/browser/workspace-frontend-module.ts +++ b/packages/workspace/src/browser/workspace-frontend-module.ts @@ -54,6 +54,7 @@ import { UserWorkingDirectoryProvider } from '@theia/core/lib/browser/user-worki import { WorkspaceUserWorkingDirectoryProvider } from './workspace-user-working-directory-provider'; import { WindowTitleUpdater } from '@theia/core/lib/browser/window/window-title-updater'; import { WorkspaceWindowTitleUpdater } from './workspace-window-title-updater'; +import { CanonicalUriService } from './canonical-uri-service'; export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => { bindWorkspacePreferences(bind); @@ -61,6 +62,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un bind(WorkspaceService).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).toService(WorkspaceService); + bind(CanonicalUriService).toSelf().inSingletonScope(); bind(WorkspaceServer).toDynamicValue(ctx => { const provider = ctx.container.get(WebSocketConnectionProvider); return provider.createProxy(workspacePath);