Initial linkify implementation

Part of 455
This commit is contained in:
Daniel Imms
2017-02-08 11:11:52 -08:00
parent e0639e1b62
commit 2207d356f2
3 changed files with 98 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
/**
* The time to wait after a row is changed before it is linkified. This prevents
* the costly operation of searching every row multiple times, pntentially a
* huge aount of times.
*/
const TIME_BEFORE_LINKIFY = 200;
const badUrlRegex = /https?:\/\/(\/[\/\\w\.-]*)*/;
const protocolClause = '(https?:\\/\\/)';
const domainCharacterSet = '[\\da-z\\.-]+';
const negatedDomainCharacterSet = '[^\\da-z\\.-]+';
const domainBodyClause = '(' + domainCharacterSet + ')';
const tldClause = '([a-z\\.]{2,6})';
const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
const portClause = '(:\\d{1,5})';
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + ')' + portClause + '?';
const pathClause = '(\\/[\\/\\w\\.-]*)*';
const negatedPathCharacterSet = '[^\\/\\w\\.-]+';
const bodyClause = hostClause + pathClause;
const start = '(?:^|' + negatedDomainCharacterSet + ')(';
const end = ')($|' + negatedPathCharacterSet + ')';
const lenientUrlClause = start + protocolClause + '?' + bodyClause + end;
const strictUrlClause = start + protocolClause + bodyClause + end;
const lenientUrlRegex = new RegExp(lenientUrlClause);
const strictUrlRegex = new RegExp(strictUrlClause);
export type LinkHandler = (uri: string) => void;
export class Linkifier {
private _rows: HTMLElement[];
private _rowTimeoutIds: number[];
private _webLinkHandler: LinkHandler;
constructor(rows: HTMLElement[]) {
this._rows = rows;
this._rowTimeoutIds = [];
}
/**
* Queues a row for linkification.
* @param {number} rowIndex The index of the row to linkify.
*/
public linkifyRow(rowIndex: number): void {
const timeoutId = this._rowTimeoutIds[rowIndex];
if (timeoutId) {
clearTimeout(timeoutId);
}
this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), TIME_BEFORE_LINKIFY);
}
public attachWebLinkHandler(handler: LinkHandler): void {
this._webLinkHandler = handler;
}
/**
* Linkifies a row.
* @param {number} rowIndex The index of the row to linkify.
*/
private _linkifyRow(rowIndex: number): void {
const rowHtml = this._rows[rowIndex].innerHTML;
const uri = this._findLinkMatch(rowHtml);
if (uri) {
const link = '<a href="' + uri + '">' + uri + '</a>';
const newHtml = rowHtml.replace(uri, link);
this._rows[rowIndex].innerHTML = newHtml;
console.log(this._rows[rowIndex].innerHTML);
}
}
/**
* Finds a link match in a piece of HTML.
* @param {string} html The HTML to search.
* @return The matching URI or null if not found.
*/
private _findLinkMatch(html): string {
const match = html.match(strictUrlRegex);
if (!match || match.length === 0) {
return null;
}
return match[1];
}
}
+4
View File
@@ -71,6 +71,10 @@
resize: none;
}
.terminal a {
color: inherit;
}
.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
background-color: #fff;
color: #000;
+11
View File
@@ -19,6 +19,7 @@ import { C0 } from './EscapeSequences';
import { InputHandler } from './InputHandler';
import { Parser } from './Parser';
import { Renderer } from './Renderer';
import { Linkifier } from './Linkifier';
import { CharMeasure } from './utils/CharMeasure';
import * as Browser from './utils/Browser';
import * as Keyboard from './utils/Keyboard';
@@ -210,6 +211,7 @@ function Terminal(options) {
this.parser = new Parser(this.inputHandler, this);
// Reuse renderer if the Terminal is being recreated via a Terminal.reset call.
this.renderer = this.renderer || null;
this.linkifier = this.linkifier || null;;
// user input states
this.writeBuffer = [];
@@ -607,6 +609,7 @@ Terminal.prototype.open = function(parent) {
this.rowContainer.classList.add('xterm-rows');
this.element.appendChild(this.rowContainer);
this.children = [];
this.linkifier = new Linkifier(this.children);
// Create the container that will hold helpers like the textarea for
// capturing DOM Events. Then produce the helpers.
@@ -1060,6 +1063,14 @@ Terminal.prototype.destroy = function() {
Terminal.prototype.refresh = function(start, end) {
if (this.renderer) {
this.renderer.queueRefresh(start, end);
// TODO: DO this better
if (!this.linkifier) {
return;
}
for (let i = start; i <= end; i++) {
this.linkifier.linkifyRow(i);
}
}
};