From d562c85b42646bbf08e661f3ab68970ce87a40a1 Mon Sep 17 00:00:00 2001 From: Hao Hu Date: Sun, 3 Feb 2019 23:22:33 -0800 Subject: [PATCH] Add git diff terminal link handler Fixes #67238 --- .../electron-browser/terminalLinkHandler.ts | 69 ++++++++++++------- .../terminalLinkHandler.test.ts | 32 +++++++++ 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts index 31a1d6fc8ed..993b6e548fa 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts @@ -63,6 +63,9 @@ export class TerminalLinkHandler { private _widgetManager: TerminalWidgetManager; private _processCwd: string; private _localLinkPattern: RegExp; + private _gitDiffPreImagePattern: RegExp; + private _gitDiffPostImagePattern: RegExp; + private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void; constructor( private _xterm: any, @@ -75,8 +78,23 @@ export class TerminalLinkHandler { const baseLocalLinkClause = _platform === platform.Platform.Windows ? winLocalLinkClause : unixLocalLinkClause; // Append line and column number regex this._localLinkPattern = new RegExp(`${baseLocalLinkClause}(${lineAndColumnClause})`); + // Matches '--- a/src/file1', capturing 'src/file1' in group 1 + this._gitDiffPreImagePattern = /^--- a\/(\S*)/; + // Matches '+++ b/src/file1', capturing 'src/file1' in group 1 + this._gitDiffPostImagePattern = /^\+\+\+ b\/(\S*)/; + + this._tooltipCallback = (e: MouseEvent) => { + if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { + const target = (e.target as HTMLElement); + this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); + } else { + this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); + } + }; + this.registerWebLinkHandler(); this.registerLocalLinkHandler(); + this.registerGitDiffLinkHandlers(); } public setWidgetManager(widgetManager: TerminalWidgetManager): void { @@ -90,14 +108,7 @@ export class TerminalLinkHandler { public registerCustomLinkHandler(regex: RegExp, handler: (uri: string) => void, matchIndex?: number, validationCallback?: XtermLinkMatcherValidationCallback): number { const options: ILinkMatcherOptions = { matchIndex, - tooltipCallback: (e: MouseEvent) => { - if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { - const target = (e.target as HTMLElement); - this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); - } else { - this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); - } - }, + tooltipCallback: this._tooltipCallback, leaveCallback: () => this._widgetManager.closeMessage(), willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), priority: CUSTOM_LINK_PRIORITY @@ -114,14 +125,7 @@ export class TerminalLinkHandler { }); this._xterm.webLinksInit(wrappedHandler, { validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateWebLink(uri, callback), - tooltipCallback: (e: MouseEvent) => { - if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { - const target = (e.target as HTMLElement); - this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); - } else { - this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); - } - }, + tooltipCallback: this._tooltipCallback, leaveCallback: () => this._widgetManager.closeMessage(), willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e) }); @@ -133,20 +137,29 @@ export class TerminalLinkHandler { }); this._xterm.registerLinkMatcher(this._localLinkRegex, wrappedHandler, { validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback), - tooltipCallback: (e: MouseEvent) => { - if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { - const target = (e.target as HTMLElement); - this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); - } else { - this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); - } - }, + tooltipCallback: this._tooltipCallback, leaveCallback: () => this._widgetManager.closeMessage(), willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), priority: LOCAL_LINK_PRIORITY }); } + public registerGitDiffLinkHandlers(): void { + const wrappedHandler = this._wrapLinkHandler(url => { + this._handleLocalLink(url); + }); + const options = { + matchIndex: 1, + validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback), + tooltipCallback: this._tooltipCallback, + leaveCallback: () => this._widgetManager.closeMessage(), + willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), + priority: LOCAL_LINK_PRIORITY + }; + this._xterm.registerLinkMatcher(this._gitDiffPreImagePattern, wrappedHandler, options); + this._xterm.registerLinkMatcher(this._gitDiffPostImagePattern, wrappedHandler, options); + } + public dispose(): void { this._xterm = null; this._hoverDisposables = dispose(this._hoverDisposables); @@ -172,6 +185,14 @@ export class TerminalLinkHandler { return this._localLinkPattern; } + protected get _gitDiffPreImageRegex(): RegExp { + return this._gitDiffPreImagePattern; + } + + protected get _gitDiffPostImageRegex(): RegExp { + return this._gitDiffPostImagePattern; + } + private _handleLocalLink(link: string): PromiseLike { return this._resolvePath(link).then(resolvedLink => { if (!resolvedLink) { diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts index 1cc1487e224..a15fe4cf18d 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -14,6 +14,12 @@ class TestTerminalLinkHandler extends TerminalLinkHandler { public get localLinkRegex(): RegExp { return this._localLinkRegex; } + public get gitDiffLinkPreImageRegex(): RegExp { + return this._gitDiffPreImageRegex; + } + public get gitDiffLinkPostImageRegex(): RegExp { + return this._gitDiffPostImageRegex; + } public preprocessPath(link: string): string | null { return this._preprocessPath(link); } @@ -217,4 +223,30 @@ suite('Workbench - TerminalLinkHandler', () => { assert.equal(linkHandler.preprocessPath('/absolute/path/file3'), '/absolute/path/file3'); }); }); + + test('gitDiffLinkRegex', () => { + // The platform is irrelevant because the links generated by Git are the same format regardless of platform + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null!, null!, null!, null!); + + function assertAreGoodMatches(matches: RegExpMatchArray | null) { + if (matches) { + assert.equal(matches.length, 2); + assert.equal(matches[1], 'src/file1'); + } else { + assert.fail(); + } + } + + // Happy cases + assertAreGoodMatches('--- a/src/file1'.match(linkHandler.gitDiffLinkPreImageRegex)); + assertAreGoodMatches('--- a/src/file1 '.match(linkHandler.gitDiffLinkPreImageRegex)); + assertAreGoodMatches('+++ b/src/file1'.match(linkHandler.gitDiffLinkPostImageRegex)); + assertAreGoodMatches('+++ b/src/file1 '.match(linkHandler.gitDiffLinkPostImageRegex)); + + // Make sure /dev/null isn't a match + assert.equal(linkHandler.gitDiffLinkPreImageRegex.test('--- /dev/null'), false); + assert.equal(linkHandler.gitDiffLinkPreImageRegex.test('--- /dev/null '), false); + assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null'), false); + assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null '), false); + }); });