mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge pull request #2908 from Tyriar/line_links
Change link provider API to provide all links for a line
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
+110
-79
@@ -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<Number, ILinkWithState[] | undefined> | undefined;
|
||||
private _activeLine: number = -1;
|
||||
|
||||
private _onShowLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
|
||||
public get onShowLinkUnderline(): IEvent<ILinkifierEvent> { 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<Number, ILink | undefined> = 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');
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -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 {
|
||||
|
||||
+94
-41
@@ -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()`);
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+5
-4
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user