Fixed link caching errors
This commit is contained in:
@@ -15,7 +15,7 @@ const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('WebLinksAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
before(async function (): Promise<any> {
|
||||
this.timeout(10000);
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
@@ -30,22 +30,22 @@ describe('WebLinksAddon', () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
beforeEach(async function (): Promise<any> {
|
||||
this.timeout(5000);
|
||||
await page.goto(APP);
|
||||
});
|
||||
|
||||
it('.com', async function(): Promise<any> {
|
||||
it('.com', async function (): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await testHostName('foo.com');
|
||||
});
|
||||
|
||||
it('.com.au', async function(): Promise<any> {
|
||||
it('.com.au', async function (): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await testHostName('foo.com.au');
|
||||
});
|
||||
|
||||
it('.io', async function(): Promise<any> {
|
||||
it('.io', async function (): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await testHostName('foo.io');
|
||||
});
|
||||
|
||||
+1
-1
@@ -292,7 +292,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.register(this._inputHandler);
|
||||
|
||||
this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService);
|
||||
this.linkifier2 = this.linkifier2 || new Linkifier2(this._bufferService);
|
||||
this.linkifier2 = this.linkifier2 || new Linkifier2(this._bufferService, this._coreService);
|
||||
|
||||
if (this.options.windowsMode) {
|
||||
this._windowsMode = applyWindowsMode(this);
|
||||
|
||||
+91
-33
@@ -1,7 +1,7 @@
|
||||
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent } from './Types';
|
||||
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, IBufferRange } from './Types';
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IMouseService } from './services/Services';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
import { IBufferService, ICoreService } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
|
||||
/**
|
||||
@@ -17,7 +17,9 @@ export class Linkifier2 implements ILinkifier2 {
|
||||
private _viewportElement: HTMLElement | undefined;
|
||||
private _linkProviders: ILinkProvider[] = [];
|
||||
private _mouseService: IMouseService | undefined;
|
||||
private _linkCache: ILinkCache[] = [];
|
||||
private _linkCache: ICachedLink[] = [];
|
||||
private _lastMouseEvent: MouseEvent | undefined;
|
||||
private _mouseOverLink: boolean = false;
|
||||
|
||||
private _onShowTooltip = new EventEmitter<ILinkifierEvent>();
|
||||
public get onShowTooltip(): IEvent<ILinkifierEvent> { return this._onShowTooltip.event; }
|
||||
@@ -25,7 +27,8 @@ export class Linkifier2 implements ILinkifier2 {
|
||||
public get onHideTooltip(): IEvent<ILinkifierEvent> { return this._onHideTooltip.event; }
|
||||
|
||||
constructor(
|
||||
private readonly _bufferService: IBufferService
|
||||
private readonly _bufferService: IBufferService,
|
||||
private readonly _coreService: ICoreService
|
||||
) {
|
||||
|
||||
}
|
||||
@@ -35,7 +38,7 @@ export class Linkifier2 implements ILinkifier2 {
|
||||
return {
|
||||
dispose: () => {
|
||||
// Remove the link provider from the list
|
||||
this._linkProviders = this._linkProviders.splice(this._linkProviders.indexOf(linkProvider), 1);
|
||||
this._linkProviders.splice(this._linkProviders.indexOf(linkProvider), 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -48,9 +51,13 @@ export class Linkifier2 implements ILinkifier2 {
|
||||
this._element.addEventListener('mousemove', this._onMouseMove.bind(this));
|
||||
this._element.addEventListener('click', this._onMouseDown.bind(this));
|
||||
this._viewportElement.addEventListener('scroll', this._onScroll.bind(this));
|
||||
|
||||
this._coreService.onData(this._onData.bind(this));
|
||||
}
|
||||
|
||||
private _onMouseMove(event: MouseEvent): void {
|
||||
this._lastMouseEvent = event;
|
||||
|
||||
if (!this._element || !this._mouseService) {
|
||||
return;
|
||||
}
|
||||
@@ -61,38 +68,40 @@ export class Linkifier2 implements ILinkifier2 {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollOffset = this._bufferService.buffer.ydisp;
|
||||
|
||||
// Check the cache for a link and determine if we need to show or hide tooltip
|
||||
let foundLink = false;
|
||||
let mouseOver = false;
|
||||
for (let i = 0; i < this._linkCache.length; i++) {
|
||||
const cachedLink = this._linkCache[i].link;
|
||||
const isInPosition = this._linkAtPosition(cachedLink, position);
|
||||
const range = cachedLink.range;
|
||||
|
||||
// Check if the mouse position contains a link
|
||||
if (isInPosition && !this._linkCache[i].mouseOver) {
|
||||
// Also check if it isn't the current line
|
||||
if (isInPosition && !this._linkCache[i].mouseOver && position.y < this._bufferService.buffer.y) {
|
||||
// Show the tooltip
|
||||
this._onShowTooltip.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x - 1, range.end.y - scrollOffset - 1, undefined));
|
||||
this._element.classList.add('xterm-cursor-pointer');
|
||||
|
||||
if (cachedLink.showTooltip) {
|
||||
cachedLink.showTooltip(event, cachedLink.url);
|
||||
}
|
||||
|
||||
this._showTooltip(this._element, this._linkCache[i].link, event);
|
||||
this._linkCache[i].mouseOver = true;
|
||||
foundLink = true;
|
||||
this._mouseOverLink = true;
|
||||
} else if (!isInPosition && this._linkCache[i].mouseOver) {
|
||||
// Hide the tooltip
|
||||
this._hideTooltip(this._element, this._linkCache[i].link, event);
|
||||
this._linkCache[i].mouseOver = false;
|
||||
}
|
||||
|
||||
if (isInPosition) {
|
||||
mouseOver = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mouseOver) {
|
||||
this._mouseOverLink = false;
|
||||
}
|
||||
|
||||
// The is no link in the cache, so ask for one
|
||||
this._linkProviders.forEach(linkProvider => {
|
||||
linkProvider.provideLink(position, this._handleNewLink.bind(this));
|
||||
@@ -118,30 +127,63 @@ export class Linkifier2 implements ILinkifier2 {
|
||||
}
|
||||
|
||||
private _onScroll(event: Event): void {
|
||||
this._invalidateCache();
|
||||
if (this._lastMouseEvent && this._mouseOverLink) {
|
||||
this._hideAllTooltips();
|
||||
}
|
||||
}
|
||||
|
||||
private _onData(e: string): void {
|
||||
if (this._lastMouseEvent && this._mouseOverLink) {
|
||||
const index = this._hideAllTooltips();
|
||||
this._linkCache.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleNewLink(link: ILink | undefined): void {
|
||||
if (link && !this._linkCache.find(cachedLink => cachedLink.link = link)) {
|
||||
this._linkCache.push({ link, mouseOver: false });
|
||||
this._bufferService.buffer.addMarker(link.range.start.y);
|
||||
}
|
||||
}
|
||||
|
||||
private _invalidateCache(): void {
|
||||
if (!this._element) {
|
||||
if (!link || !this._element || !this._lastMouseEvent || !this._mouseService) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to invalidate the cache
|
||||
// but we need to check if we need to hide any tooltip
|
||||
for (let i = 0; i < this._linkCache.length; i++) {
|
||||
if (this._linkCache[i].mouseOver) {
|
||||
this._hideTooltip(this._element, this._linkCache[i].link, new MouseEvent('invalid event'));
|
||||
}
|
||||
// Check if the link at this position is already cached
|
||||
let linkIndex = this._linkCache.findIndex(cachedLink => {
|
||||
return cachedLink.link.url === link.url &&
|
||||
cachedLink.link.range.start.x === link.range.start.x &&
|
||||
cachedLink.link.range.start.y === link.range.start.y &&
|
||||
cachedLink.link.range.end.x === link.range.end.x &&
|
||||
cachedLink.link.range.end.y === link.range.end.y;
|
||||
});
|
||||
|
||||
const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element, this._mouseService);
|
||||
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._linkCache = [];
|
||||
const linkAtPosition = this._linkAtPosition(link, position);
|
||||
|
||||
if (linkIndex === -1) {
|
||||
this._linkCache.push({ link, mouseOver: false });
|
||||
linkIndex = this._linkCache.length - 1;
|
||||
}
|
||||
|
||||
// Show the tooltip if the last mouse event was over it
|
||||
if (linkAtPosition && !this._linkCache[linkIndex].mouseOver) {
|
||||
this._showTooltip(this._element, link, this._lastMouseEvent);
|
||||
this._linkCache[linkIndex].mouseOver = true;
|
||||
this._mouseOverLink = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _showTooltip(element: HTMLElement, link: ILink, event: MouseEvent): void {
|
||||
const range = link.range;
|
||||
const scrollOffset = this._bufferService.buffer.ydisp;
|
||||
|
||||
this._onShowTooltip.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x - 1, range.end.y - scrollOffset - 1, undefined));
|
||||
element.classList.add('xterm-cursor-pointer');
|
||||
|
||||
if (link.showTooltip) {
|
||||
link.showTooltip(event, link.url);
|
||||
}
|
||||
}
|
||||
|
||||
private _hideTooltip(element: HTMLElement, link: ILink, event: MouseEvent): void {
|
||||
@@ -156,6 +198,22 @@ export class Linkifier2 implements ILinkifier2 {
|
||||
}
|
||||
}
|
||||
|
||||
private _hideAllTooltips(): number {
|
||||
if (!this._element) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Hide all the tooltips
|
||||
for (let i = 0; i < this._linkCache.length; i++) {
|
||||
if (this._linkCache[i].mouseOver) {
|
||||
this._hideTooltip(this._element, this._linkCache[i].link, new MouseEvent('invalid event'));
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the buffer position is within the link
|
||||
* @param link
|
||||
@@ -186,7 +244,7 @@ export class Linkifier2 implements ILinkifier2 {
|
||||
}
|
||||
}
|
||||
|
||||
interface ILinkCache {
|
||||
interface ICachedLink {
|
||||
link: ILink;
|
||||
mouseOver: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user