mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-23 18:19:12 +01:00
Merge pull request #100496 from microsoft/tyriar/link_providers
Proposed terminal link provider API
This commit is contained in:
@@ -587,6 +587,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTerminalService.registerLinkHandler(handler);
|
||||
},
|
||||
registerTerminalLinkProvider(handler: vscode.TerminalLinkProvider): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTerminalService.registerLinkProvider(handler);
|
||||
},
|
||||
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
|
||||
return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension);
|
||||
},
|
||||
|
||||
@@ -450,6 +450,8 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
|
||||
$stopSendingDataEvents(): void;
|
||||
$startHandlingLinks(): void;
|
||||
$stopHandlingLinks(): void;
|
||||
$startLinkProvider(): void;
|
||||
$stopLinkProvider(): void;
|
||||
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void;
|
||||
|
||||
// Process
|
||||
@@ -1380,6 +1382,17 @@ export interface IShellAndArgsDto {
|
||||
args: string[] | string | undefined;
|
||||
}
|
||||
|
||||
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. */
|
||||
length: number;
|
||||
/** The descriptive label for what the link does when activated. */
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface ITerminalDimensionsDto {
|
||||
columns: number;
|
||||
rows: number;
|
||||
@@ -1406,6 +1419,8 @@ export interface ExtHostTerminalServiceShape {
|
||||
$getAvailableShells(): Promise<IShellDefinitionDto[]>;
|
||||
$getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
|
||||
$handleLink(id: number, link: string): Promise<boolean>;
|
||||
$provideLinks(id: number, line: string): Promise<ITerminalLinkDto[]>;
|
||||
$activateLink(id: number, linkId: number): void;
|
||||
$initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
@@ -39,6 +39,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape {
|
||||
getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string;
|
||||
getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string;
|
||||
registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable;
|
||||
registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable;
|
||||
getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection;
|
||||
}
|
||||
|
||||
@@ -293,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;
|
||||
@@ -307,6 +315,8 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
|
||||
|
||||
private readonly _bufferer: TerminalDataBufferer;
|
||||
private readonly _linkHandlers: Set<vscode.TerminalLinkHandler> = new Set();
|
||||
private readonly _linkProviders: Set<vscode.TerminalLinkProvider> = new Set();
|
||||
private readonly _terminalLinkCache: Map<number, Map<number, ICachedLinkEntry>> = new Map();
|
||||
|
||||
public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; }
|
||||
public get terminals(): ExtHostTerminal[] { return this._terminals; }
|
||||
@@ -547,17 +557,30 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
|
||||
|
||||
public registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable {
|
||||
this._linkHandlers.add(handler);
|
||||
if (this._linkHandlers.size === 1) {
|
||||
if (this._linkHandlers.size === 1 && this._linkProviders.size === 0) {
|
||||
this._proxy.$startHandlingLinks();
|
||||
}
|
||||
return new VSCodeDisposable(() => {
|
||||
this._linkHandlers.delete(handler);
|
||||
if (this._linkHandlers.size === 0) {
|
||||
if (this._linkHandlers.size === 0 && this._linkProviders.size === 0) {
|
||||
this._proxy.$stopHandlingLinks();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable {
|
||||
this._linkProviders.add(provider);
|
||||
if (this._linkProviders.size === 1) {
|
||||
this._proxy.$startLinkProvider();
|
||||
}
|
||||
return new VSCodeDisposable(() => {
|
||||
this._linkProviders.delete(provider);
|
||||
if (this._linkProviders.size === 0) {
|
||||
this._proxy.$stopLinkProvider();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async $handleLink(id: number, link: string): Promise<boolean> {
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (!terminal) {
|
||||
@@ -577,6 +600,62 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
|
||||
return false;
|
||||
}
|
||||
|
||||
public async $provideLinks(terminalId: number, line: string): Promise<ITerminalLinkDto[]> {
|
||||
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);
|
||||
|
||||
const result: ITerminalLinkDto[] = [];
|
||||
const context: vscode.TerminalLinkContext = { terminal, line };
|
||||
const promises: vscode.ProviderResult<{ provider: vscode.TerminalLinkProvider, links: vscode.TerminalLink[] }>[] = [];
|
||||
for (const provider of this._linkProviders) {
|
||||
promises.push(new Promise(async r => {
|
||||
const links = (await provider.provideTerminalLinks(context)) || [];
|
||||
r({ provider, links });
|
||||
}));
|
||||
}
|
||||
|
||||
const provideResults = await Promise.all(promises);
|
||||
const cacheLinkMap = new Map<number, ICachedLinkEntry>();
|
||||
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? Should this be return void instead and remove
|
||||
// TerminalLink.target? It's a simple call to window.openUri for the extension otherwise
|
||||
// and would simplify the API.
|
||||
}
|
||||
|
||||
private _onProcessExit(id: number, exitCode: number | undefined): void {
|
||||
this._bufferer.stopBuffering(id);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user