mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-30 16:50:30 -04:00
Merge from vscode 7653d836944892f83ce9e1f95c1204bafa1aec31
This commit is contained in:
@@ -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[];
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
});
|
||||
@@ -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, []));
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user