Merge pull request #97065 from microsoft/tyriar/provide_links

Adopt xterm provideLink -> provideLinks API change
This commit is contained in:
Daniel Imms 2020-05-06 10:28:07 -07:00 committed by GitHub
commit e5be2d8ad7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 330 additions and 308 deletions

View file

@ -60,10 +60,10 @@
"vscode-ripgrep": "^1.5.8",
"vscode-sqlite3": "4.0.10",
"vscode-textmate": "5.1.1",
"xterm": "4.6.0-beta.38",
"xterm": "4.6.0-beta.44",
"xterm-addon-search": "0.7.0-beta.2",
"xterm-addon-unicode11": "0.2.0-beta.5",
"xterm-addon-web-links": "0.4.0-beta.5",
"xterm-addon-web-links": "0.4.0-beta.6",
"xterm-addon-webgl": "0.7.0-beta.10",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"

View file

@ -20,10 +20,10 @@
"vscode-proxy-agent": "^0.5.2",
"vscode-ripgrep": "^1.5.8",
"vscode-textmate": "5.1.1",
"xterm": "4.6.0-beta.38",
"xterm": "4.6.0-beta.44",
"xterm-addon-search": "0.7.0-beta.2",
"xterm-addon-unicode11": "0.2.0-beta.5",
"xterm-addon-web-links": "0.4.0-beta.5",
"xterm-addon-web-links": "0.4.0-beta.6",
"xterm-addon-webgl": "0.7.0-beta.10",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"

View file

@ -5,10 +5,10 @@
"semver-umd": "^5.5.6",
"vscode-oniguruma": "1.3.0",
"vscode-textmate": "5.1.1",
"xterm": "4.6.0-beta.38",
"xterm": "4.6.0-beta.44",
"xterm-addon-search": "0.7.0-beta.2",
"xterm-addon-unicode11": "0.2.0-beta.5",
"xterm-addon-web-links": "0.4.0-beta.5",
"xterm-addon-web-links": "0.4.0-beta.6",
"xterm-addon-webgl": "0.7.0-beta.10"
}
}

View file

@ -27,17 +27,17 @@ xterm-addon-unicode11@0.2.0-beta.5:
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.5.tgz#5961850162df20b5e966166423cd6957ac2db298"
integrity sha512-IjnbBcyfS5JgJDXPO0W2nk/VBtGwx6GWE2snMC676z4DmAABUqPXfTzJKfUoWqoT6UcbxB0oIjDzykCfoRJp6Q==
xterm-addon-web-links@0.4.0-beta.5:
version "0.4.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.5.tgz#523fd0a1c5668370d73e05019ed16eaf596894c8"
integrity sha512-Qe0idPpSokCNvGrthSBjdrOZrsgXwnLYbzuv0JoEec/A9HVcxKmZ+ktw7fOA2gT/zbcwtrA5FWrir3GlRHglCQ==
xterm-addon-web-links@0.4.0-beta.6:
version "0.4.0-beta.6"
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.6.tgz#d159d4542eb9a02d57977fe7eb5f42f8ef2f27fa"
integrity sha512-dsQVD/EyVq8PtAYGh2PGQTCt009UipIfX6Q2SBDlz+W9x7IkXjhRxRaryMmLsBCca20qeVKwmbQ+ANhLi+nTaQ==
xterm-addon-webgl@0.7.0-beta.10:
version "0.7.0-beta.10"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.10.tgz#39fdb96351e97a1bf15f4c4c8944ba3d05cacee4"
integrity sha512-nQl/ASk+ck11aSrBZXb2a0tu+SNDnm89owBk/sAZeZzi5MHNo6bB8y2VTKNNC6D3i3aFouTz4VorYB25LUgNFg==
xterm@4.6.0-beta.38:
version "4.6.0-beta.38"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.38.tgz#8472b168941500c3071aba482c2b5c6040951ec7"
integrity sha512-Q+nOalMD1MDGOqXdtkGZmOQqbSBU+71vhlX2RBwQoSpJa1QBrKDAhSlN/J+/XvouvVEtCiEFDeacF4EufMEIMg==
xterm@4.6.0-beta.44:
version "4.6.0-beta.44"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.44.tgz#76b2a6b8e147595ab44aa752c0e721d935464615"
integrity sha512-vYtfz4spFcSKLEUpC6anH7TwDams71+k2wAtUzCJ47dNL2IrwYafcFsvGPm46QLTtq4M2Bp9rQo3R3V746yxNg==

View file

@ -414,20 +414,20 @@ xterm-addon-unicode11@0.2.0-beta.5:
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.5.tgz#5961850162df20b5e966166423cd6957ac2db298"
integrity sha512-IjnbBcyfS5JgJDXPO0W2nk/VBtGwx6GWE2snMC676z4DmAABUqPXfTzJKfUoWqoT6UcbxB0oIjDzykCfoRJp6Q==
xterm-addon-web-links@0.4.0-beta.5:
version "0.4.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.5.tgz#523fd0a1c5668370d73e05019ed16eaf596894c8"
integrity sha512-Qe0idPpSokCNvGrthSBjdrOZrsgXwnLYbzuv0JoEec/A9HVcxKmZ+ktw7fOA2gT/zbcwtrA5FWrir3GlRHglCQ==
xterm-addon-web-links@0.4.0-beta.6:
version "0.4.0-beta.6"
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.6.tgz#d159d4542eb9a02d57977fe7eb5f42f8ef2f27fa"
integrity sha512-dsQVD/EyVq8PtAYGh2PGQTCt009UipIfX6Q2SBDlz+W9x7IkXjhRxRaryMmLsBCca20qeVKwmbQ+ANhLi+nTaQ==
xterm-addon-webgl@0.7.0-beta.10:
version "0.7.0-beta.10"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.10.tgz#39fdb96351e97a1bf15f4c4c8944ba3d05cacee4"
integrity sha512-nQl/ASk+ck11aSrBZXb2a0tu+SNDnm89owBk/sAZeZzi5MHNo6bB8y2VTKNNC6D3i3aFouTz4VorYB25LUgNFg==
xterm@4.6.0-beta.38:
version "4.6.0-beta.38"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.38.tgz#8472b168941500c3071aba482c2b5c6040951ec7"
integrity sha512-Q+nOalMD1MDGOqXdtkGZmOQqbSBU+71vhlX2RBwQoSpJa1QBrKDAhSlN/J+/XvouvVEtCiEFDeacF4EufMEIMg==
xterm@4.6.0-beta.44:
version "4.6.0-beta.44"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.44.tgz#76b2a6b8e147595ab44aa752c0e721d935464615"
integrity sha512-vYtfz4spFcSKLEUpC6anH7TwDams71+k2wAtUzCJ47dNL2IrwYafcFsvGPm46QLTtq4M2Bp9rQo3R3V746yxNg==
yauzl@^2.9.2:
version "2.10.0"

View file

@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ILinkProvider, ILink } from 'xterm';
import { TerminalLink } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
export abstract class TerminalBaseLinkProvider implements ILinkProvider {
private _activeLinks: TerminalLink[] | undefined;
async provideLinks(bufferLineNumber: number, callback: (links: ILink[] | undefined) => void): Promise<void> {
this._activeLinks?.forEach(l => l.dispose);
this._activeLinks = await this._provideLinks(bufferLineNumber);
callback(this._activeLinks);
}
protected abstract _provideLinks(bufferLineNumber: number): Promise<TerminalLink[]> | TerminalLink[];
}

View file

@ -20,6 +20,9 @@ export const FOLDER_NOT_IN_WORKSPACE_LABEL = localize('openFolder', 'Open folder
export class TerminalLink extends DisposableStore implements ILink {
decorations: ILinkDecorations;
private _tooltipScheduler: RunOnceScheduler | undefined;
private _hoverListeners: DisposableStore | undefined;
private readonly _onLeave = new Emitter<void>();
public get onLeave(): Event<void> { return this._onLeave.event; }
@ -40,6 +43,14 @@ export class TerminalLink extends DisposableStore implements ILink {
};
}
dispose(): void {
super.dispose();
this._hoverListeners?.dispose();
this._hoverListeners = undefined;
this._tooltipScheduler?.dispose();
this._tooltipScheduler = undefined;
}
activate(event: MouseEvent | undefined, text: string): void {
this._activateCallback(event, text);
}
@ -58,20 +69,23 @@ export class TerminalLink extends DisposableStore implements ILink {
}));
const timeout = this._configurationService.getValue<number>('editor.hover.delay');
const scheduler = new RunOnceScheduler(() => {
this._tooltipScheduler = new RunOnceScheduler(() => {
this._tooltipCallback(
this,
convertBufferRangeToViewport(this.range, this._viewportY),
this._isHighConfidenceLink ? () => this._enableDecorations() : undefined,
this._isHighConfidenceLink ? () => this._disableDecorations() : undefined
);
this.dispose();
// Clear out scheduler until next hover event
this._tooltipScheduler?.dispose();
this._tooltipScheduler = undefined;
}, timeout);
this.add(scheduler);
scheduler.schedule();
this.add(this._tooltipScheduler);
this._tooltipScheduler.schedule();
const origin = { x: event.pageX, y: event.pageY };
this.add(dom.addDisposableListener(document, dom.EventType.MOUSE_MOVE, e => {
this._hoverListeners = new DisposableStore();
this._hoverListeners.add(dom.addDisposableListener(document, dom.EventType.MOUSE_MOVE, e => {
// Update decorations
if (this._isModifierDown(e)) {
this._enableDecorations();
@ -83,14 +97,17 @@ export class TerminalLink extends DisposableStore implements ILink {
if (Math.abs(e.pageX - origin.x) > window.devicePixelRatio * 2 || Math.abs(e.pageY - origin.y) > window.devicePixelRatio * 2) {
origin.x = e.pageX;
origin.y = e.pageY;
scheduler.schedule();
this._tooltipScheduler?.schedule();
}
}));
}
leave(): void {
this._hoverListeners?.dispose();
this._hoverListeners = undefined;
this._tooltipScheduler?.dispose();
this._tooltipScheduler = undefined;
this._onLeave.fire();
this.dispose();
}
private _enableDecorations(): void {

View file

@ -3,14 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Terminal, IViewportRange, ILinkProvider, IBufferCellPosition, ILink, IBufferLine } from 'xterm';
import { Terminal, IViewportRange, IBufferLine } from 'xterm';
import { ILinkComputerTarget, LinkComputer } from 'vs/editor/common/modes/linkComputer';
import { getXtermLineContent, convertLinkRangeToBuffer, positionIsInRange } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers';
import { getXtermLineContent, convertLinkRangeToBuffer } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers';
import { TerminalLink, OPEN_FILE_LABEL } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider';
export class TerminalProtocolLinkProvider implements ILinkProvider {
export class TerminalProtocolLinkProvider extends TerminalBaseLinkProvider {
private _linkComputerTarget: ILinkComputerTarget | undefined;
constructor(
@ -19,10 +20,11 @@ export class TerminalProtocolLinkProvider implements ILinkProvider {
private readonly _tooltipCallback: (link: TerminalLink, viewportRange: IViewportRange, modifierDownCallback?: () => void, modifierUpCallback?: () => void) => void,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super();
}
public provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
let startLine = position.y - 1;
protected _provideLinks(y: number): TerminalLink[] {
let startLine = y - 1;
let endLine = startLine;
const lines: IBufferLine[] = [
@ -42,24 +44,16 @@ export class TerminalProtocolLinkProvider implements ILinkProvider {
this._linkComputerTarget = new TerminalLinkAdapter(this._xterm, startLine, endLine);
const links = LinkComputer.computeLinks(this._linkComputerTarget);
let found = false;
links.forEach(link => {
return links.map(link => {
const range = convertLinkRangeToBuffer(lines, this._xterm.cols, link.range, startLine);
// Check if the link if within the mouse position
if (positionIsInRange(position, range)) {
found = true;
const uri = link.url
? (typeof link.url === 'string' ? URI.parse(link.url) : link.url)
: undefined;
const label = (uri?.scheme === 'file') ? OPEN_FILE_LABEL : undefined;
callback(this._instantiationService.createInstance(TerminalLink, range, link.url?.toString() || '', this._xterm.buffer.active.viewportY, this._activateCallback, this._tooltipCallback, true, label));
}
const uri = link.url
? (typeof link.url === 'string' ? URI.parse(link.url) : link.url)
: undefined;
const label = (uri?.scheme === 'file') ? OPEN_FILE_LABEL : undefined;
return this._instantiationService.createInstance(TerminalLink, range, link.url?.toString() || '', this._xterm.buffer.active.viewportY, this._activateCallback, this._tooltipCallback, true, label);
});
if (!found) {
callback(undefined);
}
}
}

View file

@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Terminal, ILinkProvider, IViewportRange, IBufferCellPosition, ILink, IBufferLine } from 'xterm';
import { getXtermLineContent, convertLinkRangeToBuffer, positionIsInRange } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers';
import { Terminal, IViewportRange, IBufferLine } from 'xterm';
import { getXtermLineContent, convertLinkRangeToBuffer } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers';
import { OperatingSystem } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { TerminalLink, OPEN_FILE_LABEL, FOLDER_IN_WORKSPACE_LABEL, FOLDER_NOT_IN_WORKSPACE_LABEL } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
@ -14,6 +14,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider';
const pathPrefix = '(\\.\\.?|\\~)';
const pathSeparatorClause = '\\/';
@ -41,7 +42,7 @@ const lineAndColumnClause = [
'(([^:\\s\\(\\)<>\'\"\\[\\]]*)(:(\\d+))?(:(\\d+))?)' // (file path):336, (file path):336:9
].join('|').replace(/ /g, `[${'\u00A0'} ]`);
export class TerminalValidatedLocalLinkProvider implements ILinkProvider {
export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider {
constructor(
private readonly _xterm: Terminal,
private readonly _processOperatingSystem: OperatingSystem,
@ -54,10 +55,12 @@ export class TerminalValidatedLocalLinkProvider implements ILinkProvider {
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IHostService private readonly _hostService: IHostService
) {
super();
}
async provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void) {
let startLine = position.y - 1;
protected async _provideLinks(y: number): Promise<TerminalLink[]> {
const result: TerminalLink[] = [];
let startLine = y - 1;
let endLine = startLine;
const lines: IBufferLine[] = [
@ -121,34 +124,31 @@ export class TerminalValidatedLocalLinkProvider implements ILinkProvider {
endLineNumber: 1
}, startLine);
if (positionIsInRange(position, bufferRange)) {
const validatedLink = await new Promise<ILink | undefined>(r => {
this._validationCallback(link, (result) => {
if (result) {
const label = result.isDirectory
? (this._isDirectoryInsideWorkspace(result.uri) ? FOLDER_IN_WORKSPACE_LABEL : FOLDER_NOT_IN_WORKSPACE_LABEL)
: OPEN_FILE_LABEL;
const activateCallback = this._wrapLinkHandler((event: MouseEvent | undefined, text: string) => {
if (result.isDirectory) {
this._handleLocalFolderLink(result.uri);
} else {
this._activateFileCallback(event, text);
}
});
r(this._instantiationService.createInstance(TerminalLink, bufferRange, link, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, true, label));
} else {
r(undefined);
}
});
const validatedLink = await new Promise<TerminalLink | undefined>(r => {
this._validationCallback(link, (result) => {
if (result) {
const label = result.isDirectory
? (this._isDirectoryInsideWorkspace(result.uri) ? FOLDER_IN_WORKSPACE_LABEL : FOLDER_NOT_IN_WORKSPACE_LABEL)
: OPEN_FILE_LABEL;
const activateCallback = this._wrapLinkHandler((event: MouseEvent | undefined, text: string) => {
if (result.isDirectory) {
this._handleLocalFolderLink(result.uri);
} else {
this._activateFileCallback(event, text);
}
});
r(this._instantiationService.createInstance(TerminalLink, bufferRange, link, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, true, label));
} else {
r(undefined);
}
});
if (validatedLink) {
callback(validatedLink);
return;
}
});
if (validatedLink) {
result.push(validatedLink);
}
}
callback(undefined);
return result;
}
protected get _localLinkRegex(): RegExp {

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Terminal, ILinkProvider, IViewportRange, IBufferCellPosition, ILink } from 'xterm';
import { Terminal, IViewportRange } from 'xterm';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalLink } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
@ -15,8 +15,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider';
export class TerminalWordLinkProvider implements ILinkProvider {
export class TerminalWordLinkProvider extends TerminalBaseLinkProvider {
private readonly _fileQueryBuilder = this._instantiationService.createInstance(QueryBuilder);
constructor(
@ -30,54 +31,49 @@ export class TerminalWordLinkProvider implements ILinkProvider {
@ISearchService private readonly _searchService: ISearchService,
@IEditorService private readonly _editorService: IEditorService
) {
super();
}
public provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
const start: IBufferCellPosition = { x: position.x, y: position.y };
const end: IBufferCellPosition = { x: position.x, y: position.y };
protected _provideLinks(y: number): TerminalLink[] {
// TODO: Support wrapping
// Expand to the left until a word separator is hit
const line = this._xterm.buffer.active.getLine(position.y - 1)!;
let text = '';
start.x++; // The hovered cell is considered first
for (let x = position.x; x > 0; x--) {
const cell = line.getCell(x - 1);
if (!cell) {
break;
}
const char = cell.getChars();
const config = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
if (cell.getWidth() !== 0 && config.wordSeparators.indexOf(char) >= 0) {
break;
}
start.x = x;
text = char + text;
}
// No links were found (the hovered cell is whitespace)
if (text.length === 0) {
callback(undefined);
return;
}
// Expand to the right until a word separator is hit
for (let x = position.x + 1; x <= line.length; x++) {
const cell = line.getCell(x - 1);
if (!cell) {
break;
}
const char = cell.getChars();
const config = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
if (cell.getWidth() !== 0 && config.wordSeparators.indexOf(char) >= 0) {
break;
}
end.x = x;
text += char;
}
// Dispose of all old links if new links are provides, links are only cached for the current line
const result: TerminalLink[] = [];
const wordSeparators = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION).wordSeparators;
const activateCallback = this._wrapLinkHandler((_, link) => this._activate(link));
callback(new TerminalLink({ start, end }, text, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, false, localize('searchWorkspace', 'Search workspace'), this._configurationService));
const line = this._xterm.buffer.active.getLine(y - 1)!;
let text = '';
let startX = -1;
const cellData = line.getCell(0)!;
for (let x = 0; x < line.length; x++) {
line.getCell(x, cellData);
const chars = cellData.getChars();
const width = cellData.getWidth();
// Add a link if this is a separator
if (width !== 0 && wordSeparators.indexOf(chars) >= 0) {
if (startX !== -1) {
result.push(new TerminalLink({ start: { x: startX + 1, y }, end: { x, y } }, text, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, false, localize('searchWorkspace', 'Search workspace'), this._configurationService));
text = '';
startX = -1;
}
continue;
}
// Mark the start of a link if it hasn't started yet
if (startX === -1) {
startX = x;
}
text += chars;
}
// Add the final link if there is one
if (startX !== -1) {
result.push(new TerminalLink({ start: { x: startX + 1, y }, end: { x: line.length, y } }, text, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, false, localize('searchWorkspace', 'Search workspace'), this._configurationService));
}
return result;
}
private async _activate(link: string) {

View file

@ -34,7 +34,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess';
import { terminalConfiguration, getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
import { terminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
// Register services
@ -58,11 +58,6 @@ CommandsRegistry.registerCommand({ id: quickAccessNavigatePreviousInTerminalPick
// Register configurations
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration(terminalConfiguration);
if (platform.isWeb) {
// Desktop shell configuration are registered in electron-browser as their default values rely
// on process.env
configurationRegistry.registerConfiguration(getTerminalShellConfiguration());
}
// Register views
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
@ -164,17 +159,6 @@ if (BrowserFeatures.clipboard.readText) {
}
}
if (platform.isWeb) {
// Register standard external terminal keybinding as integrated terminal when in web as the
// external terminal is not available
KeybindingsRegistry.registerKeybindingRule({
id: TERMINAL_COMMAND_ID.NEW,
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C
});
}
// Delete word left: ctrl+w
registerSendSequenceKeybinding(String.fromCharCode('W'.charCodeAt(0) - 64), {
primary: KeyMod.CtrlCmd | KeyCode.Backspace,

View file

@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
// Desktop shell configuration are registered in electron-browser as their default values rely
// on process.env
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration(getTerminalShellConfiguration());
// Register standard external terminal keybinding as integrated terminal when in web as the
// external terminal is not available
KeybindingsRegistry.registerKeybindingRule({
id: TERMINAL_COMMAND_ID.NEW,
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C
});

View file

@ -34,6 +34,10 @@ export class TerminalHover extends Disposable implements ITerminalWidget {
super();
}
dispose() {
super.dispose();
}
attach(container: HTMLElement): void {
const target = new CellHoverTarget(container, this._targetOptions);
this._register(this._instantiationService.createInstance(HoverWidget, container, target, this._text, this._linkHandler, []));

View file

@ -5,7 +5,7 @@
import * as assert from 'assert';
import { TerminalProtocolLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider';
import { Terminal, ILink, IBufferRange, IBufferCellPosition } from 'xterm';
import { Terminal, ILink } from 'xterm';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -18,77 +18,69 @@ suite('Workbench - TerminalWebLinkProvider', () => {
instantiationService.stub(IConfigurationService, TestConfigurationService);
});
async function assertLink(text: string, expected: { text: string, range: [number, number][] }) {
async function assertLink(text: string, expected: { text: string, range: [number, number][] }[]) {
const xterm = new Terminal();
const provider = instantiationService.createInstance(TerminalProtocolLinkProvider, xterm, () => { }, () => { });
// Write the text and wait for the parser to finish
await new Promise<void>(r => xterm.write(text, r));
// Calculate positions just outside of link boundaries
const noLinkPositions: IBufferCellPosition[] = [
{ x: expected.range[0][0] - 1, y: expected.range[0][1] },
{ x: expected.range[1][0] + 1, y: expected.range[1][1] }
];
// Ensure outside positions do not detect the link
for (let i = 0; i < noLinkPositions.length; i++) {
const link = await new Promise<ILink | undefined>(r => provider.provideLink(noLinkPositions[i], r));
assert.equal(link, undefined, `Just outside range boundary should not result in link, link found at (${link?.range.start.x}, ${link?.range.start.y}) to (${link?.range.end.x}, ${link?.range.end.y}) while checking (${noLinkPositions[i].x}, ${noLinkPositions[i].y})\nExpected link text=${expected.text}\nActual link text=${link?.text}`);
}
// Convert range from [[startx, starty], [endx, endy]] to an IBufferRange
const linkRange: IBufferRange = {
start: { x: expected.range[0][0], y: expected.range[0][1] },
end: { x: expected.range[1][0], y: expected.range[1][1] },
};
// Calculate positions inside the link boundaries
const linkPositions: IBufferCellPosition[] = [
linkRange.start,
linkRange.end
];
// Ensure inside positions do detect the link
for (let i = 0; i < linkPositions.length; i++) {
const link = await new Promise<ILink | undefined>(r => provider.provideLink(linkPositions[i], r));
assert.deepEqual(link?.text, expected.text);
assert.deepEqual(link?.range, linkRange);
}
// Ensure all links are provided
const links = (await new Promise<ILink[] | undefined>(r => provider.provideLinks(1, r)))!;
assert.equal(links.length, expected.length);
const actual = links.map(e => ({
text: e.text,
range: e.range
}));
const expectedVerbose = expected.map(e => ({
text: e.text,
range: {
start: { x: e.range[0][0], y: e.range[0][1] },
end: { x: e.range[1][0], y: e.range[1][1] },
}
}));
assert.deepEqual(actual, expectedVerbose);
}
// These tests are based on LinkComputer.test.ts
test('LinkComputer cases', async () => {
await assertLink('x = "http://foo.bar";', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('x = (http://foo.bar);', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('x = \'http://foo.bar\';', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('x = http://foo.bar ;', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('x = <http://foo.bar>;', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('x = {http://foo.bar};', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('(see http://foo.bar)', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('[see http://foo.bar]', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('{see http://foo.bar}', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('<see http://foo.bar>', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('<url>http://foo.bar</url>', { range: [[6, 1], [19, 1]], text: 'http://foo.bar' });
await assertLink('// Click here to learn more. https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409', { range: [[30, 1], [7, 2]], text: 'https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409' });
await assertLink('// Click here to learn more. https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx', { range: [[30, 1], [28, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' });
await assertLink('// https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js', { range: [[4, 1], [9, 2]], text: 'https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js' });
await assertLink('<!-- !!! Do not remove !!! WebContentRef(link:https://go.microsoft.com/fwlink/?LinkId=166007, area:Admin, updated:2015, nextUpdate:2016, tags:SqlServer) !!! Do not remove !!! -->', { range: [[49, 1], [14, 2]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' });
await assertLink('For instructions, see https://go.microsoft.com/fwlink/?LinkId=166007.</value>', { range: [[23, 1], [68, 1]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' });
await assertLink('For instructions, see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx.</value>', { range: [[23, 1], [21, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' });
await assertLink('x = "https://en.wikipedia.org/wiki/Zürich";', { range: [[6, 1], [41, 1]], text: 'https://en.wikipedia.org/wiki/Zürich' });
await assertLink('請參閱 http://go.microsoft.com/fwlink/?LinkId=761051。', { range: [[8, 1], [53, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' });
await assertLink('(請參閱 http://go.microsoft.com/fwlink/?LinkId=761051', { range: [[10, 1], [55, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' });
await assertLink('x = "file:///foo.bar";', { range: [[6, 1], [20, 1]], text: 'file:///foo.bar' });
await assertLink('x = "file://c:/foo.bar";', { range: [[6, 1], [22, 1]], text: 'file://c:/foo.bar' });
await assertLink('x = "file://shares/foo.bar";', { range: [[6, 1], [26, 1]], text: 'file://shares/foo.bar' });
await assertLink('x = "file://shäres/foo.bar";', { range: [[6, 1], [26, 1]], text: 'file://shäres/foo.bar' });
await assertLink('Some text, then http://www.bing.com.', { range: [[17, 1], [35, 1]], text: 'http://www.bing.com' });
await assertLink('let url = `http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items`;', { range: [[12, 1], [78, 1]], text: 'http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items' });
await assertLink('7. At this point, ServiceMain has been called. There is no functionality presently in ServiceMain, but you can consult the [MSDN documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx) to add functionality as desired!', { range: [[66, 2], [64, 3]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx' });
await assertLink('let x = "http://[::1]:5000/connect/token"', { range: [[10, 1], [40, 1]], text: 'http://[::1]:5000/connect/token' });
await assertLink('2. Navigate to **https://portal.azure.com**', { range: [[18, 1], [41, 1]], text: 'https://portal.azure.com' });
await assertLink('POST|https://portal.azure.com|2019-12-05|', { range: [[6, 1], [29, 1]], text: 'https://portal.azure.com' });
await assertLink('aa https://foo.bar/[this is foo site] aa', { range: [[5, 1], [38, 1]], text: 'https://foo.bar/[this is foo site]' });
await assertLink('x = "http://foo.bar";', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('x = (http://foo.bar);', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('x = \'http://foo.bar\';', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('x = http://foo.bar ;', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('x = <http://foo.bar>;', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('x = {http://foo.bar};', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('(see http://foo.bar)', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('[see http://foo.bar]', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('{see http://foo.bar}', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('<see http://foo.bar>', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('<url>http://foo.bar</url>', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]);
await assertLink('// Click here to learn more. https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409', [{ range: [[30, 1], [7, 2]], text: 'https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409' }]);
await assertLink('// Click here to learn more. https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx', [{ range: [[30, 1], [28, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' }]);
await assertLink('// https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js', [{ range: [[4, 1], [9, 2]], text: 'https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js' }]);
await assertLink('<!-- !!! Do not remove !!! WebContentRef(link:https://go.microsoft.com/fwlink/?LinkId=166007, area:Admin, updated:2015, nextUpdate:2016, tags:SqlServer) !!! Do not remove !!! -->', [{ range: [[49, 1], [14, 2]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' }]);
await assertLink('For instructions, see https://go.microsoft.com/fwlink/?LinkId=166007.</value>', [{ range: [[23, 1], [68, 1]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' }]);
await assertLink('For instructions, see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx.</value>', [{ range: [[23, 1], [21, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' }]);
await assertLink('x = "https://en.wikipedia.org/wiki/Zürich";', [{ range: [[6, 1], [41, 1]], text: 'https://en.wikipedia.org/wiki/Zürich' }]);
await assertLink('請參閱 http://go.microsoft.com/fwlink/?LinkId=761051。', [{ range: [[8, 1], [53, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' }]);
await assertLink('(請參閱 http://go.microsoft.com/fwlink/?LinkId=761051', [{ range: [[10, 1], [55, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' }]);
await assertLink('x = "file:///foo.bar";', [{ range: [[6, 1], [20, 1]], text: 'file:///foo.bar' }]);
await assertLink('x = "file://c:/foo.bar";', [{ range: [[6, 1], [22, 1]], text: 'file://c:/foo.bar' }]);
await assertLink('x = "file://shares/foo.bar";', [{ range: [[6, 1], [26, 1]], text: 'file://shares/foo.bar' }]);
await assertLink('x = "file://shäres/foo.bar";', [{ range: [[6, 1], [26, 1]], text: 'file://shäres/foo.bar' }]);
await assertLink('Some text, then http://www.bing.com.', [{ range: [[17, 1], [35, 1]], text: 'http://www.bing.com' }]);
await assertLink('let url = `http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items`;', [{ range: [[12, 1], [78, 1]], text: 'http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items' }]);
await assertLink('7. At this point, ServiceMain has been called. There is no functionality presently in ServiceMain, but you can consult the [MSDN documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx) to add functionality as desired!', [{ range: [[66, 2], [64, 3]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx' }]);
await assertLink('let x = "http://[::1]:5000/connect/token"', [{ range: [[10, 1], [40, 1]], text: 'http://[::1]:5000/connect/token' }]);
await assertLink('2. Navigate to **https://portal.azure.com**', [{ range: [[18, 1], [41, 1]], text: 'https://portal.azure.com' }]);
await assertLink('POST|https://portal.azure.com|2019-12-05|', [{ range: [[6, 1], [29, 1]], text: 'https://portal.azure.com' }]);
await assertLink('aa https://foo.bar/[this is foo site] aa', [{ range: [[5, 1], [38, 1]], text: 'https://foo.bar/[this is foo site]' }]);
});
test('should support multiple link results', async () => {
await assertLink('http://foo.bar http://bar.foo', [
{ range: [[1, 1], [14, 1]], text: 'http://foo.bar' },
{ range: [[16, 1], [29, 1]], text: 'http://bar.foo' }
]);
});
});

View file

@ -5,7 +5,7 @@
import * as assert from 'assert';
import { TerminalValidatedLocalLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider';
import { Terminal, ILink, IBufferRange, IBufferCellPosition } from 'xterm';
import { Terminal, ILink } from 'xterm';
import { OperatingSystem } from 'vs/base/common/platform';
import { format } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
@ -76,43 +76,28 @@ suite('Workbench - TerminalValidatedLocalLinkProvider', () => {
instantiationService.stub(IConfigurationService, TestConfigurationService);
});
async function assertLink(text: string, os: OperatingSystem, expected: { text: string, range: [number, number][] }) {
async function assertLink(text: string, os: OperatingSystem, expected: { text: string, range: [number, number][] }[]) {
const xterm = new Terminal();
const provider = instantiationService.createInstance(TerminalValidatedLocalLinkProvider, xterm, os, () => { }, () => { }, () => { }, (_: string, cb: (result: { uri: URI, isDirectory: boolean } | undefined) => void) => { cb({ uri: URI.file('/'), isDirectory: false }); });
// Write the text and wait for the parser to finish
await new Promise<void>(r => xterm.write(text, r));
// Calculate positions just outside of link boundaries
const noLinkPositions: IBufferCellPosition[] = [
{ x: expected.range[0][0] - 1, y: expected.range[0][1] },
{ x: expected.range[1][0] + 1, y: expected.range[1][1] }
];
// Ensure outside positions do not detect the link
for (let i = 0; i < noLinkPositions.length; i++) {
const link = await new Promise<ILink | undefined>(r => provider.provideLink(noLinkPositions[i], r));
assert.equal(link, undefined, `Just outside range boundary should not result in link, link found at (${link?.range.start.x}, ${link?.range.start.y}) to (${link?.range.end.x}, ${link?.range.end.y}) while checking (${noLinkPositions[i].x}, ${noLinkPositions[i].y})\nExpected link text=${expected.text}\nActual link text=${link?.text}`);
}
// Convert range from [[startx, starty], [endx, endy]] to an IBufferRange
const linkRange: IBufferRange = {
start: { x: expected.range[0][0], y: expected.range[0][1] },
end: { x: expected.range[1][0], y: expected.range[1][1] },
};
// Calculate positions inside the link boundaries
const linkPositions: IBufferCellPosition[] = [
linkRange.start,
linkRange.end
];
// Ensure inside positions do detect the link
for (let i = 0; i < linkPositions.length; i++) {
const link = await new Promise<ILink | undefined>(r => provider.provideLink(linkPositions[i], r));
assert.deepEqual(link?.text, expected.text);
assert.deepEqual(link?.range, linkRange);
}
// Ensure all links are provided
const links = (await new Promise<ILink[] | undefined>(r => provider.provideLinks(1, r)))!;
assert.equal(links.length, expected.length);
const actual = links.map(e => ({
text: e.text,
range: e.range
}));
const expectedVerbose = expected.map(e => ({
text: e.text,
range: {
start: { x: e.range[0][0], y: e.range[0][1] },
end: { x: e.range[1][0], y: e.range[1][1] },
}
}));
assert.deepEqual(actual, expectedVerbose);
}
suite('Linux/macOS', () => {
@ -122,19 +107,21 @@ suite('Workbench - TerminalValidatedLocalLinkProvider', () => {
const linkFormat = supportedLinkFormats[i];
test(`Format: ${linkFormat.urlFormat}`, async () => {
const formattedLink = format(linkFormat.urlFormat, baseLink, linkFormat.line, linkFormat.column);
await assertLink(formattedLink, OperatingSystem.Linux, { text: formattedLink, range: [[1, 1], [formattedLink.length, 1]] });
await assertLink(` ${formattedLink} `, OperatingSystem.Linux, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
await assertLink(`(${formattedLink})`, OperatingSystem.Linux, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
await assertLink(`[${formattedLink}]`, OperatingSystem.Linux, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
await assertLink(formattedLink, OperatingSystem.Linux, [{ text: formattedLink, range: [[1, 1], [formattedLink.length, 1]] }]);
await assertLink(` ${formattedLink} `, OperatingSystem.Linux, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
await assertLink(`(${formattedLink})`, OperatingSystem.Linux, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
await assertLink(`[${formattedLink}]`, OperatingSystem.Linux, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
});
}
});
});
test('Git diff links', async () => {
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[14, 1], [20, 1]] });
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[24, 1], [30, 1]] });
await assertLink(`--- a/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[7, 1], [13, 1]] });
await assertLink(`+++ b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[7, 1], [13, 1]] });
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, [
{ text: 'foo/bar', range: [[14, 1], [20, 1]] },
{ text: 'foo/bar', range: [[24, 1], [30, 1]] }
]);
await assertLink(`--- a/foo/bar`, OperatingSystem.Linux, [{ text: 'foo/bar', range: [[7, 1], [13, 1]] }]);
await assertLink(`+++ b/foo/bar`, OperatingSystem.Linux, [{ text: 'foo/bar', range: [[7, 1], [13, 1]] }]);
});
});
@ -145,19 +132,28 @@ suite('Workbench - TerminalValidatedLocalLinkProvider', () => {
const linkFormat = supportedLinkFormats[i];
test(`Format: ${linkFormat.urlFormat}`, async () => {
const formattedLink = format(linkFormat.urlFormat, baseLink, linkFormat.line, linkFormat.column);
await assertLink(formattedLink, OperatingSystem.Windows, { text: formattedLink, range: [[1, 1], [formattedLink.length, 1]] });
await assertLink(` ${formattedLink} `, OperatingSystem.Windows, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
await assertLink(`(${formattedLink})`, OperatingSystem.Windows, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
await assertLink(`[${formattedLink}]`, OperatingSystem.Windows, { text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] });
await assertLink(formattedLink, OperatingSystem.Windows, [{ text: formattedLink, range: [[1, 1], [formattedLink.length, 1]] }]);
await assertLink(` ${formattedLink} `, OperatingSystem.Windows, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
await assertLink(`(${formattedLink})`, OperatingSystem.Windows, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
await assertLink(`[${formattedLink}]`, OperatingSystem.Windows, [{ text: formattedLink, range: [[2, 1], [formattedLink.length + 1, 1]] }]);
});
}
});
});
test('Git diff links', async () => {
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[14, 1], [20, 1]] });
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[24, 1], [30, 1]] });
await assertLink(`--- a/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[7, 1], [13, 1]] });
await assertLink(`+++ b/foo/bar`, OperatingSystem.Linux, { text: 'foo/bar', range: [[7, 1], [13, 1]] });
await assertLink(`diff --git a/foo/bar b/foo/bar`, OperatingSystem.Linux, [
{ text: 'foo/bar', range: [[14, 1], [20, 1]] },
{ text: 'foo/bar', range: [[24, 1], [30, 1]] }
]);
await assertLink(`--- a/foo/bar`, OperatingSystem.Linux, [{ text: 'foo/bar', range: [[7, 1], [13, 1]] }]);
await assertLink(`+++ b/foo/bar`, OperatingSystem.Linux, [{ text: 'foo/bar', range: [[7, 1], [13, 1]] }]);
});
});
test('should support multiple link results', async () => {
await assertLink('./foo ./bar', OperatingSystem.Linux, [
{ range: [[1, 1], [5, 1]], text: './foo' },
{ range: [[7, 1], [11, 1]], text: './bar' }
]);
});
});

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Terminal, ILink, IBufferRange, IBufferCellPosition } from 'xterm';
import { Terminal, ILink } from 'xterm';
import { TerminalWordLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
@ -21,68 +21,62 @@ suite('Workbench - TerminalWordLinkProvider', () => {
instantiationService.stub(IConfigurationService, configurationService);
});
async function assertLink(text: string, expected: { text: string, range: [number, number][] }) {
async function assertLink(text: string, expected: { text: string, range: [number, number][] }[]) {
const xterm = new Terminal();
const provider = instantiationService.createInstance(TerminalWordLinkProvider, xterm, () => { }, () => { });
const provider: TerminalWordLinkProvider = instantiationService.createInstance(TerminalWordLinkProvider, xterm, () => { }, () => { });
// Write the text and wait for the parser to finish
await new Promise<void>(r => xterm.write(text, r));
// Calculate positions just outside of link boundaries
const noLinkPositions: IBufferCellPosition[] = [
{ x: expected.range[0][0] - 1, y: expected.range[0][1] },
{ x: expected.range[1][0] + 1, y: expected.range[1][1] }
];
// Ensure outside positions do not detect the link
for (let i = 0; i < noLinkPositions.length; i++) {
const link = await new Promise<ILink | undefined>(r => provider.provideLink(noLinkPositions[i], r));
assert.equal(link, undefined, `Just outside range boundary should not result in link, link found at (${link?.range.start.x}, ${link?.range.start.y}) to (${link?.range.end.x}, ${link?.range.end.y}) while checking (${noLinkPositions[i].x}, ${noLinkPositions[i].y})\nExpected link text=${expected.text}\nActual link text=${link?.text}`);
}
// Convert range from [[startx, starty], [endx, endy]] to an IBufferRange
const linkRange: IBufferRange = {
start: { x: expected.range[0][0], y: expected.range[0][1] },
end: { x: expected.range[1][0], y: expected.range[1][1] },
};
// Calculate positions inside the link boundaries
const linkPositions: IBufferCellPosition[] = [
linkRange.start,
linkRange.end
];
// Ensure inside positions do detect the link
for (let i = 0; i < linkPositions.length; i++) {
const link = await new Promise<ILink | undefined>(r => provider.provideLink(linkPositions[i], r));
assert.deepEqual(link?.text, expected.text);
assert.deepEqual(link?.range, linkRange);
}
// Ensure all links are provided
const links = (await new Promise<ILink[] | undefined>(r => provider.provideLinks(1, r)))!;
assert.equal(links.length, expected.length);
const actual = links.map(e => ({
text: e.text,
range: e.range
}));
const expectedVerbose = expected.map(e => ({
text: e.text,
range: {
start: { x: e.range[0][0], y: e.range[0][1] },
end: { x: e.range[1][0], y: e.range[1][1] },
}
}));
assert.deepEqual(actual, expectedVerbose);
}
test('should link words as defined by wordSeparators', async () => {
await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ()[]' } });
await assertLink('foo', { range: [[1, 1], [3, 1]], text: 'foo' });
await assertLink(' foo ', { range: [[2, 1], [4, 1]], text: 'foo' });
await assertLink('(foo)', { range: [[2, 1], [4, 1]], text: 'foo' });
await assertLink('[foo]', { range: [[2, 1], [4, 1]], text: 'foo' });
await assertLink('{foo}', { range: [[1, 1], [5, 1]], text: '{foo}' });
await assertLink('foo', [{ range: [[1, 1], [3, 1]], text: 'foo' }]);
await assertLink('foo', [{ range: [[1, 1], [3, 1]], text: 'foo' }]);
await assertLink(' foo ', [{ range: [[2, 1], [4, 1]], text: 'foo' }]);
await assertLink('(foo)', [{ range: [[2, 1], [4, 1]], text: 'foo' }]);
await assertLink('[foo]', [{ range: [[2, 1], [4, 1]], text: 'foo' }]);
await assertLink('{foo}', [{ range: [[1, 1], [5, 1]], text: '{foo}' }]);
await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } });
await assertLink('foo', { range: [[1, 1], [3, 1]], text: 'foo' });
await assertLink(' foo ', { range: [[2, 1], [4, 1]], text: 'foo' });
await assertLink('(foo)', { range: [[1, 1], [5, 1]], text: '(foo)' });
await assertLink('[foo]', { range: [[1, 1], [5, 1]], text: '[foo]' });
await assertLink('{foo}', { range: [[1, 1], [5, 1]], text: '{foo}' });
await assertLink('foo', [{ range: [[1, 1], [3, 1]], text: 'foo' }]);
await assertLink(' foo ', [{ range: [[2, 1], [4, 1]], text: 'foo' }]);
await assertLink('(foo)', [{ range: [[1, 1], [5, 1]], text: '(foo)' }]);
await assertLink('[foo]', [{ range: [[1, 1], [5, 1]], text: '[foo]' }]);
await assertLink('{foo}', [{ range: [[1, 1], [5, 1]], text: '{foo}' }]);
});
test('should support wide characters', async () => {
await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' []' } });
await assertLink('aabbccdd.txt ', { range: [[1, 1], [12, 1]], text: 'aabbccdd.txt' });
await assertLink('我是学生.txt ', { range: [[1, 1], [12, 1]], text: '我是学生.txt' });
await assertLink(' aabbccdd.txt ', { range: [[2, 1], [13, 1]], text: 'aabbccdd.txt' });
await assertLink(' 我是学生.txt ', { range: [[2, 1], [13, 1]], text: '我是学生.txt' });
await assertLink(' [aabbccdd.txt] ', { range: [[3, 1], [14, 1]], text: 'aabbccdd.txt' });
await assertLink(' [我是学生.txt] ', { range: [[3, 1], [14, 1]], text: '我是学生.txt' });
await assertLink('aabbccdd.txt ', [{ range: [[1, 1], [12, 1]], text: 'aabbccdd.txt' }]);
await assertLink('我是学生.txt ', [{ range: [[1, 1], [12, 1]], text: '我是学生.txt' }]);
await assertLink(' aabbccdd.txt ', [{ range: [[2, 1], [13, 1]], text: 'aabbccdd.txt' }]);
await assertLink(' 我是学生.txt ', [{ range: [[2, 1], [13, 1]], text: '我是学生.txt' }]);
await assertLink(' [aabbccdd.txt] ', [{ range: [[3, 1], [14, 1]], text: 'aabbccdd.txt' }]);
await assertLink(' [我是学生.txt] ', [{ range: [[3, 1], [14, 1]], text: '我是学生.txt' }]);
});
test('should support multiple link results', async () => {
await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } });
await assertLink('foo bar', [
{ range: [[1, 1], [3, 1]], text: 'foo' },
{ range: [[5, 1], [7, 1]], text: 'bar' }
]);
});
});

View file

@ -116,6 +116,7 @@ import 'vs/workbench/contrib/webview/browser/webviewService';
import 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
// Terminal
import 'vs/workbench/contrib/terminal/browser/terminal.web.contribution';
import 'vs/workbench/contrib/terminal/browser/terminalInstanceService';
// Tasks

View file

@ -10052,20 +10052,20 @@ xterm-addon-unicode11@0.2.0-beta.5:
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.5.tgz#5961850162df20b5e966166423cd6957ac2db298"
integrity sha512-IjnbBcyfS5JgJDXPO0W2nk/VBtGwx6GWE2snMC676z4DmAABUqPXfTzJKfUoWqoT6UcbxB0oIjDzykCfoRJp6Q==
xterm-addon-web-links@0.4.0-beta.5:
version "0.4.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.5.tgz#523fd0a1c5668370d73e05019ed16eaf596894c8"
integrity sha512-Qe0idPpSokCNvGrthSBjdrOZrsgXwnLYbzuv0JoEec/A9HVcxKmZ+ktw7fOA2gT/zbcwtrA5FWrir3GlRHglCQ==
xterm-addon-web-links@0.4.0-beta.6:
version "0.4.0-beta.6"
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.6.tgz#d159d4542eb9a02d57977fe7eb5f42f8ef2f27fa"
integrity sha512-dsQVD/EyVq8PtAYGh2PGQTCt009UipIfX6Q2SBDlz+W9x7IkXjhRxRaryMmLsBCca20qeVKwmbQ+ANhLi+nTaQ==
xterm-addon-webgl@0.7.0-beta.10:
version "0.7.0-beta.10"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.10.tgz#39fdb96351e97a1bf15f4c4c8944ba3d05cacee4"
integrity sha512-nQl/ASk+ck11aSrBZXb2a0tu+SNDnm89owBk/sAZeZzi5MHNo6bB8y2VTKNNC6D3i3aFouTz4VorYB25LUgNFg==
xterm@4.6.0-beta.38:
version "4.6.0-beta.38"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.38.tgz#8472b168941500c3071aba482c2b5c6040951ec7"
integrity sha512-Q+nOalMD1MDGOqXdtkGZmOQqbSBU+71vhlX2RBwQoSpJa1QBrKDAhSlN/J+/XvouvVEtCiEFDeacF4EufMEIMg==
xterm@4.6.0-beta.44:
version "4.6.0-beta.44"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.44.tgz#76b2a6b8e147595ab44aa752c0e721d935464615"
integrity sha512-vYtfz4spFcSKLEUpC6anH7TwDams71+k2wAtUzCJ47dNL2IrwYafcFsvGPm46QLTtq4M2Bp9rQo3R3V746yxNg==
y18n@^3.2.1:
version "3.2.1"