Skip to content

Commit

Permalink
Implement CanonicalUriProvider API. Fixes eclipse-theia#12735
Browse files Browse the repository at this point in the history
The implementation follows the usual pattern of having a Theia service
for canonicla URI's with the VS Code implementation being a special case
of the Theia service contributions.
The API is proposed in VS Code, but used in built-in extensions in
versions >= 1.80.0

Contributed on behalf of STMicroelectronics

Signed-off-by: Thomas Mäder <t.s.maeder@gmail.com>
  • Loading branch information
tsmaeder committed Jul 21, 2023
1 parent 41c6d9f commit b510746
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 3 deletions.
7 changes: 7 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,9 @@ export interface WorkspaceMain {
$getWorkspace(): Promise<files.FileStat | undefined>;
$requestWorkspaceTrust(options?: theia.WorkspaceTrustRequestOptions): Promise<boolean | undefined>;
$resolveProxy(url: string): Promise<string | undefined>;
$registerCanonicalUriProvider(scheme: string): Promise<void | undefined>;
$unregisterCanonicalUriProvider(scheme: string): void;
$getCanonicalUri(uri: string, targetScheme: string, token: theia.CancellationToken): Promise<string | undefined>;
}

export interface WorkspaceExt {
Expand All @@ -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<theia.Uri>;
$provideCanonicalUri(uri: string, targetScheme: string, token: CancellationToken): Promise<string | undefined>;
}

export interface TimelineExt {
Expand Down
37 changes: 35 additions & 2 deletions packages/plugin-ext/src/main/browser/workspace-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -55,6 +55,8 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable {

private workspaceService: WorkspaceService;

protected readonly canonicalUriService: CanonicalUriService;

private workspaceTrustService: WorkspaceTrustService;

private fsPreferences: FileSystemPreferences;
Expand All @@ -63,6 +65,8 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable {

protected workspaceSearch: Set<number> = new Set<number>();

protected readonly canonicalUriProviders = new Map<string, Disposable>();

constructor(rpc: RPCProtocol, container: interfaces.Container) {
this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.WORKSPACE_EXT);
this.storageProxy = rpc.getProxy(MAIN_RPC_CONTEXT.STORAGE_EXT);
Expand All @@ -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);

Expand Down Expand Up @@ -285,6 +290,34 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable {
async $requestWorkspaceTrust(_options?: theia.WorkspaceTrustRequestOptions): Promise<boolean | undefined> {
return this.workspaceTrustService.requestWorkspaceTrust();
}

async $registerCanonicalUriProvider(scheme: string): Promise<void | undefined> {
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<string | undefined> {
const canonicalUri = await this.canonicalUriService.provideCanonicalUri(new URI(uri), targetScheme, token);
return isUndefined(canonicalUri) ? undefined : canonicalUri.toString();
}
}

/**
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<theia.Uri> {
return workspaceExt.getCanonicalUri(uri, options, token);
}
};

const onDidChangeLogLevel = new Emitter<theia.LogLevel>();
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-ext/src/plugin/plugin-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 39 additions & 1 deletion packages/plugin-ext/src/plugin/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -60,6 +61,8 @@ export class WorkspaceExtImpl implements WorkspaceExt {
private didGrantWorkspaceTrustEmitter = new Emitter<void>();
public readonly onDidGrantWorkspaceTrust: Event<void> = this.didGrantWorkspaceTrustEmitter.event;

private canonicalUriProviders = new Map<string, theia.CanonicalUriProvider>();

constructor(rpc: RPCProtocol,
private editorsAndDocuments: EditorsAndDocumentsExtImpl,
private messageService: MessageRegistryExt) {
Expand Down Expand Up @@ -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<theia.Uri | undefined> {
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<string | undefined> {
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;
}

}
1 change: 1 addition & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*--------------------------------------------------------------------------------------------*/

import './theia-extra';
import './theia.proposed.canonicalUriProvider';
import './theia.proposed.customEditorMove';
import './theia.proposed.diffCommand';
import './theia.proposed.documentPaste';
Expand Down
64 changes: 64 additions & 0 deletions packages/plugin/src/theia.proposed.canonicalUriProvider.d.ts
Original file line number Diff line number Diff line change
@@ -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/main/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<Uri>;
}

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<Uri>;
}

export interface CanonicalUriRequestOptions {
/**
*
* The desired scheme of the canonical URI.
*/
targetScheme: string;
}
}
57 changes: 57 additions & 0 deletions packages/workspace/src/browser/canonical-uri-service.ts
Original file line number Diff line number Diff line change
@@ -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<URI | undefined>;
}

@injectable()
export class CanonicalUriService {
private providers = new Map<string, CanonicalUriProvider>();

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<URI | undefined> {
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);
}
}
1 change: 1 addition & 0 deletions packages/workspace/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 2 additions & 0 deletions packages/workspace/src/browser/workspace-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ 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);
bindWorkspaceTrustPreferences(bind);

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<WorkspaceServer>(workspacePath);
Expand Down

0 comments on commit b510746

Please sign in to comment.