diff --git a/src/vs/editor/browser/core/markdownRenderer.ts b/src/vs/editor/browser/core/markdownRenderer.ts index c29335966ac..ec24b6b5d22 100644 --- a/src/vs/editor/browser/core/markdownRenderer.ts +++ b/src/vs/editor/browser/core/markdownRenderer.ts @@ -103,7 +103,7 @@ export class MarkdownRenderer { }, asyncRenderCallback: () => this._onDidRenderAsync.fire(), actionHandler: { - callback: (content) => this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError), + callback: (content) => this._openerService.open(content, { fromUserGesture: true, allowContributedOpeners: true }).catch(onUnexpectedError), disposeables } }; diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 5479ec29bd6..dd329d44f02 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -211,10 +211,16 @@ export class OpenerService implements IOpenerService { href = encodeURI(resolved.toString(true)); } - for (const opener of this._externalOpeners) { - const didOpen = await opener.openExternal(href, { sourceUri: uri }, CancellationToken.None); - if (didOpen) { - return true; + if (options?.allowContributedOpeners) { + const preferredOpenerId = typeof options?.allowContributedOpeners === 'string' ? options?.allowContributedOpeners : undefined; + for (const opener of this._externalOpeners) { + const didOpen = await opener.openExternal(href, { + sourceUri: uri, + preferredOpenerId, + }, CancellationToken.None); + if (didOpen) { + return true; + } } } diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 17573b46ecd..8997154cee5 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -327,7 +327,7 @@ export class LinkDetector implements IEditorContribution { } } - return this.openerService.open(uri, { openToSide, fromUserGesture }); + return this.openerService.open(uri, { openToSide, fromUserGesture, allowContributedOpeners: true }); }, err => { const messageOrError = diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 3f96fc90032..ff6740cf677 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -32,7 +32,11 @@ export type OpenInternalOptions = { readonly fromUserGesture?: boolean; }; -export type OpenExternalOptions = { readonly openExternal?: boolean; readonly allowTunneling?: boolean }; +export type OpenExternalOptions = { + readonly openExternal?: boolean; + readonly allowTunneling?: boolean; + readonly allowContributedOpeners?: boolean | string; +}; export type OpenOptions = OpenInternalOptions & OpenExternalOptions; @@ -47,7 +51,7 @@ export interface IOpener { } export interface IExternalOpener { - openExternal(href: string, ctx: { sourceUri: URI }, token: CancellationToken): Promise; + openExternal(href: string, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise; dispose?(): void; } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index a075694507a..7d67b32ada9 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -2367,13 +2367,6 @@ declare module 'vscode' { location?: Location; } - /** - * Additional metadata about the uri being opened - */ - interface OpenExternalUriContext { - - } - //#endregion //#region Opener service (https://github.com/microsoft/vscode/issues/109277) @@ -2497,6 +2490,23 @@ declare module 'vscode' { export function registerExternalUriOpener(id: string, schemes: readonly string[], opener: ExternalUriOpener, metadata: ExternalUriOpenerMetadata): Disposable; } + interface OpenExternalOptions { + /** + * + * If `true`, then VS Code will check if any contributed openers can handle the + * uri, and fallback to the default opener behavior. + * + * If it is string, then this specifies the id of the `ExternalUriOpener` + * that should be used if it is available. Use `'default'` to force VS Code's + * standard external opener to be used. + */ + readonly allowContributedOpeners?: boolean | string; + } + + namespace env { + export function openExternal(target: Uri, options?: OpenExternalOptions): Thenable; + } + //#endregion //#region https://github.com/microsoft/vscode/issues/112249 diff --git a/src/vs/workbench/api/browser/mainThreadWebviews.ts b/src/vs/workbench/api/browser/mainThreadWebviews.ts index cc5b05c298b..68409622796 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviews.ts @@ -78,7 +78,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private onDidClickLink(handle: extHostProtocol.WebviewHandle, link: string): void { const webview = this.getWebview(handle); if (this.isSupportedLink(webview, URI.parse(link))) { - this._openerService.open(link, { fromUserGesture: true }); + this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true }); } } diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index 2a431111e93..7f10aa334c6 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -52,7 +52,11 @@ export class MainThreadWindow implements MainThreadWindowShape { // called with URI or transformed -> use uri target = uri; } - return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling }); + return this.openerService.open(target, { + openExternal: true, + allowTunneling: options.allowTunneling, + allowContributedOpeners: options.allowContributedOpeners, + }); } async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5e90f0ae27b..32c4472bf89 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -292,8 +292,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get shell() { return extHostTerminalService.getDefaultShell(false, configProvider); }, - openExternal(uri: URI) { - return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.authority }); + openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string; }) { + return extHostWindow.openUri(uri, { + allowTunneling: !!initData.remote.authority, + allowContributedOpeners: options?.allowContributedOpeners, + }); }, asExternalUri(uri: URI) { if (uri.scheme === initData.environment.appUriScheme) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8cdc92b4a5e..8341b028657 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -980,6 +980,7 @@ export interface MainThreadDebugServiceShape extends IDisposable { export interface IOpenUriOptions { readonly allowTunneling?: boolean; + readonly allowContributedOpeners?: boolean | string; } export interface MainThreadWindowShape extends IDisposable { diff --git a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts index 473b4f7aedc..fae7cc0faec 100644 --- a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts +++ b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts @@ -67,7 +67,7 @@ export class ExternalUriOpenerService extends Disposable implements IExternalUri return { dispose: remove }; } - async openExternal(href: string, ctx: { sourceUri: URI }, token: CancellationToken): Promise { + async openExternal(href: string, ctx: { sourceUri: URI, preferredOpenerId?: string }, token: CancellationToken): Promise { const targetUri = typeof href === 'string' ? URI.parse(href) : href; @@ -77,11 +77,24 @@ export class ExternalUriOpenerService extends Disposable implements IExternalUri return false; } - // First check to see if we have a configured opener + // First see if we have a preferredOpener + if (ctx.preferredOpenerId) { + if (ctx.preferredOpenerId === defaultExternalUriOpenerId) { + return false; + } + + const preferredOpener = allOpeners.get(ctx.preferredOpenerId); + if (preferredOpener) { + // Skip the `canOpen` check here since the opener was specifically requested. + return preferredOpener.openExternalUri(targetUri, ctx, token); + } + } + + // Check to see if we have a configured opener const configuredOpener = this.getConfiguredOpenerForUri(allOpeners, targetUri); if (configuredOpener) { // Skip the `canOpen` check here since the opener was specifically requested. - return configuredOpener === 'default' ? false : configuredOpener.openExternalUri(targetUri, ctx, token); + return configuredOpener === defaultExternalUriOpenerId ? false : configuredOpener.openExternalUri(targetUri, ctx, token); } // Then check to see if there is a valid opener diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index bb7cbb456f3..ce56dffa393 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -427,7 +427,7 @@ var requirejs = (function() { if (matchesScheme(link, Schemas.http) || matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.mailto) || matchesScheme(link, Schemas.command)) { - this.openerService.open(link, { fromUserGesture: true }); + this.openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true }); } })); diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index e55526fa230..3c7d555dada 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -239,7 +239,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider {