diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 386d043615f..60f8d208fd7 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -424,14 +424,17 @@ class ExtensionTerminalLinkProvider implements ITerminalExternalLinkProvider { } async provideLinks(instance: ITerminalInstance, line: string): Promise { - const extHostLinks = await this._proxy.$provideLinks(instance.id, line); + const proxy = this._proxy; + const extHostLinks = await proxy.$provideLinks(instance.id, line); + console.log('ExtensionTerminalLinkProvider#provideLinks', extHostLinks); return extHostLinks.map(dto => ({ + id: dto.id, startIndex: dto.startIndex, length: dto.length, label: dto.label, activate(text: string) { - // TODO: Activate on the exthost console.log('Activated! ' + text); + proxy.$activateLink(instance.id, dto.id); } })); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 97517a01d5f..17026c0c435 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1379,6 +1379,8 @@ export interface IShellAndArgsDto { } export interface ITerminalLinkDto { + /** The ID of the link to enable activation and disposal. */ + id: number; /** The startIndex of the link in the line. */ startIndex: number; /** The length of the link in the line. */ @@ -1414,6 +1416,7 @@ export interface ExtHostTerminalServiceShape { $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; $handleLink(id: number, link: string): Promise; $provideLinks(id: number, line: string): Promise; + $activateLink(id: number, linkId: number): void; $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 73d3b2bfc44..ca40794123b 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -294,6 +294,13 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { } } +let nextLinkId = 1; + +interface ICachedLinkEntry { + provider: vscode.TerminalLinkProvider; + link: vscode.TerminalLink; +} + export abstract class BaseExtHostTerminalService implements IExtHostTerminalService, ExtHostTerminalServiceShape { readonly _serviceBrand: undefined; @@ -309,6 +316,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ private readonly _bufferer: TerminalDataBufferer; private readonly _linkHandlers: Set = new Set(); private readonly _linkProviders: Set = new Set(); + private readonly _terminalLinkCache: Map> = new Map(); public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; } public get terminals(): ExtHostTerminal[] { return this._terminals; } @@ -346,8 +354,8 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ return links; }, handleTerminalLink(link) { - // TODO: Pass provider ID back to ext host so it can trigger activate/handle - return false; + console.log('Handled link on ext host, tooltip=' + link.tooltip); + return true; } }); } @@ -609,35 +617,63 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ return false; } - public async $provideLinks(id: number, line: string): Promise { - const terminal = this._getTerminalById(id); + public async $provideLinks(terminalId: number, line: string): Promise { + const terminal = this._getTerminalById(terminalId); if (!terminal) { return []; } + // Discard any cached links the terminal has been holding, currently all links are released + // when new links are provided. + this._terminalLinkCache.delete(terminalId); + // TODO: Store link activate callback // TODO: Discard of links when appropriate const result: ITerminalLinkDto[] = []; const context: vscode.TerminalLinkContext = { terminal, line }; - const promises: vscode.ProviderResult[] = []; + const promises: vscode.ProviderResult<{ provider: vscode.TerminalLinkProvider, links: vscode.TerminalLink[] }>[] = []; for (const provider of this._linkProviders) { - promises.push(provider.provideTerminalLinks(context)); + promises.push(new Promise(async r => { + const links = (await provider.provideTerminalLinks(context)) || []; + r({ provider, links }); + })); } - const allProviderLinks = await Promise.all(promises); - for (const providerLinks of allProviderLinks) { - if (providerLinks && providerLinks.length > 0) { - result.push(...providerLinks.map(l => ({ - startIndex: l.startIndex, - length: l.length, - label: l.tooltip - }))); + const provideResults = await Promise.all(promises); + const cacheLinkMap = new Map(); + for (const provideResult of provideResults) { + if (provideResult && provideResult.links.length > 0) { + result.push(...provideResult.links.map(providerLink => { + const link = { + id: nextLinkId++, + startIndex: providerLink.startIndex, + length: providerLink.length, + label: providerLink.tooltip + }; + cacheLinkMap.set(link.id, { + provider: provideResult.provider, + link: providerLink + }); + return link; + })); } } + this._terminalLinkCache.set(terminalId, cacheLinkMap); + return result; } + $activateLink(terminalId: number, linkId: number): void { + const cachedLink = this._terminalLinkCache.get(terminalId)?.get(linkId); + if (!cachedLink) { + return; + } + cachedLink.provider.handleTerminalLink(cachedLink.link); + + // TODO: Handle when result is false + } + private _onProcessExit(id: number, exitCode: number | undefined): void { this._bufferer.stopBuffering(id);