diff --git a/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts b/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts index 1aa213574c..2956b9d906 100644 --- a/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts +++ b/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon } from 'xterm'; declare module 'xterm-addon-attach' { export interface IAttachOptions { diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.ts index 285ef5dc51..1e3c877d1d 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable } from 'xterm'; +import { Terminal, ITerminalAddon, IDisposable } from 'xterm'; import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider'; const protocolClause = '(https?:\\/\\/)'; @@ -41,37 +41,23 @@ function handleLink(event: MouseEvent, uri: string): void { } export class WebLinksAddon implements ITerminalAddon { - private _linkMatcherId: number | undefined; private _terminal: Terminal | undefined; private _linkProvider: IDisposable | undefined; constructor( private _handler: (event: MouseEvent, uri: string) => void = handleLink, - private _options: ILinkMatcherOptions | ILinkProviderOptions = {}, - private _useLinkProvider: boolean = false + private _options: ILinkProviderOptions = {} ) { } public activate(terminal: Terminal): void { this._terminal = terminal; - - if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) { - const options = this._options as ILinkProviderOptions; - const regex = options.urlRegex || strictUrlRegex; - this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options)); - } else { - // TODO: This should be removed eventually - const options = this._options as ILinkMatcherOptions; - options.matchIndex = 1; - this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, options); - } + const options = this._options as ILinkProviderOptions; + const regex = options.urlRegex || strictUrlRegex; + this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options)); } public dispose(): void { - if (this._linkMatcherId !== undefined && this._terminal !== undefined) { - this._terminal.deregisterLinkMatcher(this._linkMatcherId); - } - this._linkProvider?.dispose(); } } diff --git a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts index 5e6c266d40..4e1767b936 100644 --- a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts +++ b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts @@ -4,7 +4,7 @@ */ -import { Terminal, ILinkMatcherOptions, ITerminalAddon, IViewportRange } from 'xterm'; +import { Terminal, ITerminalAddon, IViewportRange } from 'xterm'; declare module 'xterm-addon-web-links' { /** @@ -14,13 +14,9 @@ declare module 'xterm-addon-web-links' { /** * Creates a new web links addon. * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @param useLinkProvider Whether to use the new link provider API to create - * the links. This is an option because use of both link matcher (old) and - * link provider (new) may cause issues. Link provider will eventually be - * the default and only option. + * @param options Options for the link provider. */ - constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions | ILinkProviderOptions, useLinkProvider?: boolean); + constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkProviderOptions); /** * Activates the addon @@ -50,7 +46,7 @@ declare module 'xterm-addon-web-links' { */ leave?(event: MouseEvent, text: string): void; - /** + /** * A callback to use instead of the default one. */ urlRegex?: RegExp; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 8f6b3e6dab..b2d004f917 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -15,8 +15,6 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ITerminal) { super(container, 'link', zIndex, true, colors); - terminal.linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); - terminal.linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts deleted file mode 100644 index a07f69ed23..0000000000 --- a/src/browser/Linkifier.test.ts +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; -import { IBufferLine } from 'common/Types'; -import { Linkifier } from 'browser/Linkifier'; -import { BufferLine } from 'common/buffer/BufferLine'; -import { CellData } from 'common/buffer/CellData'; -import { MockLogService, MockBufferService } from 'common/TestUtils.test'; -import { IBufferService } from 'common/services/Services'; -import { UnicodeService } from 'common/services/UnicodeService'; - -class TestLinkifier extends Linkifier { - constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService(), new UnicodeService()); - 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 { - public dispose(): void { - } - public clears: number = 0; - public zones: IMouseZone[] = []; - public add(zone: IMouseZone): void { - this.zones.push(zone); - } - public clearAll(): void { - this.clears++; - } -} - -describe('Linkifier', () => { - let bufferService: IBufferService; - let linkifier: TestLinkifier; - let mouseZoneManager: TestMouseZoneManager; - - beforeEach(() => { - bufferService = new MockBufferService(100, 10); - linkifier = new TestLinkifier(bufferService); - mouseZoneManager = new TestMouseZoneManager(); - }); - - function stringToRow(text: string): IBufferLine { - const result = new BufferLine(text.length); - for (let i = 0; i < text.length; i++) { - result.setCell(i, CellData.fromCharData([0, text.charAt(i), 1, text.charCodeAt(i)])); - } - return result; - } - - function addRow(text: string): void { - bufferService.buffer.lines.push(stringToRow(text)); - } - - function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: Mocha.Done): void { - addRow(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.x + 1); - assert.equal(mouseZoneManager.zones[i].x2, l.x + l.length + 1); - assert.equal(mouseZoneManager.zones[i].y1, bufferService.buffer.lines.length); - assert.equal(mouseZoneManager.zones[i].y2, bufferService.buffer.lines.length); - }); - done(); - }, 0); - } - - function assertLinkifiesMultiLineLink(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: Mocha.Done): void { - addRow(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('before attachToDom', () => { - it('should allow link matcher registration', done => { - assert.doesNotThrow(() => { - const linkMatcherId = linkifier.registerLinkMatcher(/foo/, () => {}); - assert.isTrue(linkifier.deregisterLinkMatcher(linkMatcherId)); - done(); - }); - }); - }); - - describe('after attachToDom', () => { - beforeEach(() => { - linkifier.attachToDom({} as any, mouseZoneManager); - }); - - describe('link matcher', () => { - it('should match a single link', done => { - assertLinkifiesRow('foo', /foo/, [{x: 0, length: 3}], done); - }); - it('should match a single link at the start of a text node', done => { - assertLinkifiesRow('foo bar', /foo/, [{x: 0, length: 3}], done); - }); - it('should match a single link in the middle of a text node', done => { - assertLinkifiesRow('foo bar baz', /bar/, [{x: 4, length: 3}], done); - }); - it('should match a single link at the end of a text node', done => { - assertLinkifiesRow('foo bar', /bar/, [{x: 4, length: 3}], done); - }); - it('should match a link after a link at the start of a text node', done => { - assertLinkifiesRow('foo bar', /foo|bar/, [{x: 0, length: 3}, {x: 4, length: 3}], done); - }); - it('should match a link after a link in the middle of a text node', done => { - assertLinkifiesRow('foo bar baz', /bar|baz/, [{x: 4, length: 3}, {x: 8, length: 3}], done); - }); - it('should match a link immediately after a link at the end of a text node', done => { - assertLinkifiesRow('foo barbaz', /bar|baz/, [{x: 4, length: 3}, {x: 7, length: 3}], done); - }); - it('should not duplicate text after a unicode character (wrapped in a span)', done => { - // This is a regression test for an issue that came about when using - // an oh-my-zsh theme that added the large blue diamond unicode - // character (U+1F537) which caused the path to be duplicated. See #642. - assertLinkifiesRow('echo \'🔷foo\'', /foo/, [{x: 8, length: 3}], done); - }); - 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 => { - 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 => { - 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 => { - 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 => { - bufferService.resize(4, bufferService.rows); - bufferService.buffer.lines.length = 0; - assertLinkifiesMultiLineLink('123456789', /56789/, [{x1: 0, x2: 1, y1: 1, y2: 2}], done); - }); - }); - }); - - describe('validationCallback', () => { - it('should enable link if true', done => { - bufferService.buffer.lines.length = 0; - addRow('test'); - linkifier.registerLinkMatcher(/test/, () => done(), { - validationCallback: (url, cb) => { - assert.equal(mouseZoneManager.zones.length, 0); - cb(true); - assert.equal(mouseZoneManager.zones.length, 1); - assert.equal(mouseZoneManager.zones[0].x1, 1); - assert.equal(mouseZoneManager.zones[0].x2, 5); - assert.equal(mouseZoneManager.zones[0].y1, 1); - assert.equal(mouseZoneManager.zones[0].y2, 1); - // Fires done() - mouseZoneManager.zones[0].clickCallback({} as any); - } - }); - linkifier.linkifyRows(); - }); - - it('should validate the uri, not the row', done => { - addRow('abc test abc'); - linkifier.registerLinkMatcher(/test/, () => done(), { - validationCallback: (uri, cb) => { - assert.equal(uri, 'test'); - done(); - } - }); - linkifier.linkifyRows(); - }); - - it('should disable link if false', done => { - addRow('test'); - linkifier.registerLinkMatcher(/test/, () => assert.fail(), { - validationCallback: (url, cb) => { - assert.equal(mouseZoneManager.zones.length, 0); - cb(false); - assert.equal(mouseZoneManager.zones.length, 0); - } - }); - linkifier.linkifyRows(); - // Allow time for the validation callback to be performed - setTimeout(() => done(), 10); - }); - - it('should trigger for multiple link matches on one row', done => { - addRow('test test'); - let count = 0; - linkifier.registerLinkMatcher(/test/, () => assert.fail(), { - validationCallback: (url, cb) => { - count++; - if (count === 2) { - done(); - } - cb(false); - } - }); - linkifier.linkifyRows(); - }); - }); - - describe('priority', () => { - it('should order the list from highest priority to lowest #1', () => { - const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 1 }); - const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: -1 }); - assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, bId]); - }); - - it('should order the list from highest priority to lowest #2', () => { - const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: -1 }); - const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 1 }); - assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [bId, aId]); - }); - - it('should order items of equal priority in the order they are added', () => { - const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 0 }); - const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 0 }); - assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, bId]); - }); - }); - }); -}); diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts deleted file mode 100644 index b17d66a86e..0000000000 --- a/src/browser/Linkifier.ts +++ /dev/null @@ -1,356 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; -import { IBufferStringIteratorResult } from 'common/buffer/Types'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ILogService, IBufferService, IOptionsService, IUnicodeService } from 'common/services/Services'; - -/** - * Limit of the unwrapping line expansion (overscan) at the top and bottom - * of the actual viewport in ASCII characters. - * A limit of 2000 should match most sane urls. - */ -const OVERSCAN_CHAR_LIMIT = 2000; - -/** - * The Linkifier applies links to rows shortly after they have been refreshed. - */ -export class Linkifier implements ILinkifier { - /** - * The time to wait after a row is changed before it is linkified. This prevents - * the costly operation of searching every row multiple times, potentially a - * huge amount of times. - */ - protected static _timeBeforeLatency = 200; - - protected _linkMatchers: IRegisteredLinkMatcher[] = []; - - private _mouseZoneManager: IMouseZoneManager | undefined; - private _element: HTMLElement | undefined; - - private _rowsTimeoutId: number | undefined; - private _nextLinkMatcherId = 0; - private _rowsToLinkify: { start: number | undefined, end: number | undefined }; - - 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; } - - constructor( - @IBufferService protected readonly _bufferService: IBufferService, - @ILogService private readonly _logService: ILogService, - @IUnicodeService private readonly _unicodeService: IUnicodeService - ) { - this._rowsToLinkify = { - start: undefined, - end: undefined - }; - } - - /** - * Attaches the linkifier to the DOM, enabling linkification. - * @param mouseZoneManager The mouse zone manager to register link zones with. - */ - public attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void { - this._element = element; - this._mouseZoneManager = mouseZoneManager; - } - - /** - * Queue linkification on a set of rows. - * @param start The row to linkify from (inclusive). - * @param end The row to linkify to (inclusive). - */ - public linkifyRows(start: number, end: number): void { - // Don't attempt linkify if not yet attached to DOM - if (!this._mouseZoneManager) { - return; - } - - // Increase range to linkify - if (this._rowsToLinkify.start === undefined || this._rowsToLinkify.end === undefined) { - this._rowsToLinkify.start = start; - this._rowsToLinkify.end = end; - } else { - this._rowsToLinkify.start = Math.min(this._rowsToLinkify.start, start); - this._rowsToLinkify.end = Math.max(this._rowsToLinkify.end, end); - } - - // Clear out any existing links on this row range - this._mouseZoneManager.clearAll(start, end); - - // Restart timer - if (this._rowsTimeoutId) { - clearTimeout(this._rowsTimeoutId); - } - - // Cannot use window.setTimeout since tests need to run in node - this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency) as any as number; - } - - /** - * Linkifies the rows requested. - */ - private _linkifyRows(): void { - 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) { - return; - } - - // Invalidate bad end row values (if a resize happened) - 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). - // _doLinkifyRow gets full unwrapped lines with the start row as buffer offset - // for every matcher. - // The unwrapping is needed to also match content that got wrapped across - // several buffer lines. To avoid a worst case scenario where the whole buffer - // contains just a single unwrapped string we limit this line expansion beyond - // 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._bufferService.cols); - const iterator = this._bufferService.buffer.iterator( - false, absoluteRowIndexStart, absoluteRowIndexEnd, overscanLineLimit, overscanLineLimit); - while (iterator.hasNext()) { - const lineData: IBufferStringIteratorResult = iterator.next(); - for (let i = 0; i < this._linkMatchers.length; i++) { - this._doLinkifyRow(lineData.range.first, lineData.content, this._linkMatchers[i]); - } - } - - this._rowsToLinkify.start = undefined; - this._rowsToLinkify.end = undefined; - } - - /** - * Registers a link matcher, allowing custom link patterns to be matched and - * handled. - * @param regex The regular expression to search for. Specifically, this - * searches the textContent of the rows. You will want to use \s to match a - * space ' ' character for example. - * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @return The ID of the new matcher, this can be used to deregister. - */ - public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number { - if (!handler) { - throw new Error('handler must be defined'); - } - const matcher: IRegisteredLinkMatcher = { - id: this._nextLinkMatcherId++, - regex, - handler, - matchIndex: options.matchIndex, - validationCallback: options.validationCallback, - hoverTooltipCallback: options.tooltipCallback, - hoverLeaveCallback: options.leaveCallback, - willLinkActivate: options.willLinkActivate, - priority: options.priority || 0 - }; - this._addLinkMatcherToList(matcher); - return matcher.id; - } - - /** - * Inserts a link matcher to the list in the correct position based on the - * priority of each link matcher. New link matchers of equal priority are - * considered after older link matchers. - * @param matcher The link matcher to be added. - */ - private _addLinkMatcherToList(matcher: IRegisteredLinkMatcher): void { - if (this._linkMatchers.length === 0) { - this._linkMatchers.push(matcher); - return; - } - - for (let i = this._linkMatchers.length - 1; i >= 0; i--) { - if (matcher.priority <= this._linkMatchers[i].priority) { - this._linkMatchers.splice(i + 1, 0, matcher); - return; - } - } - - this._linkMatchers.splice(0, 0, matcher); - } - - /** - * Deregisters a link matcher if it has been registered. - * @param matcherId The link matcher's ID (returned after register) - * @return Whether a link matcher was found and deregistered. - */ - public deregisterLinkMatcher(matcherId: number): boolean { - for (let i = 0; i < this._linkMatchers.length; i++) { - if (this._linkMatchers[i].id === matcherId) { - this._linkMatchers.splice(i, 1); - return true; - } - } - return false; - } - - /** - * Linkifies a row given a specific handler. - * @param rowIndex The row index to linkify (absolute index). - * @param text string content of the unwrapped row. - * @param matcher The link matcher for this line. - */ - private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher): void { - // clone regex to do a global search on text - const rex = new RegExp(matcher.regex.source, (matcher.regex.flags || '') + 'g'); - let match; - let stringIndex = -1; - while ((match = rex.exec(text)) !== null) { - const uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; - if (!uri) { - // something matched but does not comply with the given matchIndex - // since this is most likely a bug the regex itself we simply do nothing here - this._logService.debug('match found without corresponding matchIndex', match, matcher); - break; - } - - // Get index, match.index is for the outer match which includes negated chars - // therefore we cannot use match.index directly, instead we search the position - // of the match group in text again - // also correct regex and string search offsets for the next loop run - stringIndex = text.indexOf(uri, stringIndex + 1); - rex.lastIndex = stringIndex + uri.length; - if (stringIndex < 0) { - // invalid stringIndex (should not have happened) - break; - } - - // get the buffer index as [absolute row, col] for the match - const bufferIndex = this._bufferService.buffer.stringIndexToBufferIndex(rowIndex, stringIndex); - if (bufferIndex[0] < 0) { - // invalid bufferIndex (should not have happened) - break; - } - - const line = this._bufferService.buffer.lines.get(bufferIndex[0]); - 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 - if (this._rowsTimeoutId) { - return; - } - if (isValid) { - this._addLink(bufferIndex[1], bufferIndex[0] - this._bufferService.buffer.ydisp, uri, matcher, fg); - } - }); - } else { - this._addLink(bufferIndex[1], bufferIndex[0] - this._bufferService.buffer.ydisp, uri, matcher, fg); - } - } - } - - /** - * Registers a link to the mouse zone manager. - * @param x The column the link starts. - * @param y The row the link is on. - * @param uri The URI of the link. - * @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 | undefined): void { - if (!this._mouseZoneManager || !this._element) { - return; - } - // FIXME: get cell length from buffer to avoid mismatch after Unicode version change - const width = this._unicodeService.getStringCellWidth(uri); - 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._bufferService.cols; - y2--; - } - - this._mouseZoneManager.add(new MouseZone( - x1 + 1, - y1 + 1, - x2 + 1, - y2 + 1, - e => { - if (matcher.handler) { - return matcher.handler(e, uri); - } - const newWindow = window.open(); - if (newWindow) { - newWindow.opener = null; - newWindow.location.href = uri; - } else { - console.warn('Opening link blocked as opener could not be cleared'); - } - }, - () => { - this._onShowLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - this._element!.classList.add('xterm-cursor-pointer'); - }, - e => { - this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - if (matcher.hoverTooltipCallback) { - // Note that IViewportRange use 1-based coordinates to align with escape sequences such - // as CUP which use 1,1 as the default for row/col - matcher.hoverTooltipCallback(e, uri, { start: { x: x1, y: y1 }, end: { x: x2, y: y2 } }); - } - }, - () => { - this._onHideLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - this._element!.classList.remove('xterm-cursor-pointer'); - if (matcher.hoverLeaveCallback) { - matcher.hoverLeaveCallback(); - } - }, - e => { - if (matcher.willLinkActivate) { - return matcher.willLinkActivate(e, uri); - } - return true; - } - )); - } - - private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent { - 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/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 3eafb26150..8542ddcaf5 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -7,11 +7,9 @@ import { assert } from 'chai'; import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from 'browser/TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { IBufferService, IUnicodeService } from 'common/services/Services'; -import { Linkifier } from 'browser/Linkifier'; -import { MockLogService, MockUnicodeService } from 'common/TestUtils.test'; -import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types'; -import { IMarker, ITerminalOptions } from 'common/Types'; +import { MockUnicodeService } from 'common/TestUtils.test'; +import { IMouseZoneManager, IMouseZone } from 'browser/Types'; +import { IMarker } from 'common/Types'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -1044,105 +1042,6 @@ describe('Terminal', () => { }); }); - 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, terminal.unicodeService); - mouseZoneManager = new TestMouseZoneManager(); - linkifier.attachToDom({} as any, mouseZoneManager); - }); - - function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: { x1: number, y1: number, x2: number, y2: number }[]): Promise { - return new Promise(async r => { - await terminal.writeP(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); - }); - r(); - }, 0); - }); - } - - describe('unicode before the match', () => { - it('combining - match within one line', () => { - return assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{ x1: 4, x2: 7, y1: 0, y2: 0 }]); - }); - it('combining - match over two lines', () => { - return assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - it('surrogate - match within one line', () => { - return assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{ x1: 4, x2: 7, y1: 0, y2: 0 }]); - }); - it('surrogate - match over two lines', () => { - return assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - it('combining surrogate - match within one line', () => { - return assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{ x1: 4, x2: 7, y1: 0, y2: 0 }]); - }); - it('combining surrogate - match over two lines', () => { - return assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - it('fullwidth - match within one line', () => { - return assertLinkifiesInTerminal('12 foo', /foo/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); - }); - it('fullwidth - match over two lines', () => { - return assertLinkifiesInTerminal('12 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - it('combining fullwidth - match within one line', () => { - return assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); - }); - it('combining fullwidth - match over two lines', () => { - return assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - }); - describe('unicode within the match', () => { - it('combining - match within one line', () => { - return assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{ x1: 5, x2: 9, y1: 0, y2: 0 }]); - }); - it('combining - match over two lines', () => { - return assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{ x1: 9, x2: 3, y1: 0, y2: 1 }]); - }); - it('surrogate - match within one line', () => { - return assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); - }); - it('surrogate - match over two lines', () => { - return assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{ x1: 9, x2: 2, y1: 0, y2: 1 }]); - }); - it('combining surrogate - match within one line', () => { - return assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); - }); - it('combining surrogate - match over two lines', () => { - return assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{ x1: 9, x2: 2, y1: 0, y2: 1 }]); - }); - it('fullwidth - match within one line', () => { - return assertLinkifiesInTerminal('test a1b', /a1b/, [{ x1: 5, x2: 9, y1: 0, y2: 0 }]); - }); - it('fullwidth - match over two lines', () => { - return assertLinkifiesInTerminal('testtest a1b', /a1b/, [{ x1: 9, x2: 3, y1: 0, y2: 1 }]); - }); - it('combining fullwidth - match within one line', () => { - return assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{ x1: 5, x2: 9, y1: 0, y2: 0 }]); - }); - it('combining fullwidth - match over two lines', () => { - return assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{ x1: 9, x2: 3, y1: 0, y2: 1 }]); - }); - }); - }); - describe('Buffer.stringIndexToBufferIndex', () => { let terminal: TestTerminal; @@ -1519,16 +1418,6 @@ describe('Terminal', () => { }); }); -class TestLinkifier extends Linkifier { - constructor(bufferService: IBufferService, unicodeService: IUnicodeService) { - super(bufferService, new MockLogService(), unicodeService); - 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 { public dispose(): void { } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 76d5461f19..202e4a8092 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IMouseZoneManager, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; import { IRenderer } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -29,7 +29,6 @@ import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copy import { C0, C1_ESCAPED } from 'common/data/EscapeSequences'; import { WindowsOptionsReportType } from '../common/InputHandler'; import { Renderer } from 'browser/renderer/Renderer'; -import { Linkifier } from 'browser/Linkifier'; import { SelectionService } from 'browser/services/SelectionService'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -116,7 +115,6 @@ export class Terminal extends CoreTerminal implements ITerminal { */ private _unprocessedDeadKey: boolean = false; - public linkifier: ILinkifier; public linkifier2: ILinkifier2; public viewport: IViewport | undefined; private _compositionHelper: ICompositionHelper | undefined; @@ -166,7 +164,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._setup(); - this.linkifier = this._instantiationService.createInstance(Linkifier); this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2)); this._decorationService = this._instantiationService.createInstance(DecorationService); this._instantiationService.setService(IDecorationService, this._decorationService); @@ -448,7 +445,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(addDisposableDomListener(this.textarea!, 'compositionend', () => this._compositionHelper!.compositionend())); this.register(addDisposableDomListener(this.textarea!, 'input', (ev: InputEvent) => this._inputEvent(ev), true)); this.register(this.onRender(() => this._compositionHelper!.updateCompositionElements())); - this.register(this.onRender(e => this._queueLinkification(e.start, e.end))); } /** @@ -583,7 +579,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement); this.register(this._mouseZoneManager); this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); - this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement)); // This event listener must be registered aftre MouseZoneManager is created @@ -627,8 +622,8 @@ export class Terminal extends CoreTerminal implements ITerminal { private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier, this.linkifier2); - case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier, this.linkifier2); + case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier2); + case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } } @@ -908,15 +903,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._renderService?.refreshRows(start, end); } - /** - * Queues linkification for the specified rows. - * @param start The row to start from (between 0 and this.rows - 1). - * @param end The row to end at (between start and this.rows - 1). - */ - private _queueLinkification(start: number, end: number): void { - this.linkifier?.linkifyRows(start, end); - } - /** * Change the cursor style for different selection modes */ @@ -960,32 +946,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._customKeyEventHandler = customKeyEventHandler; } - /** - * Registers a link matcher, allowing custom link patterns to be matched and - * handled. - * @param regex The regular expression to search for, specifically - * this searches the textContent of the rows. You will want to use \s to match - * a space ' ' character for example. - * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @return The ID of the new matcher, this can be used to deregister. - */ - public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number { - const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); - this.refresh(0, this.rows - 1); - return matcherId; - } - - /** - * Deregisters a link matcher if it has been registered. - * @param matcherId The link matcher's ID (returned after register) - */ - public deregisterLinkMatcher(matcherId: number): void { - if (this.linkifier.deregisterLinkMatcher(matcherId)) { - this.refresh(0, this.rows - 1); - } - } - public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { return this.linkifier2.registerLinkProvider(linkProvider); } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 92f90a1d4e..62dc611e8c 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -7,7 +7,7 @@ import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOpt import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer } from 'browser/Types'; +import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -95,12 +95,6 @@ export class MockTerminal implements ITerminal { public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable { throw new Error('Method not implemented.'); } - public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number { - throw new Error('Method not implemented.'); - } - public deregisterLinkMatcher(matcherId: number): void { - throw new Error('Method not implemented.'); - } public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { throw new Error('Method not implemented.'); } @@ -148,7 +142,6 @@ export class MockTerminal implements ITerminal { } public bracketedPasteMode!: boolean; public renderer!: IRenderer; - public linkifier!: ILinkifier; public linkifier2!: ILinkifier2; public isFocused!: boolean; public options: ITerminalOptions = {}; diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 41992a8b4b..e328924a71 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -17,7 +17,6 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal { buffer: IBuffer; viewport: IViewport | undefined; options: ITerminalOptions; - linkifier: ILinkifier; linkifier2: ILinkifier2; onBlur: IEvent; @@ -56,8 +55,6 @@ export interface IPublicTerminal extends IDisposable { registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable; registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable; registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; - deregisterLinkMatcher(matcherId: number): void; registerLinkProvider(linkProvider: ILinkProvider): IDisposable; registerCharacterJoiner(handler: (text: string) => [number, number][]): number; deregisterCharacterJoiner(joinerId: number): void; @@ -153,36 +150,6 @@ export interface IViewport extends IDisposable { onThemeChange(colors: IColorSet): void; } -export interface IViewportRange { - start: IViewportRangePosition; - end: IViewportRangePosition; -} - -export interface IViewportRangePosition { - x: number; - y: number; -} - -export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void; -export type LinkMatcherHoverTooltipCallback = (event: MouseEvent, uri: string, position: IViewportRange) => void; -export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; - -export interface ILinkMatcher { - id: number; - regex: RegExp; - handler: LinkMatcherHandler; - hoverTooltipCallback?: LinkMatcherHoverTooltipCallback; - hoverLeaveCallback?: () => void; - matchIndex?: number; - validationCallback?: LinkMatcherValidationCallback; - priority?: number; - willLinkActivate?: (event: MouseEvent, uri: string) => boolean; -} - -export interface IRegisteredLinkMatcher extends ILinkMatcher { - priority: number; -} - export interface ILinkifierEvent { x1: number; y1: number; @@ -192,17 +159,6 @@ export interface ILinkifierEvent { fg: number | undefined; } -export interface ILinkifier { - onShowLinkUnderline: IEvent; - onHideLinkUnderline: 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; -} - interface ILinkState { decorations: ILinkDecorations; isHovered: boolean; @@ -221,40 +177,6 @@ export interface ILinkifier2 { registerLinkProvider(linkProvider: ILinkProvider): IDisposable; } -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?: LinkMatcherHoverTooltipCallback; - /** - * 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; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 4b7f2725d8..6898af36c2 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IDecorationOptions, IDecoration } from 'xterm'; +import { Terminal as ITerminalApi, IMarker, IDisposable, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IDecorationOptions, IDecoration } from 'xterm'; import { ITerminal } from 'browser/Types'; import { Terminal as TerminalCore } from 'browser/Terminal'; import * as Strings from 'browser/LocalizableStrings'; @@ -147,14 +147,6 @@ export class Terminal implements ITerminalApi { public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { this._core.attachCustomKeyEventHandler(customKeyEventHandler); } - public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number { - this._checkProposedApi(); - return this._core.registerLinkMatcher(regex, handler, options); - } - public deregisterLinkMatcher(matcherId: number): void { - this._checkProposedApi(); - this._core.deregisterLinkMatcher(matcherId); - } public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { this._checkProposedApi(); return this._core.registerLinkProvider(linkProvider); diff --git a/src/browser/renderer/LinkRenderLayer.ts b/src/browser/renderer/LinkRenderLayer.ts index 15086d9a2e..f1a9b13e57 100644 --- a/src/browser/renderer/LinkRenderLayer.ts +++ b/src/browser/renderer/LinkRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils'; -import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; +import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; export class LinkRenderLayer extends BaseRenderLayer { @@ -18,15 +18,12 @@ export class LinkRenderLayer extends BaseRenderLayer { zIndex: number, colors: IColorSet, rendererId: number, - linkifier: ILinkifier, linkifier2: ILinkifier2, @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, @IDecorationService decorationService: IDecorationService ) { super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); - linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); - linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 8bc3227828..665979318a 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -9,7 +9,7 @@ import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer'; import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer'; import { Disposable } from 'common/Lifecycle'; -import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types'; +import { IColorSet, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; @@ -31,7 +31,6 @@ export class Renderer extends Disposable implements IRenderer { constructor( private _colors: IColorSet, private readonly _screenElement: HTMLElement, - linkifier: ILinkifier, linkifier2: ILinkifier2, @IInstantiationService instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService, @@ -43,7 +42,7 @@ export class Renderer extends Disposable implements IRenderer { this._renderLayers = [ instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id), instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id), - instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier, linkifier2), + instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier2), instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw) ]; this.dimensions = { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 248f37ac4b..a4500e39d7 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -7,7 +7,7 @@ import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/rende 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, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; +import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; @@ -47,7 +47,6 @@ export class DomRenderer extends Disposable implements IRenderer { private readonly _element: HTMLElement, private readonly _screenElement: HTMLElement, private readonly _viewportElement: HTMLElement, - private readonly _linkifier: ILinkifier, private readonly _linkifier2: ILinkifier2, @IInstantiationService instantiationService: IInstantiationService, @ICharSizeService private readonly _charSizeService: ICharSizeService, @@ -87,9 +86,6 @@ export class DomRenderer extends Disposable implements IRenderer { this._screenElement.appendChild(this._rowContainer); this._screenElement.appendChild(this._selectionContainer); - this.register(this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e))); - this.register(this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e))); - this.register(this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e))); this.register(this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e))); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index e8a2f8f4ae..b6bb0fda7a 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -316,50 +316,6 @@ declare module 'xterm' { extendedAnsi?: string[]; } - /** - * An object containing options for a link matcher. - */ - 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 whether to create an individual link, pass - * whether the link is valid to the callback. - */ - validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void; - - /** - * A callback that fires when the mouse hovers over a link for a period of - * time (defined by {@link ITerminalOptions.linkTooltipHoverDuration}). - */ - tooltipCallback?: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void; - - /** - * A callback that fires when the mouse leaves a link. Note that this can - * happen even when tooltipCallback hasn't fired for the link yet. - */ - 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; - } - /** * An object that can be disposed via a dispose function. */ @@ -909,28 +865,6 @@ declare module 'xterm' { */ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - /** - * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to - * be matched and handled. - * @deprecated The link matcher API is now deprecated in favor of the link - * provider API, see `registerLinkProvider`. - * @param regex The regular expression to search for, specifically this - * searches the textContent of the rows. You will want to use \s to match a - * space ' ' character for example. - * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @return The ID of the new matcher, this can be used to deregister. - */ - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; - - /** - * (EXPERIMENTAL) Deregisters a link matcher if it has been registered. - * @deprecated The link matcher API is now deprecated in favor of the link - * provider API, see `registerLinkProvider`. - * @param matcherId The link matcher's ID (returned after register) - */ - deregisterLinkMatcher(matcherId: number): void; - /** * Registers a link provider, allowing a custom parser to be used to match * and handle links. Multiple link providers can be used, they will be asked @@ -1274,7 +1208,7 @@ declare module 'xterm' { /** * An object representing a range within the viewport of the terminal. */ - export interface IViewportRange { + export interface IViewportRange { /** * The start of the range. */