diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index 9b0622ddf75e4f..154a8eac830915 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -200,6 +200,12 @@ export class TerminalLinkManager extends DisposableStore { private _setupLinkDetector(id: string, detector: ITerminalLinkDetector, isExternal: boolean = false): ILinkProvider { const detectorAdapter = this.add(this._instantiationService.createInstance(TerminalLinkDetectorAdapter, detector)); this.add(detectorAdapter.onDidActivateLink(e => { + // xterm.js activates links on mouseup of any button, only the primary button should + // activate. Bail before preventDefault as that would block the middle click paste of + // the selection clipboard on Linux. + if (e.event && e.event.button !== 0) { + return; + } // Prevent default electron link handling so Alt+Click mode works normally e.event?.preventDefault(); // Require correct modifier on click unless event is coming from linkQuickPick selection diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts index 7132cb0d596618..4a6301a8a5f191 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts @@ -20,7 +20,7 @@ import { IDetectedLinks, TerminalLinkManager } from '../../browser/terminalLinkM import { ITerminalCapabilityImplMap, ITerminalCapabilityStore, TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalConfiguration, ITerminalProcessManager } from '../../../../terminal/common/terminal.js'; import { TestViewDescriptorService } from '../../../../terminal/test/browser/xterm/xtermTerminal.test.js'; -import { TestStorageService } from '../../../../../test/common/workbenchTestServices.js'; +import { TestProductService, TestStorageService } from '../../../../../test/common/workbenchTestServices.js'; import type { ILink, Terminal } from '@xterm/xterm'; import { IXtermCore } from '../../../../terminal/browser/xterm-private.js'; import { TerminalLinkResolver } from '../../browser/terminalLinkResolver.js'; @@ -36,6 +36,7 @@ import { IHoverService } from '../../../../../../platform/hover/browser/hover.js import { ILinkHoverTargetOptions } from '../../../../terminal/browser/widgets/terminalHoverWidget.js'; import { TerminalWidgetManager } from '../../../../terminal/browser/widgets/widgetManager.js'; import { TerminalLink } from '../../browser/terminalLink.js'; +import { IProductService } from '../../../../../../platform/product/common/productService.js'; const defaultTerminalConfig: Partial = { fontFamily: 'monospace', @@ -64,6 +65,9 @@ class TestLinkManager extends TerminalLinkManager { setLinks(links: IDetectedLinks): void { this._links = links; } + getDetectedLinks(y: number, type: 'word' | 'url' | 'localFile'): Promise { + return super._getLinksForType(y, type); + } } suite('TerminalLinkManager', () => { @@ -96,6 +100,7 @@ suite('TerminalLinkManager', () => { instantiationService.stub(IStorageService, store.add(new TestStorageService())); instantiationService.stub(IThemeService, themeService); instantiationService.stub(IViewDescriptorService, viewDescriptorService); + instantiationService.stub(IProductService, TestProductService); const TerminalCtor = (await importAMDNodeModule('@xterm/xterm', 'lib/xterm.js')).Terminal; xterm = store.add(new TerminalCtor({ allowProposedApi: true, cols: 80, rows: 30, logger: TestXtermLogger })); @@ -341,6 +346,18 @@ suite('TerminalLinkManager', () => { strictEqual(fileLink, link2); }); }); + + suite('link activation', () => { + test('should only prevent default for the primary button so middle click paste works', async () => { + await new Promise(r => xterm.write('foo', r)); + const [link] = (await linkManager.getDetectedLinks(1, 'word'))!; + const events = [0, 1, 2].map(button => new MouseEvent('mouseup', { button, cancelable: true })); + for (const e of events) { + link.activate(e, link.text); + } + deepStrictEqual(events.map(e => e.defaultPrevented), [true, false, false]); + }); + }); }); function upcastPartial(v: Partial): T { return v as T;