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);