diff --git a/addons/xterm-addon-web-links/src/WebLinkProvider.ts b/addons/xterm-addon-web-links/src/WebLinkProvider.ts index 563108d2..af6ad7d3 100644 --- a/addons/xterm-addon-web-links/src/WebLinkProvider.ts +++ b/addons/xterm-addon-web-links/src/WebLinkProvider.ts @@ -15,19 +15,20 @@ export class WebLinkProvider implements ILinkProvider { } - public provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void { - callback(LinkComputer.computeLink(position, this._regex, this._terminal, this._handler)); + public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void { + callback(LinkComputer.computeLink(y, this._regex, this._terminal, this._handler)); } } export class LinkComputer { - public static computeLink(position: IBufferCellPosition, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink | undefined { + public static computeLink(y: number, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink[] { const rex = new RegExp(regex.source, (regex.flags || '') + 'g'); - const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(position.y - 1, false, terminal); + const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(y - 1, false, terminal); let match; let stringIndex = -1; + const result: ILink[] = []; while ((match = rex.exec(line)) !== null) { const text = match[1]; @@ -68,8 +69,10 @@ export class LinkComputer { } }; - return { range, text, activate: handler }; + result.push({ range, text, activate: handler }); } + + return result; } /** diff --git a/src/browser/Linkifier2.test.ts b/src/browser/Linkifier2.test.ts index fb71b6aa..c1a0cf6c 100644 --- a/src/browser/Linkifier2.test.ts +++ b/src/browser/Linkifier2.test.ts @@ -10,18 +10,14 @@ import { MockBufferService } from 'common/TestUtils.test'; import { ILink } from 'browser/Types'; class TestLinkifier2 extends Linkifier2 { - protected _currentLinkState = { - decorations: { - underline: true, - pointerCursor: true - }, - isHovered: true - }; - constructor(bufferService: IBufferService) { super(bufferService); } + public set currentLink(link: any) { + this._currentLink = link; + } + public linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void { this._linkHover(element, link, event); } @@ -35,11 +31,6 @@ describe('Linkifier2', () => { let bufferService: IBufferService; let linkifier: TestLinkifier2; - beforeEach(() => { - bufferService = new MockBufferService(100, 10); - linkifier = new TestLinkifier2(bufferService); - }); - const link: ILink = { text: 'foo', range: { @@ -55,6 +46,21 @@ describe('Linkifier2', () => { activate: () => { } }; + beforeEach(() => { + bufferService = new MockBufferService(100, 10); + linkifier = new TestLinkifier2(bufferService); + linkifier.currentLink = { + link, + state: { + decorations: { + underline: true, + pointerCursor: true + }, + isHovered: true + } + }; + }); + it('onShowLinkUnderline event range is correct', done => { linkifier.onShowLinkUnderline(e => { assert.equal(link.range.start.x - 1, e.x1); diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 39f750cc..fb4a0286 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -8,7 +8,7 @@ import { IDisposable } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { Disposable, getDisposeArrayDisposable } from 'common/Lifecycle'; +import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; interface ILinkState { @@ -16,17 +16,23 @@ interface ILinkState { isHovered: boolean; } +interface ILinkWithState { + link: ILink; + state?: ILinkState; +} + export class Linkifier2 extends Disposable implements ILinkifier2 { private _element: HTMLElement | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; private _linkProviders: ILinkProvider[] = []; - private _currentLink: ILink | undefined; - protected _currentLinkState: ILinkState | undefined; + protected _currentLink: ILinkWithState | undefined; private _lastMouseEvent: MouseEvent | undefined; private _linkCacheDisposables: IDisposable[] = []; private _lastBufferCell: IBufferCellPosition | undefined; private _isMouseOut: boolean = true; + private _activeProviderReplies: Map | undefined; + private _activeLine: number = -1; private _onShowLinkUnderline = this.register(new EventEmitter()); public get onShowLinkUnderline(): IEvent { return this._onShowLinkUnderline.event; } @@ -101,61 +107,88 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { } private _onHover(position: IBufferCellPosition): void { - if (this._currentLink) { - // Check the if the link is in the mouse position - const isInPosition = this._linkAtPosition(this._currentLink, position); + // TODO: This currently does not cache link provider results across wrapped lines, activeLine should be something like `activeRange: {startY, endY}` + // Check if we need to clear the link + if (this._activeLine !== position.y) { + this._clearCurrentLink(); + this._askForLink(position, false); + return; + } - // Check if we need to clear the link - if (!isInPosition) { - this._clearCurrentLink(); - this._askForLink(position); - } - } else { - this._askForLink(position); + // Check the if the link is in the mouse position + const isCurrentLinkInPosition = this._currentLink && this._linkAtPosition(this._currentLink.link, position); + if (!isCurrentLinkInPosition) { + this._clearCurrentLink(); + this._askForLink(position, true); } } - private _askForLink(position: IBufferCellPosition): void { - const providerReplies: Map = new Map(); + private _askForLink(position: IBufferCellPosition, useLineCache: boolean): void { + if (!this._activeProviderReplies || !useLineCache) { + this._activeProviderReplies = new Map(); + this._activeLine = position.y; + } let linkProvided = false; // There is no link cached, so ask for one this._linkProviders.forEach((linkProvider, i) => { - linkProvider.provideLink(position, (link: ILink | undefined) => { - if (this._isMouseOut) { - return; - } - providerReplies.set(i, link); - - // Check if every provider before this one has come back undefined - let hasLinkBefore = false; - for (let j = 0; j < i; j++) { - if (!providerReplies.has(j) || providerReplies.get(j)) { - hasLinkBefore = true; + const existingReply = this._activeProviderReplies?.get(i); + if (existingReply) { + linkProvided = this._checkLinkProviderResult(i, position, linkProvided); + } else { + linkProvider.provideLinks(position.y, (links: ILink[] | undefined) => { + if (this._isMouseOut) { + return; } - } - - // If all providers with higher priority came back undefined, then this link should be used - if (!hasLinkBefore && link) { - linkProvided = true; - this._handleNewLink(link); - } - - // Check if all the providers have responded - if (providerReplies.size === this._linkProviders.length && !linkProvided) { - // Respect the order of the link providers - for (let j = 0; j < providerReplies.size; j++) { - const currentLink = providerReplies.get(j); - if (currentLink) { - this._handleNewLink(currentLink); - break; - } - } - } - }); + const linksWithState: ILinkWithState[] | undefined = links?.map(link => ({ link })); + this._activeProviderReplies?.set(i, linksWithState); + linkProvided = this._checkLinkProviderResult(i, position, linkProvided); + }); + } }); } + private _checkLinkProviderResult(index: number, position: IBufferCellPosition, linkProvided: boolean): boolean { + if (!this._activeProviderReplies) { + return linkProvided; + } + + const links = this._activeProviderReplies.get(index); + + // Check if every provider before this one has come back undefined + let hasLinkBefore = false; + for (let j = 0; j < index; j++) { + if (!this._activeProviderReplies.has(j) || this._activeProviderReplies.get(j)) { + hasLinkBefore = true; + } + } + + // If all providers with higher priority came back undefined, then this provider's link for + // the position should be used + if (!hasLinkBefore && links) { + const linkAtPosition = links.find(link => this._linkAtPosition(link.link, position)); + if (linkAtPosition) { + linkProvided = true; + this._handleNewLink(linkAtPosition); + } + } + + // Check if all the providers have responded + if (this._activeProviderReplies.size === this._linkProviders.length && !linkProvided) { + // Respect the order of the link providers + for (let j = 0; j < this._activeProviderReplies.size; j++) { + const currentLink = this._activeProviderReplies.get(j)?.find(link => this._linkAtPosition(link.link, position)); + if (currentLink) { + linkProvided = true; + this._handleNewLink(currentLink); + break; + } + } + } + + return linkProvided; + } + private _onClick(event: MouseEvent): void { if (!this._element || !this._mouseService || !this._currentLink) { return; @@ -167,8 +200,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { return; } - if (this._linkAtPosition(this._currentLink, position)) { - this._currentLink.activate(event, this._currentLink.text); + if (this._linkAtPosition(this._currentLink.link, position)) { + this._currentLink.link.activate(event, this._currentLink.link.text); } } @@ -178,16 +211,14 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { } // If we have a start and end row, check that the link is within it - if (!startRow || !endRow || (this._currentLink.range.start.y >= startRow && this._currentLink.range.end.y <= endRow)) { - this._linkLeave(this._element, this._currentLink, this._lastMouseEvent); + if (!startRow || !endRow || (this._currentLink.link.range.start.y >= startRow && this._currentLink.link.range.end.y <= endRow)) { + this._linkLeave(this._element, this._currentLink.link, this._lastMouseEvent); this._currentLink = undefined; - this._currentLinkState = undefined; - this._linkCacheDisposables.forEach(l => l.dispose()); - this._linkCacheDisposables = []; + disposeArray(this._linkCacheDisposables); } } - private _handleNewLink(link: ILink): void { + private _handleNewLink(linkWithState: ILinkWithState): void { if (!this._element || !this._lastMouseEvent || !this._mouseService) { return; } @@ -199,38 +230,38 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { } // Trigger hover if the we have a link at the position - if (this._linkAtPosition(link, position)) { - this._currentLink = link; - this._currentLinkState = { + if (this._linkAtPosition(linkWithState.link, position)) { + this._currentLink = linkWithState; + this._currentLink.state = { decorations: { - underline: link.decorations === undefined ? true : link.decorations.underline, - pointerCursor: link.decorations === undefined ? true : link.decorations.pointerCursor + underline: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.underline, + pointerCursor: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.pointerCursor }, isHovered: true }; - this._linkHover(this._element, link, this._lastMouseEvent); + this._linkHover(this._element, linkWithState.link, this._lastMouseEvent); // Add listener for tracking decorations changes - link.decorations = {} as ILinkDecorations; - Object.defineProperties(link.decorations, { + linkWithState.link.decorations = {} as ILinkDecorations; + Object.defineProperties(linkWithState.link.decorations, { pointerCursor: { - get: () => this._currentLinkState?.decorations.pointerCursor, + get: () => this._currentLink?.state?.decorations.pointerCursor, set: v => { - if (this._currentLinkState && this._currentLinkState?.decorations.pointerCursor !== v) { - this._currentLinkState.decorations.pointerCursor = v; - if (this._currentLinkState.isHovered) { + if (this._currentLink?.state && this._currentLink.state.decorations.pointerCursor !== v) { + this._currentLink.state.decorations.pointerCursor = v; + if (this._currentLink.state.isHovered) { this._element?.classList.toggle('xterm-cursor-pointer', v); } } } }, underline: { - get: () => this._currentLinkState?.decorations.underline, + get: () => this._currentLink?.state?.decorations.underline, set: v => { - if (this._currentLinkState && this._currentLinkState?.decorations.underline !== v) { - this._currentLinkState.decorations.underline = v; - if (this._currentLinkState.isHovered) { - this._fireUnderlineEvent(link, v); + if (this._currentLink?.state && this._currentLink?.state?.decorations.underline !== v) { + this._currentLink.state.decorations.underline = v; + if (this._currentLink.state.isHovered) { + this._fireUnderlineEvent(linkWithState.link, v); } } } @@ -250,12 +281,12 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { } protected _linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void { - if (this._currentLinkState) { - this._currentLinkState.isHovered = true; - if (this._currentLinkState.decorations.underline) { + if (this._currentLink?.state) { + this._currentLink.state.isHovered = true; + if (this._currentLink.state.decorations.underline) { this._fireUnderlineEvent(link, true); } - if (this._currentLinkState.decorations.pointerCursor) { + if (this._currentLink.state.decorations.pointerCursor) { element.classList.add('xterm-cursor-pointer'); } } @@ -274,12 +305,12 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { } protected _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void { - if (this._currentLinkState) { - this._currentLinkState.isHovered = false; - if (this._currentLinkState.decorations.underline) { + if (this._currentLink?.state) { + this._currentLink.state.isHovered = false; + if (this._currentLink.state.decorations.underline) { this._fireUnderlineEvent(link, false); } - if (this._currentLinkState.decorations.pointerCursor) { + if (this._currentLink.state.decorations.pointerCursor) { element.classList.remove('xterm-cursor-pointer'); } } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 76aa0c8c..d0f8da15 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -264,7 +264,7 @@ export interface IMouseZone { } interface ILinkProvider { - provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void; + provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void; } interface ILink { diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 75e00152..54576ce7 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -557,12 +557,12 @@ describe('API Integration Tests', function(): void { }); describe('registerLinkProvider', () => { - it('should fire provideLink when hovering cells', async () => { + it('should fire provideLinks when hovering cells', async () => { await openTerminal(page, { rendererType: 'dom' }); await page.evaluate(` window.calls = []; window.disposable = window.term.registerLinkProvider({ - provideLink: (position, cb) => { + provideLinks: (position, cb) => { calls.push(position); cb(undefined); } @@ -572,7 +572,7 @@ describe('API Integration Tests', function(): void { await moveMouseCell(page, dims, 1, 1); await moveMouseCell(page, dims, 2, 2); await moveMouseCell(page, dims, 10, 4); - await pollFor(page, `window.calls`, [{ x: 1, y: 1 }, { x: 2, y: 2 }, { x: 10, y: 4 }]); + await pollFor(page, `window.calls`, [1, 2, 4]); await page.evaluate(`window.disposable.dispose()`); }); @@ -584,30 +584,30 @@ describe('API Integration Tests', function(): void { await page.evaluate(` window.calls = []; window.disposable = window.term.registerLinkProvider({ - provideLink: (position, cb) => { - window.calls.push('provide ' + position.x + ',' + position.y); - if (position.x >= 5 && position.x <= 7 && position.y === 1) { + provideLinks: (position, cb) => { + window.calls.push('provide ' + position); + if (position === 1) { window.calls.push('match'); - cb({ + cb([{ range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } }, text: 'bar', activate: () => window.calls.push('activate'), hover: () => window.calls.push('hover'), leave: () => window.calls.push('leave') - }); + }]); } } }); `); const dims = await getDimensions(); await moveMouseCell(page, dims, 5, 1); - await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover']); + await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover']); await moveMouseCell(page, dims, 4, 1); - await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover', 'leave', 'provide 4,1']); + await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover', 'leave', ]); await moveMouseCell(page, dims, 7, 1); - await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover', 'leave', 'provide 4,1', 'provide 7,1', 'match', 'hover']); + await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover', 'leave', 'hover']); await moveMouseCell(page, dims, 8, 1); - await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover', 'leave', 'provide 4,1', 'provide 7,1', 'match', 'hover', 'leave', 'provide 8,1']); + await pollFor(page, `window.calls`, ['provide 1', 'match', 'hover', 'leave', 'hover', 'leave']); await page.evaluate(`window.disposable.dispose()`); }); @@ -619,28 +619,35 @@ describe('API Integration Tests', function(): void { await page.evaluate(` window.calls = []; window.disposable = window.term.registerLinkProvider({ - provideLink: (position, cb) => { - window.calls.push('provide ' + position.x + ',' + position.y); - if (position.x >= 5 && position.x <= 7 && position.y === 1) { - window.calls.push('match'); - cb({ + provideLinks: (position, cb) => { + window.calls.push('provide ' + position); + if (position === 1) { + window.calls.push('match 1'); + cb([{ range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } }, text: 'bar', activate: () => window.calls.push('activate') - }); + }]); + } else if (position === 2) { + window.calls.push('match 2'); + cb([{ + range: { start: { x: 5, y: 2 }, end: { x: 7, y: 2 } }, + text: 'bar', + activate: () => window.calls.push('activate') + }]); } } }); `); const dims = await getDimensions(); await moveMouseCell(page, dims, 5, 1); - await pollFor(page, `window.calls`, ['provide 5,1', 'match']); - await moveMouseCell(page, dims, 4, 1); - await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'provide 4,1']); + await pollFor(page, `window.calls`, ['provide 1', 'match 1']); + await moveMouseCell(page, dims, 4, 2); + await pollFor(page, `window.calls`, ['provide 1', 'match 1', 'provide 2', 'match 2']); await moveMouseCell(page, dims, 7, 1); - await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'provide 4,1', 'provide 7,1', 'match']); - await moveMouseCell(page, dims, 8, 1); - await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'provide 4,1', 'provide 7,1', 'match', 'provide 8,1']); + await pollFor(page, `window.calls`, ['provide 1', 'match 1', 'provide 2', 'match 2', 'provide 1', 'match 1']); + await moveMouseCell(page, dims, 6, 2); + await pollFor(page, `window.calls`, ['provide 1', 'match 1', 'provide 2', 'match 2', 'provide 1', 'match 1', 'provide 2', 'match 2']); await page.evaluate(`window.disposable.dispose()`); }); @@ -656,38 +663,84 @@ describe('API Integration Tests', function(): void { await moveMouseCell(page, dims, 5, 5); await page.mouse.down(); await page.mouse.up(); - await timeout(50); // Not sure how to avoid this timeout, checking for xterm-focus doesn't help + await timeout(200); // Not sure how to avoid this timeout, checking for xterm-focus doesn't help await page.evaluate(` window.calls = []; window.disposable = window.term.registerLinkProvider({ - provideLink: (position, cb) => { - window.calls.push('provide ' + position.x + ',' + position.y); - cb({ - range: { start: position, end: position }, - text: window.term.buffer.active.getLine(position.y - 1).getCell(position.x - 1).getChars(), - activate: (_, text) => window.calls.push('activate ' + text), - hover: () => window.calls.push('hover'), - leave: () => window.calls.push('leave') - }); + provideLinks: (y, cb) => { + window.calls.push('provide ' + y); + cb([{ + range: { start: { x: 1, y }, end: { x: 80, y } }, + text: window.term.buffer.active.getLine(y - 1).translateToString(), + activate: (_, text) => window.calls.push('activate ' + y), + hover: () => window.calls.push('hover ' + y), + leave: () => window.calls.push('leave ' + y) + }]); } }); `); await moveMouseCell(page, dims, 3, 1); - await pollFor(page, `window.calls`, ['provide 3,1', 'hover']); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1']); await page.mouse.down(); await page.mouse.up(); - await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b']); - await moveMouseCell(page, dims, 1, 1); - await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover']); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1']); + await moveMouseCell(page, dims, 1, 2); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2']); await page.mouse.down(); await page.mouse.up(); - await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover', 'activate a']); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2', 'activate 2']); await moveMouseCell(page, dims, 5, 1); - await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover', 'activate a', 'leave', 'provide 5,1', 'hover']); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2', 'activate 2', 'leave 2', 'provide 1', 'hover 1']); await page.mouse.down(); await page.mouse.up(); - await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover', 'activate a', 'leave', 'provide 5,1', 'hover', 'activate c']); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1', 'activate 1', 'leave 1', 'provide 2', 'hover 2', 'activate 2', 'leave 2', 'provide 1', 'hover 1', 'activate 1']); + await page.evaluate(`window.disposable.dispose()`); + }); + + it('should work when multiple links are provided on the same line', async () => { + await openTerminal(page, { rendererType: 'dom' }); + await writeSync(page, 'foo bar baz'); + // Wait for renderer to catch up as links are cleared on render + await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz '); + await page.evaluate(` + window.calls = []; + window.disposable = window.term.registerLinkProvider({ + provideLinks: (position, cb) => { + window.calls.push('provide ' + position); + if (position === 1) { + cb([{ + range: { start: { x: 1, y: 1 }, end: { x: 3, y: 1 } }, + text: '', + activate: () => window.calls.push('activate'), + hover: () => window.calls.push('hover 1-3'), + leave: () => window.calls.push('leave 1-3') + }, { + range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } }, + text: '', + activate: () => window.calls.push('activate'), + hover: () => window.calls.push('hover 5-7'), + leave: () => window.calls.push('leave 5-7') + }, { + range: { start: { x: 9, y: 1 }, end: { x: 11, y: 1 } }, + text: '', + activate: () => window.calls.push('activate'), + hover: () => window.calls.push('hover 9-11'), + leave: () => window.calls.push('leave 9-11') + }]); + } + } + }); + `); + const dims = await getDimensions(); + await moveMouseCell(page, dims, 2, 1); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3']); + await moveMouseCell(page, dims, 6, 1); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7']); + await moveMouseCell(page, dims, 6, 2); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'provide 2']); + await moveMouseCell(page, dims, 10, 1); + await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'provide 2', 'provide 1', 'hover 9-11']); await page.evaluate(`window.disposable.dispose()`); }); }); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index dfda8583..df82ae61 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1100,11 +1100,12 @@ declare module 'xterm' { interface ILinkProvider { /** * Provides a link a buffer position - * @param position The position of the buffer that is currently active. - * @param callback The callback to be fired with the resulting link or - * `undefined` when ready. + * @param bufferLineNumber The y position of the buffer to check for links + * within. + * @param callback The callback to be fired when ready with the resulting + * link(s) for the line or `undefined`. */ - provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void; + provideLinks(bufferLineNumber: number, callback: (links: ILink[] | undefined) => void): void; } /**