From a696f4747a46367177fa683e37587210933789f0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 13 Jul 2019 13:18:44 -0700 Subject: [PATCH 1/6] Move clipboard to browser --- src/Terminal.ts | 2 +- src/{ => browser}/Clipboard.test.ts | 2 +- src/{ => browser}/Clipboard.ts | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) rename src/{ => browser}/Clipboard.test.ts (95%) rename src/{ => browser}/Clipboard.ts (97%) diff --git a/src/Terminal.ts b/src/Terminal.ts index a8912b3f..a97eeeed 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -25,7 +25,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from './Viewport'; -import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './Clipboard'; +import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './browser/Clipboard'; import { C0 } from 'common/data/EscapeSequences'; import { InputHandler } from './InputHandler'; import { Renderer } from './renderer/Renderer'; diff --git a/src/Clipboard.test.ts b/src/browser/Clipboard.test.ts similarity index 95% rename from src/Clipboard.test.ts rename to src/browser/Clipboard.test.ts index 07c0f66e..aa7bdabd 100644 --- a/src/Clipboard.test.ts +++ b/src/browser/Clipboard.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import * as Clipboard from './Clipboard'; +import * as Clipboard from 'browser/Clipboard'; describe('evaluatePastedTextProcessing', () => { it('should replace carriage return and/or line feed with carriage return', () => { diff --git a/src/Clipboard.ts b/src/browser/Clipboard.ts similarity index 97% rename from src/Clipboard.ts rename to src/browser/Clipboard.ts index f4b415de..f56868cc 100644 --- a/src/Clipboard.ts +++ b/src/browser/Clipboard.ts @@ -29,7 +29,9 @@ export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): * @param ev The original copy event to be handled */ export function copyHandler(ev: ClipboardEvent, selectionService: ISelectionService): void { - ev.clipboardData.setData('text/plain', selectionService.selectionText); + if (ev.clipboardData) { + ev.clipboardData.setData('text/plain', selectionService.selectionText); + } // Prevent or the original text will be copied. ev.preventDefault(); } From 1ffa568a2fe16ae7d87e4ba6a7dc324390467594 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 13 Jul 2019 13:47:17 -0700 Subject: [PATCH 2/6] Remove ITerminal from Linkifier --- src/Linkifier.test.ts | 49 +++++++++++++++++++++---------------------- src/Linkifier.ts | 47 ++++++++++++++++++++++------------------- src/Terminal.ts | 6 ++---- src/Types.d.ts | 2 +- 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index ac1fb8b3..973e6d9b 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -4,23 +4,23 @@ */ import { assert } from 'chai'; -import { IMouseZoneManager, IMouseZone, ILinkMatcher, ITerminal } from './Types'; +import { IMouseZoneManager, IMouseZone, ILinkMatcher } from './Types'; import { IBufferLine } from 'common/Types'; import { Linkifier } from './Linkifier'; -import { MockBuffer, MockTerminal, TestTerminal } from './TestUtils.test'; -import { CircularList } from 'common/CircularList'; +import { TestTerminal } from './TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { MockLogService } from 'common/TestUtils.test'; +import { MockLogService, MockBufferService } from 'common/TestUtils.test'; +import { IBufferService } from 'common/services/Services'; class TestLinkifier extends Linkifier { - constructor(terminal: ITerminal) { - super(terminal, new MockLogService()); + constructor(bufferService: IBufferService) { + super(bufferService, new MockLogService()); Linkifier._timeBeforeLatency = 0; } public get linkMatchers(): ILinkMatcher[] { return this._linkMatchers; } - public linkifyRows(): void { super.linkifyRows(0, this._terminal.buffer.lines.length - 1); } + public linkifyRows(): void { super.linkifyRows(0, this._bufferService.buffer.lines.length - 1); } } class TestMouseZoneManager implements IMouseZoneManager { @@ -37,18 +37,13 @@ class TestMouseZoneManager implements IMouseZoneManager { } describe('Linkifier', () => { - let terminal: ITerminal; + let bufferService: IBufferService; let linkifier: TestLinkifier; let mouseZoneManager: TestMouseZoneManager; beforeEach(() => { - terminal = new MockTerminal(); - (terminal as any).cols = 100; - (terminal as any).rows = 10; - terminal.buffer = new MockBuffer(); - (terminal.buffer).setLines(new CircularList(20)); - terminal.buffer.ydisp = 0; - linkifier = new TestLinkifier(terminal); + bufferService = new MockBufferService(100, 10); + linkifier = new TestLinkifier(bufferService); mouseZoneManager = new TestMouseZoneManager(); }); @@ -61,13 +56,12 @@ describe('Linkifier', () => { } function addRow(text: string): void { - terminal.buffer.lines.push(stringToRow(text)); + bufferService.buffer.lines.push(stringToRow(text)); } function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void { addRow(rowText); linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); - (terminal as any).rows = terminal.buffer.lines.length - 1; linkifier.linkifyRows(); // Allow linkify to happen setTimeout(() => { @@ -75,8 +69,8 @@ describe('Linkifier', () => { links.forEach((l, i) => { assert.equal(mouseZoneManager.zones[i].x1, l.x + 1); assert.equal(mouseZoneManager.zones[i].x2, l.x + l.length + 1); - assert.equal(mouseZoneManager.zones[i].y1, terminal.buffer.lines.length); - assert.equal(mouseZoneManager.zones[i].y2, terminal.buffer.lines.length); + assert.equal(mouseZoneManager.zones[i].y1, bufferService.buffer.lines.length); + assert.equal(mouseZoneManager.zones[i].y2, bufferService.buffer.lines.length); }); done(); }, 0); @@ -111,7 +105,7 @@ describe('Linkifier', () => { describe('after attachToDom', () => { beforeEach(() => { - linkifier.attachToDom(mouseZoneManager); + linkifier.attachToDom(null, mouseZoneManager); }); describe('link matcher', () => { @@ -144,19 +138,23 @@ describe('Linkifier', () => { }); describe('multi-line links', () => { it('should match links that start on line 1/2 of a wrapped line and end on the last character of line 1/2', done => { - (terminal as any).cols = 4; + bufferService.resize(4, bufferService.rows); + bufferService.buffer.lines.length = 0; assertLinkifiesMultiLineLink('12345', /1234/, [{x1: 0, x2: 4, y1: 0, y2: 0}], done); }); it('should match links that start on line 1/2 of a wrapped line and wrap to line 2/2', done => { - (terminal as any).cols = 4; + bufferService.resize(4, bufferService.rows); + bufferService.buffer.lines.length = 0; assertLinkifiesMultiLineLink('12345', /12345/, [{x1: 0, x2: 1, y1: 0, y2: 1}], done); }); it('should match links that start and end on line 2/2 of a wrapped line', done => { - (terminal as any).cols = 4; + bufferService.resize(4, bufferService.rows); + bufferService.buffer.lines.length = 0; assertLinkifiesMultiLineLink('12345678', /5678/, [{x1: 0, x2: 4, y1: 1, y2: 1}], done); }); it('should match links that start on line 2/3 of a wrapped line and wrap to line 3/3', done => { - (terminal as any).cols = 4; + bufferService.resize(4, bufferService.rows); + bufferService.buffer.lines.length = 0; assertLinkifiesMultiLineLink('123456789', /56789/, [{x1: 0, x2: 1, y1: 1, y2: 2}], done); }); }); @@ -164,6 +162,7 @@ describe('Linkifier', () => { describe('validationCallback', () => { it('should enable link if true', done => { + bufferService.buffer.lines.length = 0; addRow('test'); linkifier.registerLinkMatcher(/test/, () => done(), { validationCallback: (url, cb) => { @@ -251,7 +250,7 @@ describe('Linkifier', () => { terminal = new TestTerminal({cols: 10, rows: 5}); linkifier = new TestLinkifier(terminal); mouseZoneManager = new TestMouseZoneManager(); - linkifier.attachToDom(mouseZoneManager); + linkifier.attachToDom(null, mouseZoneManager); }); function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void { diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 769f3a50..ee585a48 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IMouseZoneManager } from './Types'; +import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager } from './Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; import { MouseZone } from './MouseZoneManager'; import { getStringCellWidth } from 'common/CharWidth'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ILogService } from 'common/services/Services'; +import { ILogService, IBufferService } from 'common/services/Services'; /** * Limit of the unwrapping line expansion (overscan) at the top and bottom @@ -30,7 +30,9 @@ export class Linkifier implements ILinkifier { protected _linkMatchers: ILinkMatcher[] = []; - private _mouseZoneManager: IMouseZoneManager; + private _mouseZoneManager: IMouseZoneManager | undefined; + private _element: HTMLElement | undefined; + private _rowsTimeoutId: number; private _nextLinkMatcherId = 0; private _rowsToLinkify: { start: number, end: number }; @@ -43,8 +45,8 @@ export class Linkifier implements ILinkifier { public get onLinkTooltip(): IEvent { return this._onLinkTooltip.event; } constructor( - protected _terminal: ITerminal, - private _logService: ILogService + protected readonly _bufferService: IBufferService, + private readonly _logService: ILogService ) { this._rowsToLinkify = { start: null, @@ -56,7 +58,8 @@ export class Linkifier implements ILinkifier { * Attaches the linkifier to the DOM, enabling linkification. * @param mouseZoneManager The mouse zone manager to register link zones with. */ - public attachToDom(mouseZoneManager: IMouseZoneManager): void { + public attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void { + this._element = element; this._mouseZoneManager = mouseZoneManager; } @@ -95,7 +98,7 @@ export class Linkifier implements ILinkifier { */ private _linkifyRows(): void { this._rowsTimeoutId = null; - const buffer = this._terminal.buffer; + const buffer = this._bufferService.buffer; // Ensure the start row exists const absoluteRowIndexStart = buffer.ydisp + this._rowsToLinkify.start; @@ -104,7 +107,7 @@ export class Linkifier implements ILinkifier { } // Invalidate bad end row values (if a resize happened) - const absoluteRowIndexEnd = buffer.ydisp + Math.min(this._rowsToLinkify.end, this._terminal.rows) + 1; + const absoluteRowIndexEnd = buffer.ydisp + Math.min(this._rowsToLinkify.end, this._bufferService.rows) + 1; // Iterate over the range of unwrapped content strings within start..end // (excluding). @@ -116,8 +119,8 @@ export class Linkifier implements ILinkifier { // the viewport to +OVERSCAN_CHAR_LIMIT chars (overscan) at top and bottom. // This comes with the tradeoff that matches longer than OVERSCAN_CHAR_LIMIT // chars will not match anymore at the viewport borders. - const overscanLineLimit = Math.ceil(OVERSCAN_CHAR_LIMIT / this._terminal.cols); - const iterator = this._terminal.buffer.iterator( + const overscanLineLimit = Math.ceil(OVERSCAN_CHAR_LIMIT / this._bufferService.cols); + const iterator = this._bufferService.buffer.iterator( false, absoluteRowIndexStart, absoluteRowIndexEnd, overscanLineLimit, overscanLineLimit); while (iterator.hasNext()) { const lineData: IBufferStringIteratorResult = iterator.next(); @@ -228,13 +231,13 @@ export class Linkifier implements ILinkifier { } // get the buffer index as [absolute row, col] for the match - const bufferIndex = this._terminal.buffer.stringIndexToBufferIndex(rowIndex, stringIndex); + const bufferIndex = this._bufferService.buffer.stringIndexToBufferIndex(rowIndex, stringIndex); if (bufferIndex[0] < 0) { // invalid bufferIndex (should not have happened) break; } - const line = this._terminal.buffer.lines.get(bufferIndex[0]); + const line = this._bufferService.buffer.lines.get(bufferIndex[0]); const attr = line.getFg(bufferIndex[1]); let fg: number | undefined; if (attr) { @@ -248,11 +251,11 @@ export class Linkifier implements ILinkifier { return; } if (isValid) { - this._addLink(bufferIndex[1], bufferIndex[0] - this._terminal.buffer.ydisp, uri, matcher, fg); + this._addLink(bufferIndex[1], bufferIndex[0] - this._bufferService.buffer.ydisp, uri, matcher, fg); } }); } else { - this._addLink(bufferIndex[1], bufferIndex[0] - this._terminal.buffer.ydisp, uri, matcher, fg); + this._addLink(bufferIndex[1], bufferIndex[0] - this._bufferService.buffer.ydisp, uri, matcher, fg); } } } @@ -267,12 +270,12 @@ export class Linkifier implements ILinkifier { */ private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher, fg: number): void { const width = getStringCellWidth(uri); - const x1 = x % this._terminal.cols; - const y1 = y + Math.floor(x / this._terminal.cols); - let x2 = (x1 + width) % this._terminal.cols; - let y2 = y1 + Math.floor((x1 + width) / this._terminal.cols); + const x1 = x % this._bufferService.cols; + const y1 = y + Math.floor(x / this._bufferService.cols); + let x2 = (x1 + width) % this._bufferService.cols; + let y2 = y1 + Math.floor((x1 + width) / this._bufferService.cols); if (x2 === 0) { - x2 = this._terminal.cols; + x2 = this._bufferService.cols; y2--; } @@ -289,7 +292,7 @@ export class Linkifier implements ILinkifier { }, () => { this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - this._terminal.element.classList.add('xterm-cursor-pointer'); + this._element.classList.add('xterm-cursor-pointer'); }, e => { this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); @@ -299,7 +302,7 @@ export class Linkifier implements ILinkifier { }, () => { this._onLinkLeave.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - this._terminal.element.classList.remove('xterm-cursor-pointer'); + this._element.classList.remove('xterm-cursor-pointer'); if (matcher.hoverLeaveCallback) { matcher.hoverLeaveCallback(); } @@ -314,6 +317,6 @@ export class Linkifier implements ILinkifier { } private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkifierEvent { - return { x1, y1, x2, y2, cols: this._terminal.cols, fg }; + return { x1, y1, x2, y2, cols: this._bufferService.cols, fg }; } } diff --git a/src/Terminal.ts b/src/Terminal.ts index a97eeeed..c3b392a5 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -304,9 +304,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); - this._selectionService = this._selectionService || null; - this.linkifier = this.linkifier || new Linkifier(this, this._logService); - this._mouseZoneManager = this._mouseZoneManager || null; + this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService); if (this.options.windowsMode) { this._windowsMode = applyWindowsMode(this); @@ -603,7 +601,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._mouseZoneManager = new MouseZoneManager(this, this._mouseService); this.register(this._mouseZoneManager); this.register(this.onScroll(() => this._mouseZoneManager.clearAll())); - this.linkifier.attachToDom(this._mouseZoneManager); + this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this._renderService.dimensions, this._charSizeService); this.viewport.onThemeChange(this._colorManager.colors); diff --git a/src/Types.d.ts b/src/Types.d.ts index 1aa05b6c..1c56ef1b 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -290,7 +290,7 @@ export interface ILinkifier { onLinkLeave: IEvent; onLinkTooltip: IEvent; - attachToDom(mouseZoneManager: IMouseZoneManager): void; + attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void; linkifyRows(start: number, end: number): void; registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): boolean; From f0a02e318832626e611e8405658f3da8dcc9badf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 13 Jul 2019 13:53:16 -0700 Subject: [PATCH 3/6] Move linkifier types into browser --- src/Linkifier.test.ts | 2 +- src/Linkifier.ts | 18 ++++++- src/MouseZoneManager.ts | 18 +------ src/Terminal.ts | 3 +- src/TestUtils.test.ts | 4 +- src/Types.d.ts | 88 +------------------------------- src/browser/Types.d.ts | 89 +++++++++++++++++++++++++++++++++ src/renderer/LinkRenderLayer.ts | 4 +- src/renderer/dom/DomRenderer.ts | 4 +- 9 files changed, 117 insertions(+), 113 deletions(-) diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 973e6d9b..9849e03d 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { IMouseZoneManager, IMouseZone, ILinkMatcher } from './Types'; +import { IMouseZoneManager, IMouseZone, ILinkMatcher } from 'browser/Types'; import { IBufferLine } from 'common/Types'; import { Linkifier } from './Linkifier'; import { TestTerminal } from './TestUtils.test'; diff --git a/src/Linkifier.ts b/src/Linkifier.ts index ee585a48..0d31cf51 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -3,9 +3,8 @@ * @license MIT */ -import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager } from './Types'; +import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone } from 'browser/Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; -import { MouseZone } from './MouseZoneManager'; import { getStringCellWidth } from 'common/CharWidth'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ILogService, IBufferService } from 'common/services/Services'; @@ -320,3 +319,18 @@ export class Linkifier implements ILinkifier { return { x1, y1, x2, y2, cols: this._bufferService.cols, fg }; } } + +export class MouseZone implements IMouseZone { + constructor( + public x1: number, + public y1: number, + public x2: number, + public y2: number, + public clickCallback: (e: MouseEvent) => any, + public hoverCallback: (e: MouseEvent) => any, + public tooltipCallback: (e: MouseEvent) => any, + public leaveCallback: () => void, + public willLinkActivate: (e: MouseEvent) => boolean + ) { + } +} diff --git a/src/MouseZoneManager.ts b/src/MouseZoneManager.ts index de724b88..92ae9b1d 100644 --- a/src/MouseZoneManager.ts +++ b/src/MouseZoneManager.ts @@ -3,10 +3,11 @@ * @license MIT */ -import { ITerminal, IMouseZoneManager, IMouseZone } from './Types'; +import { ITerminal } from './Types'; import { Disposable } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IMouseService } from 'browser/services/Services'; +import { IMouseZoneManager, IMouseZone } from 'browser/Types'; const HOVER_DURATION = 500; @@ -230,18 +231,3 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { return null; } } - -export class MouseZone implements IMouseZone { - constructor( - public x1: number, - public y1: number, - public x2: number, - public y2: number, - public clickCallback: (e: MouseEvent) => any, - public hoverCallback: (e: MouseEvent) => any, - public tooltipCallback: (e: MouseEvent) => any, - public leaveCallback: () => void, - public willLinkActivate: (e: MouseEvent) => boolean - ) { - } -} diff --git a/src/Terminal.ts b/src/Terminal.ts index c3b392a5..aadc73a9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, IMouseZoneManager } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, CustomKeyEventHandler } from './Types'; import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from './Viewport'; @@ -59,6 +59,7 @@ import { MouseService } from 'browser/services/MouseService'; import { IParams } from 'common/parser/Types'; import { CoreService } from 'common/services/CoreService'; import { LogService } from 'common/services/LogService'; +import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions } from 'browser/Types'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index e9a76f7f..5cee2e28 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions, ILinkifier, ILinkMatcherOptions } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -12,7 +12,7 @@ import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IColorManager, IColorSet } from 'browser/Types'; +import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { EventEmitter } from 'common/EventEmitter'; import { IParams } from 'common/parser/Types'; diff --git a/src/Types.d.ts b/src/Types.d.ts index 1c56ef1b..1a647927 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -6,7 +6,7 @@ import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { ICharset, IAttributeData, CharData } from 'common/Types'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ILinkifier, ILinkMatcherOptions } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IParams } from 'common/parser/Types'; @@ -15,9 +15,6 @@ export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type LineData = CharData[]; -export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void; -export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; - /** * This interface encapsulates everything needed from the Terminal by the * InputHandler. This cleanly separates the large amount of methods needed by @@ -167,27 +164,6 @@ export interface IInputHandler { /** ESC # 8 */ screenAlignmentPattern(): void; } -export interface ILinkMatcher { - id: number; - regex: RegExp; - handler: LinkMatcherHandler; - hoverTooltipCallback?: LinkMatcherHandler; - hoverLeaveCallback?: () => void; - matchIndex?: number; - validationCallback?: LinkMatcherValidationCallback; - priority?: number; - willLinkActivate?: (event: MouseEvent, uri: string) => boolean; -} - -export interface ILinkifierEvent { - x1: number; - y1: number; - x2: number; - y2: number; - cols: number; - fg: number; -} - export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { screenElement: HTMLElement; browser: IBrowser; @@ -285,51 +261,6 @@ export interface ITerminalOptions extends IPublicTerminalOptions { useFlowControl?: boolean; } -export interface ILinkifier { - onLinkHover: IEvent; - onLinkLeave: IEvent; - onLinkTooltip: IEvent; - - attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void; - linkifyRows(start: number, end: number): void; - registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; - deregisterLinkMatcher(matcherId: number): boolean; -} - -export interface ILinkMatcherOptions { - /** - * The index of the link from the regex.match(text) call. This defaults to 0 - * (for regular expressions without capture groups). - */ - matchIndex?: number; - /** - * A callback that validates an individual link, returning true if valid and - * false if invalid. - */ - validationCallback?: LinkMatcherValidationCallback; - /** - * A callback that fires when the mouse hovers over a link. - */ - tooltipCallback?: LinkMatcherHandler; - /** - * A callback that fires when the mouse leaves a link that was hovered. - */ - leaveCallback?: () => void; - /** - * The priority of the link matcher, this defines the order in which the link - * matcher is evaluated relative to others, from highest to lowest. The - * default value is 0. - */ - priority?: number; - /** - * A callback that fires when the mousedown and click events occur that - * determines whether a link will be activated upon click. This enables - * only activating a link when a certain modifier is held down, if not the - * mouse event will continue propagation (eg. double click to select word). - */ - willLinkActivate?: (event: MouseEvent, uri: string) => boolean; -} - export interface IBrowser { isNode: boolean; userAgent: string; @@ -340,20 +271,3 @@ export interface IBrowser { isIphone: boolean; isWindows: boolean; } - -export interface IMouseZoneManager extends IDisposable { - add(zone: IMouseZone): void; - clearAll(start?: number, end?: number): void; -} - -export interface IMouseZone { - x1: number; - x2: number; - y1: number; - y2: number; - clickCallback: (e: MouseEvent) => any; - hoverCallback: (e: MouseEvent) => any | undefined; - tooltipCallback: (e: MouseEvent) => any | undefined; - leaveCallback: () => any | undefined; - willLinkActivate: (e: MouseEvent) => boolean; -} diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index ef725ba6..9add34fe 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -3,6 +3,9 @@ * @license MIT */ +import { IEvent } from 'common/EventEmitter'; +import { IDisposable } from 'common/Types'; + export interface IColorManager { colors: IColorSet; } @@ -20,3 +23,89 @@ export interface IColorSet { selection: IColor; ansi: IColor[]; } + +export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void; +export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; + +export interface ILinkMatcher { + id: number; + regex: RegExp; + handler: LinkMatcherHandler; + hoverTooltipCallback?: LinkMatcherHandler; + hoverLeaveCallback?: () => void; + matchIndex?: number; + validationCallback?: LinkMatcherValidationCallback; + priority?: number; + willLinkActivate?: (event: MouseEvent, uri: string) => boolean; +} + +export interface ILinkifierEvent { + x1: number; + y1: number; + x2: number; + y2: number; + cols: number; + fg: number; +} + +export interface ILinkifier { + onLinkHover: IEvent; + onLinkLeave: IEvent; + onLinkTooltip: IEvent; + + attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void; + linkifyRows(start: number, end: number): void; + registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; + deregisterLinkMatcher(matcherId: number): boolean; +} + +export interface ILinkMatcherOptions { + /** + * The index of the link from the regex.match(text) call. This defaults to 0 + * (for regular expressions without capture groups). + */ + matchIndex?: number; + /** + * A callback that validates an individual link, returning true if valid and + * false if invalid. + */ + validationCallback?: LinkMatcherValidationCallback; + /** + * A callback that fires when the mouse hovers over a link. + */ + tooltipCallback?: LinkMatcherHandler; + /** + * A callback that fires when the mouse leaves a link that was hovered. + */ + leaveCallback?: () => void; + /** + * The priority of the link matcher, this defines the order in which the link + * matcher is evaluated relative to others, from highest to lowest. The + * default value is 0. + */ + priority?: number; + /** + * A callback that fires when the mousedown and click events occur that + * determines whether a link will be activated upon click. This enables + * only activating a link when a certain modifier is held down, if not the + * mouse event will continue propagation (eg. double click to select word). + */ + willLinkActivate?: (event: MouseEvent, uri: string) => boolean; +} + +export interface IMouseZoneManager extends IDisposable { + add(zone: IMouseZone): void; + clearAll(start?: number, end?: number): void; +} + +export interface IMouseZone { + x1: number; + x2: number; + y1: number; + y2: number; + clickCallback: (e: MouseEvent) => any; + hoverCallback: (e: MouseEvent) => any | undefined; + tooltipCallback: (e: MouseEvent) => any | undefined; + leaveCallback: () => any | undefined; + willLinkActivate: (e: MouseEvent) => boolean; +} diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index f32b5ad9..6e6ba2e4 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { ILinkifierEvent, ITerminal, ILinkifierAccessor } from '../Types'; +import { ITerminal, ILinkifierAccessor } from '../Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from './atlas/CharAtlasUtils'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ILinkifierEvent } from 'browser/Types'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent = null; diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index a6b6e868..60bf50da 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -4,11 +4,11 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; -import { ILinkifierEvent, ITerminal } from '../../Types'; +import { ITerminal } from '../../Types'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { Disposable } from 'common/Lifecycle'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ILinkifierEvent } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IOptionsService } from 'common/services/Services'; From 0cd4093e53507191a84a580b91a1f16badbcdf1a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 13 Jul 2019 14:15:28 -0700 Subject: [PATCH 4/6] Move Linkifier to browser --- .../src/renderLayer/LinkRenderLayer.ts | 8 +- src/Terminal.ts | 4 +- src/{ => browser}/Linkifier.test.ts | 188 +++++++++--------- src/{ => browser}/Linkifier.ts | 50 +++-- src/browser/Types.d.ts | 6 +- src/browser/tsconfig.json | 2 +- 6 files changed, 136 insertions(+), 122 deletions(-) rename src/{ => browser}/Linkifier.test.ts (62%) rename src/{ => browser}/Linkifier.ts (89%) diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index a29d2cfd..118aedc3 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { ILinkifierEvent, ILinkifierAccessor } from '../../../../src/Types'; +import { ILinkifierAccessor } from '../../../../src/Types'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from '../atlas/CharAtlasUtils'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ILinkifierEvent } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; export class LinkRenderLayer extends BaseRenderLayer { @@ -45,9 +45,9 @@ export class LinkRenderLayer extends BaseRenderLayer { private _onLinkHover(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; - } else if (is256Color(e.fg)) { + } else if (e.fg !== undefined && is256Color(e.fg)) { // 256 color support - this._ctx.fillStyle = this._colors.ansi[e.fg].css; + this._ctx.fillStyle = this._colors.ansi[e.fg!].css; } else { this._ctx.fillStyle = this._colors.foreground.css; } diff --git a/src/Terminal.ts b/src/Terminal.ts index aadc73a9..6b0f4946 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -25,11 +25,11 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from './Viewport'; -import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './browser/Clipboard'; +import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from 'browser/Clipboard'; import { C0 } from 'common/data/EscapeSequences'; import { InputHandler } from './InputHandler'; import { Renderer } from './renderer/Renderer'; -import { Linkifier } from './Linkifier'; +import { Linkifier } from 'browser/Linkifier'; import { SelectionService } from './browser/services/SelectionService'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; diff --git a/src/Linkifier.test.ts b/src/browser/Linkifier.test.ts similarity index 62% rename from src/Linkifier.test.ts rename to src/browser/Linkifier.test.ts index 9849e03d..bf2cc984 100644 --- a/src/Linkifier.test.ts +++ b/src/browser/Linkifier.test.ts @@ -6,8 +6,8 @@ import { assert } from 'chai'; import { IMouseZoneManager, IMouseZone, ILinkMatcher } from 'browser/Types'; import { IBufferLine } from 'common/Types'; -import { Linkifier } from './Linkifier'; -import { TestTerminal } from './TestUtils.test'; +import { Linkifier } from 'browser/Linkifier'; +// import { TestTerminal } from '../TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { MockLogService, MockBufferService } from 'common/TestUtils.test'; @@ -105,7 +105,7 @@ describe('Linkifier', () => { describe('after attachToDom', () => { beforeEach(() => { - linkifier.attachToDom(null, mouseZoneManager); + linkifier.attachToDom(undefined as any, mouseZoneManager); }); describe('link matcher', () => { @@ -241,98 +241,98 @@ describe('Linkifier', () => { }); }); }); - describe('unicode handling', () => { - let terminal: TestTerminal; + // describe('unicode handling', () => { + // let terminal: TestTerminal; - // other than the tests above unicode testing needs the full terminal instance - // to get the special handling of fullwidth, surrogate and combining chars in the input handler - beforeEach(() => { - terminal = new TestTerminal({cols: 10, rows: 5}); - linkifier = new TestLinkifier(terminal); - mouseZoneManager = new TestMouseZoneManager(); - linkifier.attachToDom(null, mouseZoneManager); - }); + // // other than the tests above unicode testing needs the full terminal instance + // // to get the special handling of fullwidth, surrogate and combining chars in the input handler + // beforeEach(() => { + // terminal = new TestTerminal({cols: 10, rows: 5}); + // linkifier = new TestLinkifier(terminal); + // mouseZoneManager = new TestMouseZoneManager(); + // linkifier.attachToDom(undefined as any, mouseZoneManager); + // }); - function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void { - terminal.writeSync(rowText); - linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); - linkifier.linkifyRows(); - // Allow linkify to happen - setTimeout(() => { - assert.equal(mouseZoneManager.zones.length, links.length); - links.forEach((l, i) => { - assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1); - assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1); - assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1); - assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1); - }); - done(); - }, 0); - } + // function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void { + // terminal.writeSync(rowText); + // linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); + // linkifier.linkifyRows(); + // // Allow linkify to happen + // setTimeout(() => { + // assert.equal(mouseZoneManager.zones.length, links.length); + // links.forEach((l, i) => { + // assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1); + // assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1); + // assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1); + // assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1); + // }); + // done(); + // }, 0); + // } - describe('unicode before the match', () => { - it('combining - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); - }); - it('combining - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - }); - it('surrogate - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); - }); - it('surrogate - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - }); - it('combining surrogate - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); - }); - it('combining surrogate - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - }); - it('fullwidth - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); - }); - it('fullwidth - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - }); - it('combining fullwidth - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); - }); - it('combining fullwidth - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - }); - }); - describe('unicode within the match', () => { - it('combining - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); - }); - it('combining - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); - }); - it('surrogate - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); - }); - it('surrogate - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); - }); - it('combining surrogate - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); - }); - it('combining surrogate - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); - }); - it('fullwidth - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test a1b', /a1b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); - }); - it('fullwidth - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest a1b', /a1b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); - }); - it('combining fullwidth - match within one line', function(done: () => void): void { - assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); - }); - it('combining fullwidth - match over two lines', function(done: () => void): void { - assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); - }); - }); - }); + // describe('unicode before the match', () => { + // it('combining - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); + // }); + // it('combining - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + // }); + // it('surrogate - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); + // }); + // it('surrogate - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + // }); + // it('combining surrogate - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); + // }); + // it('combining surrogate - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + // }); + // it('fullwidth - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + // }); + // it('fullwidth - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + // }); + // it('combining fullwidth - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + // }); + // it('combining fullwidth - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + // }); + // }); + // describe('unicode within the match', () => { + // it('combining - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); + // }); + // it('combining - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); + // }); + // it('surrogate - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + // }); + // it('surrogate - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); + // }); + // it('combining surrogate - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + // }); + // it('combining surrogate - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); + // }); + // it('fullwidth - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('test a1b', /a1b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); + // }); + // it('fullwidth - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('testtest a1b', /a1b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); + // }); + // it('combining fullwidth - match within one line', function(done: () => void): void { + // assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); + // }); + // it('combining fullwidth - match over two lines', function(done: () => void): void { + // assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); + // }); + // }); + // }); }); diff --git a/src/Linkifier.ts b/src/browser/Linkifier.ts similarity index 89% rename from src/Linkifier.ts rename to src/browser/Linkifier.ts index 0d31cf51..a5454ac5 100644 --- a/src/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone } from 'browser/Types'; +import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; import { getStringCellWidth } from 'common/CharWidth'; import { EventEmitter, IEvent } from 'common/EventEmitter'; @@ -27,14 +27,14 @@ export class Linkifier implements ILinkifier { */ protected static _timeBeforeLatency = 200; - protected _linkMatchers: ILinkMatcher[] = []; + protected _linkMatchers: IRegisteredLinkMatcher[] = []; private _mouseZoneManager: IMouseZoneManager | undefined; private _element: HTMLElement | undefined; - private _rowsTimeoutId: number; + private _rowsTimeoutId: number | undefined; private _nextLinkMatcherId = 0; - private _rowsToLinkify: { start: number, end: number }; + private _rowsToLinkify: { start: number | undefined, end: number | undefined }; private _onLinkHover = new EventEmitter(); public get onLinkHover(): IEvent { return this._onLinkHover.event; } @@ -48,8 +48,8 @@ export class Linkifier implements ILinkifier { private readonly _logService: ILogService ) { this._rowsToLinkify = { - start: null, - end: null + start: undefined, + end: undefined }; } @@ -74,7 +74,7 @@ export class Linkifier implements ILinkifier { } // Increase range to linkify - if (this._rowsToLinkify.start === null) { + if (this._rowsToLinkify.start === undefined || this._rowsToLinkify.end === undefined) { this._rowsToLinkify.start = start; this._rowsToLinkify.end = end; } else { @@ -96,9 +96,14 @@ export class Linkifier implements ILinkifier { * Linkifies the rows requested. */ private _linkifyRows(): void { - this._rowsTimeoutId = null; + this._rowsTimeoutId = undefined; const buffer = this._bufferService.buffer; + if (this._rowsToLinkify.start === undefined || this._rowsToLinkify.end === undefined) { + this._logService.debug('_rowToLinkify was unset before _linkifyRows was called'); + return; + } + // Ensure the start row exists const absoluteRowIndexStart = buffer.ydisp + this._rowsToLinkify.start; if (absoluteRowIndexStart >= buffer.lines.length) { @@ -128,8 +133,8 @@ export class Linkifier implements ILinkifier { } } - this._rowsToLinkify.start = null; - this._rowsToLinkify.end = null; + this._rowsToLinkify.start = undefined; + this._rowsToLinkify.end = undefined; } /** @@ -146,7 +151,7 @@ export class Linkifier implements ILinkifier { if (!handler) { throw new Error('handler must be defined'); } - const matcher: ILinkMatcher = { + const matcher: IRegisteredLinkMatcher = { id: this._nextLinkMatcherId++, regex, handler, @@ -167,7 +172,7 @@ export class Linkifier implements ILinkifier { * considered after older link matchers. * @param matcher The link matcher to be added. */ - private _addLinkMatcherToList(matcher: ILinkMatcher): void { + private _addLinkMatcherToList(matcher: IRegisteredLinkMatcher): void { if (this._linkMatchers.length === 0) { this._linkMatchers.push(matcher); return; @@ -237,12 +242,13 @@ export class Linkifier implements ILinkifier { } const line = this._bufferService.buffer.lines.get(bufferIndex[0]); - const attr = line.getFg(bufferIndex[1]); - let fg: number | undefined; - if (attr) { - fg = (attr >> 9) & 0x1ff; + if (!line) { + break; } + const attr = line.getFg(bufferIndex[1]); + const fg = attr ? (attr >> 9) & 0x1ff : undefined; + if (matcher.validationCallback) { matcher.validationCallback(uri, isValid => { // Discard link if the line has already changed @@ -267,7 +273,11 @@ export class Linkifier implements ILinkifier { * @param matcher The link matcher for the link. * @param fg The link color for hover event. */ - private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher, fg: number): void { + private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher, fg: number | undefined): void { + if (!this._mouseZoneManager || !this._element) { + return; + } + const width = getStringCellWidth(uri); const x1 = x % this._bufferService.cols; const y1 = y + Math.floor(x / this._bufferService.cols); @@ -291,7 +301,7 @@ export class Linkifier implements ILinkifier { }, () => { this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - this._element.classList.add('xterm-cursor-pointer'); + this._element!.classList.add('xterm-cursor-pointer'); }, e => { this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); @@ -301,7 +311,7 @@ export class Linkifier implements ILinkifier { }, () => { this._onLinkLeave.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - this._element.classList.remove('xterm-cursor-pointer'); + this._element!.classList.remove('xterm-cursor-pointer'); if (matcher.hoverLeaveCallback) { matcher.hoverLeaveCallback(); } @@ -315,7 +325,7 @@ export class Linkifier implements ILinkifier { )); } - private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkifierEvent { + private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent { return { x1, y1, x2, y2, cols: this._bufferService.cols, fg }; } } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 9add34fe..985244b3 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -39,13 +39,17 @@ export interface ILinkMatcher { willLinkActivate?: (event: MouseEvent, uri: string) => boolean; } +export interface IRegisteredLinkMatcher extends ILinkMatcher { + priority: number; +} + export interface ILinkifierEvent { x1: number; y1: number; x2: number; y2: number; cols: number; - fg: number; + fg: number | undefined; } export interface ILinkifier { diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index 06818413..7465bbd3 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "dom", - "es5", + "es2015", ], "outDir": "../../out", "types": [ From 2eec18e7f5e1d386c4defc833e89814e81903f2e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 13 Jul 2019 14:35:31 -0700 Subject: [PATCH 5/6] Remove ITerminal from MouseZoneManager --- src/MouseZoneManager.ts | 38 +++++++++++++++++++++-------------- src/Terminal.ts | 10 ++++----- src/browser/Linkifier.test.ts | 2 +- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/MouseZoneManager.ts b/src/MouseZoneManager.ts index 92ae9b1d..42eb6545 100644 --- a/src/MouseZoneManager.ts +++ b/src/MouseZoneManager.ts @@ -3,11 +3,11 @@ * @license MIT */ -import { ITerminal } from './Types'; import { Disposable } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IMouseService } from 'browser/services/Services'; +import { IMouseService, ISelectionService } from 'browser/services/Services'; import { IMouseZoneManager, IMouseZone } from 'browser/Types'; +import { IBufferService } from 'common/services/Services'; const HOVER_DURATION = 500; @@ -33,12 +33,15 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { private _initialSelectionLength: number; constructor( - private _terminal: ITerminal, - private _mouseService: IMouseService + private readonly _element: HTMLElement, + private readonly _screenElement: HTMLElement, + private readonly _bufferService: IBufferService, + private readonly _mouseService: IMouseService, + private readonly _selectionService: ISelectionService ) { super(); - this.register(addDisposableDomListener(this._terminal.element, 'mousedown', e => this._onMouseDown(e))); + this.register(addDisposableDomListener(this._element, 'mousedown', e => this._onMouseDown(e))); // These events are expensive, only listen to it when mouse zones are active this._mouseMoveListener = e => this._onMouseMove(e); @@ -67,7 +70,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { // Clear all if start/end weren't set if (!end) { start = 0; - end = this._terminal.rows - 1; + end = this._bufferService.rows - 1; } // Iterate through zones and clear them out if they're within the range @@ -93,18 +96,18 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { private _activate(): void { if (!this._areZonesActive) { this._areZonesActive = true; - this._terminal.element.addEventListener('mousemove', this._mouseMoveListener); - this._terminal.element.addEventListener('mouseleave', this._mouseLeaveListener); - this._terminal.element.addEventListener('click', this._clickListener); + this._element.addEventListener('mousemove', this._mouseMoveListener); + this._element.addEventListener('mouseleave', this._mouseLeaveListener); + this._element.addEventListener('click', this._clickListener); } } private _deactivate(): void { if (this._areZonesActive) { this._areZonesActive = false; - this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener); - this._terminal.element.removeEventListener('mouseleave', this._mouseLeaveListener); - this._terminal.element.removeEventListener('click', this._clickListener); + this._element.removeEventListener('mousemove', this._mouseMoveListener); + this._element.removeEventListener('mouseleave', this._mouseLeaveListener); + this._element.removeEventListener('click', this._clickListener); } } @@ -162,7 +165,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { private _onMouseDown(e: MouseEvent): void { // Store current terminal selection length, to check if we're performing // a selection operation - this._initialSelectionLength = this._terminal.getSelection().length; + this._initialSelectionLength = this._getSelectionLength(); // Ignore the event if there are no zones active if (!this._areZonesActive) { @@ -196,7 +199,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { // Find the active zone and click it if found and no selection was // being performed const zone = this._findZoneEventAt(e); - const currentSelectionLength = this._terminal.getSelection().length; + const currentSelectionLength = this._getSelectionLength(); if (zone && currentSelectionLength === this._initialSelectionLength) { zone.clickCallback(e); @@ -205,8 +208,13 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { } } + private _getSelectionLength(): number { + const selectionText = this._selectionService.selectionText; + return selectionText ? selectionText.length : 0; + } + private _findZoneEventAt(e: MouseEvent): IMouseZone { - const coords = this._mouseService.getCoords(e, this._terminal.screenElement, this._terminal.cols, this._terminal.rows); + const coords = this._mouseService.getCoords(e, this._screenElement, this._bufferService.cols, this._bufferService.rows); if (!coords) { return null; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 6b0f4946..d9a6d77b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -599,11 +599,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._soundService = new SoundService(this.optionsService); this._mouseService = new MouseService(this._renderService, this._charSizeService); - this._mouseZoneManager = new MouseZoneManager(this, this._mouseService); - this.register(this._mouseZoneManager); - this.register(this.onScroll(() => this._mouseZoneManager.clearAll())); - this.linkifier.attachToDom(this.element, this._mouseZoneManager); - this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this._renderService.dimensions, this._charSizeService); this.viewport.onThemeChange(this._colorManager.colors); this.register(this.viewport); @@ -636,6 +631,11 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp })); this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService.refresh())); + this._mouseZoneManager = new MouseZoneManager(this.element, this.screenElement, this._bufferService, this._mouseService, this._selectionService); + this.register(this._mouseZoneManager); + this.register(this.onScroll(() => this._mouseZoneManager.clearAll())); + this.linkifier.attachToDom(this.element, this._mouseZoneManager); + // apply mouse event classes set by escape codes before terminal was attached this.element.classList.toggle('enable-mouse-events', this.mouseEvents); if (this.mouseEvents) { diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts index bf2cc984..f15eebcc 100644 --- a/src/browser/Linkifier.test.ts +++ b/src/browser/Linkifier.test.ts @@ -105,7 +105,7 @@ describe('Linkifier', () => { describe('after attachToDom', () => { beforeEach(() => { - linkifier.attachToDom(undefined as any, mouseZoneManager); + linkifier.attachToDom({} as any, mouseZoneManager); }); describe('link matcher', () => { From 78f015308af1c644589a2459d5f9051c1df878c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 13 Jul 2019 17:28:09 -0700 Subject: [PATCH 6/6] Re-enable linkifier unicode tests in Terminal.test.ts --- src/Terminal.test.ts | 132 ++++++++++++++++++++++++++++++++-- src/TestUtils.test.ts | 2 + src/browser/Linkifier.test.ts | 99 +------------------------ 3 files changed, 129 insertions(+), 104 deletions(-) diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index dad8d46a..b83692e7 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -4,20 +4,18 @@ */ import { assert, expect } from 'chai'; -import { Terminal } from './Terminal'; -import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test'; +import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { wcwidth } from 'common/CharWidth'; +import { IBufferService } from 'common/services/Services'; +import { Linkifier } from 'browser/Linkifier'; +import { MockLogService } from 'common/TestUtils.test'; +import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types'; const INIT_COLS = 80; const INIT_ROWS = 24; -class TestTerminal extends Terminal { - public keyDown(ev: any): boolean { return this._keyDown(ev); } - public keyPress(ev: any): boolean { return this._keyPress(ev); } -} - describe('Terminal', () => { let term: TestTerminal; const termOptions = { @@ -1024,4 +1022,124 @@ describe('Terminal', () => { expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth }); }); + + describe('Linkifier unicode handling', () => { + let terminal: TestTerminal; + let linkifier: TestLinkifier; + let mouseZoneManager: TestMouseZoneManager; + + // other than the tests above unicode testing needs the full terminal instance + // to get the special handling of fullwidth, surrogate and combining chars in the input handler + beforeEach(() => { + terminal = new TestTerminal({ cols: 10, rows: 5 }); + linkifier = new TestLinkifier((terminal as any)._bufferService); + mouseZoneManager = new TestMouseZoneManager(); + linkifier.attachToDom({} as any, mouseZoneManager); + }); + + function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void { + terminal.writeSync(rowText); + linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); + linkifier.linkifyRows(); + // Allow linkify to happen + setTimeout(() => { + assert.equal(mouseZoneManager.zones.length, links.length); + links.forEach((l, i) => { + assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1); + assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1); + assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1); + assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1); + }); + done(); + }, 0); + } + + describe('unicode before the match', () => { + it('combining - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); + }); + it('combining - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + }); + it('surrogate - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); + }); + it('surrogate - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + }); + it('combining surrogate - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); + }); + it('combining surrogate - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + }); + it('fullwidth - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + }); + it('fullwidth - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + }); + it('combining fullwidth - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + }); + it('combining fullwidth - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); + }); + }); + describe('unicode within the match', () => { + it('combining - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); + }); + it('combining - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); + }); + it('surrogate - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + }); + it('surrogate - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); + }); + it('combining surrogate - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); + }); + it('combining surrogate - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); + }); + it('fullwidth - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('test a1b', /a1b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); + }); + it('fullwidth - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('testtest a1b', /a1b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); + }); + it('combining fullwidth - match within one line', function(done: () => void): void { + assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); + }); + it('combining fullwidth - match over two lines', function(done: () => void): void { + assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); + }); + }); + }); }); + +class TestLinkifier extends Linkifier { + constructor(bufferService: IBufferService) { + super(bufferService, new MockLogService()); + Linkifier._timeBeforeLatency = 0; + } + + public get linkMatchers(): IRegisteredLinkMatcher[] { return this._linkMatchers; } + public linkifyRows(): void { super.linkifyRows(0, this._bufferService.buffer.lines.length - 1); } +} + +class TestMouseZoneManager implements IMouseZoneManager { + dispose(): void { + } + public clears: number = 0; + public zones: IMouseZone[] = []; + add(zone: IMouseZone): void { + this.zones.push(zone); + } + clearAll(): void { + this.clears++; + } +} diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 5cee2e28..195321a2 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -23,6 +23,8 @@ export class TestTerminal extends Terminal { this.writeBuffer.push(data); this._innerWrite(); } + keyDown(ev: any): boolean { return this._keyDown(ev); } + keyPress(ev: any): boolean { return this._keyPress(ev); } } export class MockTerminal implements ITerminal { diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts index f15eebcc..128f786d 100644 --- a/src/browser/Linkifier.test.ts +++ b/src/browser/Linkifier.test.ts @@ -4,10 +4,9 @@ */ import { assert } from 'chai'; -import { IMouseZoneManager, IMouseZone, ILinkMatcher } from 'browser/Types'; +import { IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; import { IBufferLine } from 'common/Types'; import { Linkifier } from 'browser/Linkifier'; -// import { TestTerminal } from '../TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { MockLogService, MockBufferService } from 'common/TestUtils.test'; @@ -19,7 +18,7 @@ class TestLinkifier extends Linkifier { Linkifier._timeBeforeLatency = 0; } - public get linkMatchers(): ILinkMatcher[] { return this._linkMatchers; } + public get linkMatchers(): IRegisteredLinkMatcher[] { return this._linkMatchers; } public linkifyRows(): void { super.linkifyRows(0, this._bufferService.buffer.lines.length - 1); } } @@ -241,98 +240,4 @@ describe('Linkifier', () => { }); }); }); - // describe('unicode handling', () => { - // let terminal: TestTerminal; - - // // other than the tests above unicode testing needs the full terminal instance - // // to get the special handling of fullwidth, surrogate and combining chars in the input handler - // beforeEach(() => { - // terminal = new TestTerminal({cols: 10, rows: 5}); - // linkifier = new TestLinkifier(terminal); - // mouseZoneManager = new TestMouseZoneManager(); - // linkifier.attachToDom(undefined as any, mouseZoneManager); - // }); - - // function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void { - // terminal.writeSync(rowText); - // linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); - // linkifier.linkifyRows(); - // // Allow linkify to happen - // setTimeout(() => { - // assert.equal(mouseZoneManager.zones.length, links.length); - // links.forEach((l, i) => { - // assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1); - // assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1); - // assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1); - // assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1); - // }); - // done(); - // }, 0); - // } - - // describe('unicode before the match', () => { - // it('combining - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); - // }); - // it('combining - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - // }); - // it('surrogate - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); - // }); - // it('surrogate - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - // }); - // it('combining surrogate - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done); - // }); - // it('combining surrogate - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - // }); - // it('fullwidth - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); - // }); - // it('fullwidth - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - // }); - // it('combining fullwidth - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); - // }); - // it('combining fullwidth - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done); - // }); - // }); - // describe('unicode within the match', () => { - // it('combining - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); - // }); - // it('combining - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); - // }); - // it('surrogate - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); - // }); - // it('surrogate - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); - // }); - // it('combining surrogate - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done); - // }); - // it('combining surrogate - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done); - // }); - // it('fullwidth - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('test a1b', /a1b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); - // }); - // it('fullwidth - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('testtest a1b', /a1b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); - // }); - // it('combining fullwidth - match within one line', function(done: () => void): void { - // assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done); - // }); - // it('combining fullwidth - match over two lines', function(done: () => void): void { - // assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done); - // }); - // }); - // }); });