Merge from vscode 7653d836944892f83ce9e1f95c1204bafa1aec31

This commit is contained in:
ADS Merger
2020-05-08 03:58:34 +00:00
parent dac1970c43
commit fa62ec1f34
209 changed files with 5131 additions and 2480 deletions

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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 Source EULA. 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 Source EULA. 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 Source EULA. 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 Source EULA. 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

@@ -315,7 +315,7 @@ export const terminalConfiguration: IConfigurationNode = {
'terminal.integrated.experimentalLinkProvider': {
description: localize('terminal.integrated.experimentalLinkProvider', "An experimental setting that aims to improve link detection in the terminal by improving when links are detected and by enabling shared link detection with the editor. Currently this only supports web links."),
type: 'boolean',
default: false
default: true
}
}
};

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' }
]);
});
});