diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index ebf773d3..10cf9c1f 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -16,11 +16,11 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); - terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); - terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); + terminal.linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); + terminal.linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); - terminal.linkifier2.onLinkHover(e => this._onLinkHover(e)); - terminal.linkifier2.onLinkLeave(e => this._onLinkLeave(e)); + terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); + terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); } public resize(terminal: Terminal, dim: IRenderDimensions): void { @@ -45,7 +45,7 @@ export class LinkRenderLayer extends BaseRenderLayer { } } - private _onLinkHover(e: ILinkifierEvent): void { + private _onShowLinkUnderline(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; } else if (e.fg !== undefined && is256Color(e.fg)) { @@ -69,7 +69,7 @@ export class LinkRenderLayer extends BaseRenderLayer { this._state = e; } - private _onLinkLeave(e: ILinkifierEvent): void { + private _onHideLinkUnderline(e: ILinkifierEvent): void { this._clearCurrentLink(); } } diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index 971501b7..ca45ef2a 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -35,10 +35,10 @@ export class Linkifier implements ILinkifier { private _nextLinkMatcherId = 0; private _rowsToLinkify: { start: number | undefined, end: number | undefined }; - private _onLinkHover = new EventEmitter(); - public get onLinkHover(): IEvent { return this._onLinkHover.event; } - private _onLinkLeave = new EventEmitter(); - public get onLinkLeave(): IEvent { return this._onLinkLeave.event; } + private _onShowLinkUnderline = new EventEmitter(); + public get onShowLinkUnderline(): IEvent { return this._onShowLinkUnderline.event; } + private _onHideLinkUnderline = new EventEmitter(); + public get onHideLinkUnderline(): IEvent { return this._onHideLinkUnderline.event; } private _onLinkTooltip = new EventEmitter(); public get onLinkTooltip(): IEvent { return this._onLinkTooltip.event; } @@ -307,7 +307,7 @@ export class Linkifier implements ILinkifier { } }, () => { - this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + this._onShowLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); this._element!.classList.add('xterm-cursor-pointer'); }, e => { @@ -319,7 +319,7 @@ export class Linkifier implements ILinkifier { } }, () => { - this._onLinkLeave.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + this._onHideLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); this._element!.classList.remove('xterm-cursor-pointer'); if (matcher.hoverLeaveCallback) { matcher.hoverLeaveCallback(); diff --git a/src/browser/Linkifier2.test.ts b/src/browser/Linkifier2.test.ts index bcf068a8..36a6da43 100644 --- a/src/browser/Linkifier2.test.ts +++ b/src/browser/Linkifier2.test.ts @@ -48,7 +48,7 @@ describe('Linkifier2', () => { }; it('onLinkHover event range is correct', done => { - linkifier.onLinkHover(e => { + linkifier.onShowLinkUnderline(e => { assert.equal(link.range.start.x - 1, e.x1); assert.equal(link.range.start.y - 1, e.y1); assert.equal(link.range.end.x, e.x2); @@ -61,7 +61,7 @@ describe('Linkifier2', () => { }); it('onLinkLeave event range is correct', done => { - linkifier.onLinkLeave(e => { + linkifier.onHideLinkUnderline(e => { assert.equal(link.range.start.x - 1, e.x1); assert.equal(link.range.start.y - 1, e.y1); assert.equal(link.range.end.x, e.x2); diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 128c21ee..b4db56b3 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -9,20 +9,25 @@ import { IMouseService, IRenderService } from './services/Services'; import { IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; +interface ILinkState { + isHovered: boolean; +} + export class Linkifier2 implements ILinkifier2 { private _element: HTMLElement | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; private _linkProviders: ILinkProvider[] = []; private _currentLink: ILink | undefined; + private _currentLinkState: ILinkState | undefined; private _lastMouseEvent: MouseEvent | undefined; private _linkCacheDisposables: IDisposable[] = []; private _lastBufferCell: IBufferCellPosition | undefined; - private _onLinkHover = new EventEmitter(); - public get onLinkHover(): IEvent { return this._onLinkHover.event; } - private _onLinkLeave = new EventEmitter(); - public get onLinkLeave(): IEvent { return this._onLinkLeave.event; } + private _onShowLinkUnderline = new EventEmitter(); + public get onShowLinkUnderline(): IEvent { return this._onShowLinkUnderline.event; } + private _onHideLinkUnderline = new EventEmitter(); + public get onHideLinkUnderline(): IEvent { return this._onHideLinkUnderline.event; } constructor( private readonly _bufferService: IBufferService @@ -151,6 +156,7 @@ export class Linkifier2 implements ILinkifier2 { if (!startRow || !endRow || (this._currentLink.range.start.y >= startRow && this._currentLink.range.end.y <= endRow)) { this._linkLeave(this._element, this._currentLink, this._lastMouseEvent); this._currentLink = undefined; + this._currentLinkState = undefined; this._linkCacheDisposables.forEach(l => l.dispose()); this._linkCacheDisposables = []; } @@ -170,8 +176,25 @@ export class Linkifier2 implements ILinkifier2 { // Trigger hover if the we have a link at the position if (this._linkAtPosition(link, position)) { this._currentLink = link; + this._currentLinkState = { isHovered: true }; this._linkHover(this._element, link, this._lastMouseEvent); + // Add listener for tracking hideUnderline changes + let hideUnderlineValue = link.hideUnderline || false; + Object.defineProperties(link, { + hideUnderline: { + get: () => hideUnderlineValue, + set: v => { + if (hideUnderlineValue !== v) { + hideUnderlineValue = v; + if (this._currentLinkState?.isHovered) { + this._fireUnderlineEvent(link, !v); + } + } + } + } + }); + // Add listener for rerendering if (this._renderService) { this._linkCacheDisposables.push(this._renderService.onRenderedBufferChange(e => { @@ -182,11 +205,11 @@ export class Linkifier2 implements ILinkifier2 { } protected _linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void { - const range = link.range; - const scrollOffset = this._bufferService.buffer.ydisp; - if (!link.hideUnderline) { - this._onLinkHover.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined)); + this._fireUnderlineEvent(link, true); + } + if (this._currentLinkState) { + this._currentLinkState.isHovered = true; } element.classList.add('xterm-cursor-pointer'); @@ -195,11 +218,21 @@ export class Linkifier2 implements ILinkifier2 { } } - protected _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void { + private _fireUnderlineEvent(link: ILink, showEvent: boolean): void { const range = link.range; const scrollOffset = this._bufferService.buffer.ydisp; + const event = this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined); + const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline; + emitter.fire(event); + } - this._onLinkLeave.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined)); + protected _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void { + if (!link.hideUnderline) { + this._fireUnderlineEvent(link, false); + } + if (this._currentLinkState) { + this._currentLinkState.isHovered = false; + } element.classList.remove('xterm-cursor-pointer'); if (link.leave) { diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index fc790a7e..b443e486 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -96,8 +96,8 @@ export interface ILinkifierEvent { } export interface ILinkifier { - onLinkHover: IEvent; - onLinkLeave: IEvent; + onShowLinkUnderline: IEvent; + onHideLinkUnderline: IEvent; onLinkTooltip: IEvent; attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void; @@ -107,8 +107,8 @@ export interface ILinkifier { } export interface ILinkifier2 { - onLinkHover: IEvent; - onLinkLeave: IEvent; + onShowLinkUnderline: IEvent; + onHideLinkUnderline: IEvent; attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void; registerLinkProvider(linkProvider: ILinkProvider): IDisposable; diff --git a/src/browser/renderer/LinkRenderLayer.ts b/src/browser/renderer/LinkRenderLayer.ts index 33f80cdc..c41955d9 100644 --- a/src/browser/renderer/LinkRenderLayer.ts +++ b/src/browser/renderer/LinkRenderLayer.ts @@ -24,11 +24,11 @@ export class LinkRenderLayer extends BaseRenderLayer { optionsService: IOptionsService ) { super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService); - linkifier.onLinkHover(e => this._onLinkHover(e)); - linkifier.onLinkLeave(e => this._onLinkLeave(e)); + linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); + linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); - linkifier2.onLinkHover(e => this._onLinkHover(e)); - linkifier2.onLinkLeave(e => this._onLinkLeave(e)); + linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); + linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); } public resize(dim: IRenderDimensions): void { @@ -53,7 +53,7 @@ export class LinkRenderLayer extends BaseRenderLayer { } } - private _onLinkHover(e: ILinkifierEvent): void { + private _onShowLinkUnderline(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; } else if (e.fg && is256Color(e.fg)) { @@ -77,7 +77,7 @@ export class LinkRenderLayer extends BaseRenderLayer { this._state = e; } - private _onLinkLeave(e: ILinkifierEvent): void { + private _onHideLinkUnderline(e: ILinkifierEvent): void { this._clearCurrentLink(); } } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index a2e370fc..1303ec1b 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -86,11 +86,11 @@ export class DomRenderer extends Disposable implements IRenderer { this._screenElement.appendChild(this._rowContainer); this._screenElement.appendChild(this._selectionContainer); - this._linkifier.onLinkHover(e => this._onLinkHover(e)); - this._linkifier.onLinkLeave(e => this._onLinkLeave(e)); + this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e)); + this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e)); - this._linkifier2.onLinkHover(e => this._onLinkHover(e)); - this._linkifier2.onLinkLeave(e => this._onLinkLeave(e)); + this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e)); + this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e)); } public dispose(): void { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9830f4d9..7570a521 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1121,7 +1121,8 @@ declare module 'xterm' { text: string; /** - * Whether to hide the link's underline. + * Whether to hide the underline, this property is tracked and changes after the link is + * provided will trigger changes. */ hideUnderline?: boolean;