diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 35610acb..1e4c0cdc 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -42,6 +42,7 @@ describe('Linkifier', () => { beforeEach(() => { terminal = new MockTerminal(); terminal.cols = 100; + terminal.rows = 10; terminal.buffer = new MockBuffer(); (terminal.buffer).setLines(new CircularList(20)); terminal.buffer.ydisp = 0; @@ -64,6 +65,7 @@ describe('Linkifier', () => { function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void { addRow(rowText); linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); + terminal.rows = terminal.buffer.lines.length - 1; linkifier.linkifyRows(); // Allow linkify to happen setTimeout(() => { diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 4d84b1a7..0dd33a85 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -81,18 +81,22 @@ export class Linkifier extends EventEmitter implements ILinkifier { */ private _linkifyRows(): void { this._rowsTimeoutId = null; + const buffer = this._terminal.buffer; - // Ensure the row exists - const absoluteRowIndexStart = this._terminal.buffer.ydisp + this._rowsToLinkify.start; - if (absoluteRowIndexStart >= this._terminal.buffer.lines.length) { + // Ensure the start row exists + const absoluteRowIndexStart = buffer.ydisp + this._rowsToLinkify.start; + if (absoluteRowIndexStart >= buffer.lines.length) { return; } + // Invalidate bad end row values (if a resize happened) + const absoluteRowIndexEnd = buffer.ydisp + Math.min(this._rowsToLinkify.end, this._terminal.rows) + 1; + // iterate over the range of unwrapped content strings within start..end (excluding) // _doLinkifyRow gets full unwrapped lines with the start row as buffer offset for every matcher // for wrapped content over several rows the iterator might return rows outside the viewport // we skip those later in _doLinkifyRow - const iterator = this._terminal.buffer.iterator(false, absoluteRowIndexStart, this._terminal.buffer.ydisp + this._rowsToLinkify.end + 1); + const iterator = buffer.iterator(false, absoluteRowIndexStart, absoluteRowIndexEnd); while (iterator.hasNext()) { const lineData: IBufferStringIteratorResult = iterator.next(); for (let i = 0; i < this._linkMatchers.length; i++) { diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 9a2ef469..8f51c4ef 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions, IColorSet } from '../Types'; -import { ITerminal, CharacterJoinerHandler } from '../../Types'; +import { ILinkHoverEvent, ITerminal, CharacterJoinerHandler, LinkHoverEventTypes } from '../../Types'; import { ITheme } from 'xterm'; import { EventEmitter } from '../../common/EventEmitter'; import { ColorManager } from '../ColorManager'; @@ -79,6 +79,9 @@ export class DomRenderer extends EventEmitter implements IRenderer { this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); this._terminal.screenElement.appendChild(this._rowContainer); this._terminal.screenElement.appendChild(this._selectionContainer); + + this._terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: ILinkHoverEvent) => this._onLinkHover(e)); + this._terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e)); } public dispose(): void { @@ -338,4 +341,23 @@ export class DomRenderer extends EventEmitter implements IRenderer { public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; } public deregisterCharacterJoiner(joinerId: number): boolean { return false; } + + private _onLinkHover(e: ILinkHoverEvent): void { + this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true); + } + + private _onLinkLeave(e: ILinkHoverEvent): void { + this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false); + } + + private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void { + while (x !== x2 || y !== y2) { + const span = this._rowElements[y].children[x]; + span.style.textDecoration = enabled ? 'underline' : 'none'; + x = (x + 1) % cols; + if (x === 0) { + y++; + } + } + } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c82e821e..3982874e 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -150,7 +150,6 @@ declare module 'xterm' { * when canvas is too slow for the environment. The following features do * not work when the DOM renderer is used: * - * - Link underlines * - Line height * - Letter spacing * - Cursor blink