From 5fc2e26dd9bf272bce43db5a3671de02f89412bb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 May 2020 04:04:11 -0700 Subject: [PATCH] Update word link provider with new model --- .../browser/links/terminalBaseLinkProvider.ts | 19 ++++ .../terminal/browser/links/terminalLink.ts | 29 +++++-- .../browser/links/terminalLinkManager.ts | 30 +++---- .../browser/links/terminalWordLinkProvider.ts | 87 +++++++++---------- 4 files changed, 97 insertions(+), 68 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider.ts diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider.ts new file mode 100644 index 00000000000..38301d0e32c --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILinkProvider, ILink } from 'xterm'; +import { TerminalLink } from 'vs/workbench/contrib/terminal/browser/links/terminalLink'; + +export abstract class TerminalBaseLinkProvider implements ILinkProvider { + private _activeLinks: TerminalLink[] | undefined; + + async provideLinks(bufferLineNumber: number, callback: (links: ILink[] | undefined) => void): Promise { + this._activeLinks?.forEach(l => l.dispose); + this._activeLinks = await this._provideLinks(bufferLineNumber); + callback(this._activeLinks); + } + + protected abstract _provideLinks(bufferLineNumber: number): Promise | TerminalLink[]; +} diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts index ef200442217..cd94c13cc1f 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts @@ -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(); public get onLeave(): Event { 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,21 @@ export class TerminalLink extends DisposableStore implements ILink { })); const timeout = this._configurationService.getValue('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(); + // this.dispose(); }, 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 +95,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 { diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts index c8e0235f257..8cf791e5b2e 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts @@ -297,22 +297,22 @@ export class TerminalLinkManager extends DisposableStore { public registerLinkProvider(): void { // Protocol links - const wrappedActivateCallback = this._wrapLinkHandler((_, link) => this._handleProtocolLink(link)); - const protocolProvider = this._instantiationService.createInstance(TerminalProtocolLinkProvider, this._xterm, wrappedActivateCallback, this._tooltipCallback2.bind(this)); - this._linkProviders.push(this._xterm.registerLinkProvider(protocolProvider)); + // const wrappedActivateCallback = this._wrapLinkHandler((_, link) => this._handleProtocolLink(link)); + // const protocolProvider = this._instantiationService.createInstance(TerminalProtocolLinkProvider, this._xterm, wrappedActivateCallback, this._tooltipCallback2.bind(this)); + // this._linkProviders.push(this._xterm.registerLinkProvider(protocolProvider)); - // Validated local links - if (this._configurationService.getValue(TERMINAL_CONFIG_SECTION).enableFileLinks) { - const wrappedTextLinkActivateCallback = this._wrapLinkHandler((_, link) => this._handleLocalLink(link)); - const validatedProvider = this._instantiationService.createInstance(TerminalValidatedLocalLinkProvider, - this._xterm, - this._processManager.os || OS, - wrappedTextLinkActivateCallback, - this._wrapLinkHandler.bind(this), - this._tooltipCallback2.bind(this), - async (link, cb) => cb(await this._resolvePath(link))); - this._linkProviders.push(this._xterm.registerLinkProvider(validatedProvider)); - } + // // Validated local links + // if (this._configurationService.getValue(TERMINAL_CONFIG_SECTION).enableFileLinks) { + // const wrappedTextLinkActivateCallback = this._wrapLinkHandler((_, link) => this._handleLocalLink(link)); + // const validatedProvider = this._instantiationService.createInstance(TerminalValidatedLocalLinkProvider, + // this._xterm, + // this._processManager.os || OS, + // wrappedTextLinkActivateCallback, + // this._wrapLinkHandler.bind(this), + // this._tooltipCallback2.bind(this), + // async (link, cb) => cb(await this._resolvePath(link))); + // this._linkProviders.push(this._xterm.registerLinkProvider(validatedProvider)); + // } // Word links const wordProvider = this._instantiationService.createInstance(TerminalWordLinkProvider, this._xterm, this._wrapLinkHandler.bind(this), this._tooltipCallback2.bind(this)); diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts index 84d7b9a29f5..5444e399c6f 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Terminal, ILinkProvider, IViewportRange, IBufferCellPosition, ILink } from 'xterm'; +import { Terminal, IViewportRange } from 'xterm'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalLink } from 'vs/workbench/contrib/terminal/browser/links/terminalLink'; @@ -15,8 +15,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; +import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider'; -export class TerminalWordLinkProvider implements ILinkProvider { +export class TerminalWordLinkProvider extends TerminalBaseLinkProvider { private readonly _fileQueryBuilder = this._instantiationService.createInstance(QueryBuilder); constructor( @@ -30,54 +31,48 @@ 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(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(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(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(); + + // Add a link if this is a separator + if (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) {