diff --git a/src/Interfaces.ts b/src/Interfaces.ts index d2f8e8726d..ca228ce01b 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -85,6 +85,12 @@ export interface LinkMatcherOptions { * false if invalid. */ validationCallback?: LinkMatcherValidationCallback; + /** + * 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; } /** diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index b1c1541df1..012b1b6869 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -5,12 +5,15 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; import { ITerminal, ILinkifier } from './Interfaces'; import { Linkifier } from './Linkifier'; +import { LinkMatcher } from './Types'; class TestLinkifier extends Linkifier { constructor(document: Document, rows: HTMLElement[]) { Linkifier.TIME_BEFORE_LINKIFY = 0; super(document, rows); } + + public get linkMatchers(): LinkMatcher[] { return this._linkMatchers; } } describe('Linkifier', () => { @@ -19,14 +22,14 @@ describe('Linkifier', () => { let container: HTMLElement; let rows: HTMLElement[]; - let linkifier: ILinkifier; + let linkifier: TestLinkifier; beforeEach(done => { rows = []; jsdom.env('', (err, w) => { window = w; document = window.document; - linkifier = new Linkifier(document, rows); + linkifier = new TestLinkifier(document, rows); container = document.createElement('div'); document.body.appendChild(container); done(); @@ -73,4 +76,24 @@ describe('Linkifier', () => { setTimeout(() => done(), 10); }); }); + + 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, 0, 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, 0, 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), [0, aId, bId]); + }); + }); }); diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 0b144cfda0..7aab1ef3c8 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -3,15 +3,7 @@ */ import { LinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; - -type LinkMatcher = { - id: number, - regex: RegExp, - handler: LinkMatcherHandler, - matchIndex?: number, - validationCallback?: LinkMatcherValidationCallback; -}; +import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; const INVALID_LINK_CLASS = 'xterm-invalid-link'; @@ -50,10 +42,11 @@ export class Linkifier { */ protected static TIME_BEFORE_LINKIFY = 200; + protected _linkMatchers: LinkMatcher[]; + private _document: Document; private _rows: HTMLElement[]; private _rowTimeoutIds: number[]; - private _linkMatchers: LinkMatcher[]; private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; constructor(document: Document, rows: HTMLElement[]) { @@ -105,12 +98,35 @@ export class Linkifier { regex, handler, matchIndex: options.matchIndex, - validationCallback: options.validationCallback + validationCallback: options.validationCallback, + priority: options.priority || 0 }; - this._linkMatchers.push(matcher); + 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: LinkMatcher): 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 {number} matcherId The link matcher's ID (returned after register) @@ -137,7 +153,6 @@ export class Linkifier { return; } const text = row.textContent; - // TODO: Onl execute handler if isValid for (let i = 0; i < this._linkMatchers.length; i++) { const matcher = this._linkMatchers[i]; const uri = this._findLinkMatch(text, matcher.regex, matcher.matchIndex); diff --git a/src/Types.ts b/src/Types.ts index 038a1f7a2c..2dbb47effb 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -2,5 +2,13 @@ * @license MIT */ +export type LinkMatcher = { + id: number, + regex: RegExp, + handler: LinkMatcherHandler, + matchIndex?: number, + validationCallback?: LinkMatcherValidationCallback, + priority?: number +}; export type LinkMatcherHandler = (uri: string) => void; export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;