Remove usage of ! operator

This commit is contained in:
Jon Bockhorst
2019-11-01 13:42:02 -05:00
parent cf082cca04
commit ec78fc7e7c
5 changed files with 46 additions and 681 deletions
@@ -1,217 +1,23 @@
import { ILinkProvider, IBufferCellPosition, ILink, Terminal, IBuffer } from 'xterm';
import { CharCode } from './charCode';
import { CharacterClassifier } from './characterClassifier';
export default class WebLinkProvider implements ILinkProvider {
constructor(
private readonly _terminal: Terminal,
private readonly _regex: RegExp,
private readonly _handler: (event: MouseEvent, uri: string) => void
) {
}
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
const link = LinkComputer.computeLink(position, this._terminal.buffer, this._handler);
callback(link);
callback(LinkComputer.computeLink(position, this._regex, this._terminal.buffer, this._handler));
}
}
export const enum State {
INVALID = 0,
START = 1,
H = 2,
HT = 3,
HTT = 4,
HTTP = 5,
BEFORE_COLON = 9,
AFTER_COLON = 10,
ALMOST_THERE = 11,
END = 12,
ACCEPT = 13,
LAST_KNOWN_STATE = 14 // marker, custom states may follow
}
export type Edge = [State, number, State];
export class Uint8Matrix {
private readonly _data: Uint8Array;
public readonly rows: number;
public readonly cols: number;
constructor(rows: number, cols: number, defaultValue: number) {
const data = new Uint8Array(rows * cols);
const len = rows * cols;
for (let i = 0; i < len; i++) {
data[i] = defaultValue;
}
this._data = data;
this.rows = rows;
this.cols = cols;
}
public get(row: number, col: number): number {
return this._data[row * this.cols + col];
}
public set(row: number, col: number, value: number): void {
this._data[row * this.cols + col] = value;
}
}
export class StateMachine {
private readonly _states: Uint8Matrix;
private readonly _maxCharCode: number;
constructor(edges: Edge[]) {
let maxCharCode = 0;
let maxState = State.INVALID;
for (let i = 0; i < edges.length; i++) {
const [from, chCode, to] = edges[i];
if (chCode > maxCharCode) {
maxCharCode = chCode;
}
if (from > maxState) {
maxState = from;
}
if (to > maxState) {
maxState = to;
}
}
maxCharCode++;
maxState++;
const states = new Uint8Matrix(maxState, maxCharCode, State.INVALID);
for (let i = 0; i < edges.length; i++) {
const [from, chCode, to] = edges[i];
states.set(from, chCode, to);
}
this._states = states;
this._maxCharCode = maxCharCode;
}
public nextState(currentState: State, chCode: number): State {
if (chCode < 0 || chCode >= this._maxCharCode) {
return State.INVALID;
}
return this._states.get(currentState, chCode);
}
}
// State machine for http:// or https:// or file://
let stateMachine: StateMachine | null = null;
function getStateMachine(): StateMachine {
if (stateMachine === null) {
stateMachine = new StateMachine([
[State.START, CharCode.h, State.H],
[State.START, CharCode.H, State.H],
[State.H, CharCode.t, State.HT],
[State.H, CharCode.T, State.HT],
[State.HT, CharCode.t, State.HTT],
[State.HT, CharCode.T, State.HTT],
[State.HTT, CharCode.p, State.HTTP],
[State.HTT, CharCode.P, State.HTTP],
[State.HTTP, CharCode.s, State.BEFORE_COLON],
[State.HTTP, CharCode.S, State.BEFORE_COLON],
[State.HTTP, CharCode.COLON, State.AFTER_COLON],
[State.BEFORE_COLON, CharCode.COLON, State.AFTER_COLON],
[State.AFTER_COLON, CharCode.SLASH, State.ALMOST_THERE],
[State.ALMOST_THERE, CharCode.SLASH, State.END]
]);
}
return stateMachine;
}
const enum CharacterClass {
NONE = 0,
FORCE_TERMINATION = 1,
CANNOT_END_IN = 2
}
let classifier: CharacterClassifier<CharacterClass> | null = null;
function getClassifier(): CharacterClassifier<CharacterClass> {
if (classifier === null) {
classifier = new CharacterClassifier<CharacterClass>(CharacterClass.NONE);
const FORCE_TERMINATION_CHARACTERS = ' \t<>\'\"、。。、,.:;?!@#$%&*‘“〈《「『【〔([{「」}])〕】』」》〉”’`~…';
for (let i = 0; i < FORCE_TERMINATION_CHARACTERS.length; i++) {
classifier.set(FORCE_TERMINATION_CHARACTERS.charCodeAt(i), CharacterClass.FORCE_TERMINATION);
}
const CANNOT_END_WITH_CHARACTERS = '.,;';
for (let i = 0; i < CANNOT_END_WITH_CHARACTERS.length; i++) {
classifier.set(CANNOT_END_WITH_CHARACTERS.charCodeAt(i), CharacterClass.CANNOT_END_IN);
}
}
return classifier;
}
export class LinkComputer {
private static _createLink(classifier: CharacterClassifier<CharacterClass>, line: string, lineNumber: number, linkBeginIndex: number, linkEndIndex: number, handler: (event: MouseEvent, link: string) => void): ILink {
// Do not allow to end link in certain characters...
let lastIncludedCharIndex = linkEndIndex - 1;
do {
const chCode = line.charCodeAt(lastIncludedCharIndex);
const chClass = classifier.get(chCode);
if (chClass !== CharacterClass.CANNOT_END_IN) {
break;
}
lastIncludedCharIndex--;
} while (lastIncludedCharIndex > linkBeginIndex);
// Handle links enclosed in parens, square brackets and curlys.
if (linkBeginIndex > 0) {
const charCodeBeforeLink = line.charCodeAt(linkBeginIndex - 1);
const lastCharCodeInLink = line.charCodeAt(lastIncludedCharIndex);
if (
(charCodeBeforeLink === CharCode.OPEN_PAREN && lastCharCodeInLink === CharCode.CLOSE_PAREN)
|| (charCodeBeforeLink === CharCode.OPEN_SQUARE_BRACKET && lastCharCodeInLink === CharCode.CLOSE_SQUARE_BRACKET)
|| (charCodeBeforeLink === CharCode.OPEN_CURLY_BRACE && lastCharCodeInLink === CharCode.CLOSE_CURLY_BRACE)
) {
// Do not end in ) if ( is before the link start
// Do not end in ] if [ is before the link start
// Do not end in } if { is before the link start
lastIncludedCharIndex--;
}
}
return {
range: {
start: {
x: linkBeginIndex + 1,
y: lineNumber
},
end: {
x: lastIncludedCharIndex + 2,
y: lineNumber
}
},
url: line.substring(linkBeginIndex, lastIncludedCharIndex + 1),
handle: handler
};
}
public static computeLink(position: IBufferCellPosition, buffer: IBuffer, handler: (event: MouseEvent, link: string) => void): ILink | undefined {
const stateMachine: StateMachine = getStateMachine();
const classifier = getClassifier();
public static computeLink(position: IBufferCellPosition, regex: RegExp, buffer: IBuffer, handler: (event: MouseEvent, uri: string) => void): ILink | undefined {
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
const bufferLine = buffer.getLine(position.y - 1);
if (!bufferLine) {
@@ -219,111 +25,13 @@ export class LinkComputer {
}
const line = bufferLine.translateToString();
const len = line.length;
const i = position.y;
let match;
let stringIndex = -1;
let linkBeginIndex = position.x - 1;
let state = State.START;
let hasOpenParens = false;
let hasOpenSquareBracket = false;
let hasOpenCurlyBracket = false;
// while ((match = rex.exec(line)) !== null) {
// const uri = match[1];
// }
while (linkBeginIndex >= 0) {
let j = linkBeginIndex;
while (j < len) {
const linkBeginChCode = line.charCodeAt(j);
const chCode = line.charCodeAt(j);
if (state === State.ACCEPT) {
let chClass: CharacterClass;
switch (chCode) {
case CharCode.OPEN_PAREN:
hasOpenParens = true;
chClass = CharacterClass.NONE;
break;
case CharCode.CLOSE_PAREN:
chClass = (hasOpenParens ? CharacterClass.NONE : CharacterClass.FORCE_TERMINATION);
break;
case CharCode.OPEN_SQUARE_BRACKET:
hasOpenSquareBracket = true;
chClass = CharacterClass.NONE;
break;
case CharCode.CLOSE_SQUARE_BRACKET:
chClass = (hasOpenSquareBracket ? CharacterClass.NONE : CharacterClass.FORCE_TERMINATION);
break;
case CharCode.OPEN_CURLY_BRACE:
hasOpenCurlyBracket = true;
chClass = CharacterClass.NONE;
break;
case CharCode.CLOSE_CURLY_BRACE:
chClass = (hasOpenCurlyBracket ? CharacterClass.NONE : CharacterClass.FORCE_TERMINATION);
break;
/* The following three rules make it that ' or " or ` are allowed inside links if the link began with a different one */
case CharCode.SINGLE_QUOTE:
chClass = (linkBeginChCode === CharCode.DOUBLE_QUOTE || linkBeginChCode === CharCode.BACK_TICK) ? CharacterClass.NONE : CharacterClass.FORCE_TERMINATION;
break;
case CharCode.DOUBLE_QUOTE:
chClass = (linkBeginChCode === CharCode.SINGLE_QUOTE || linkBeginChCode === CharCode.BACK_TICK) ? CharacterClass.NONE : CharacterClass.FORCE_TERMINATION;
break;
case CharCode.BACK_TICK:
chClass = (linkBeginChCode === CharCode.SINGLE_QUOTE || linkBeginChCode === CharCode.DOUBLE_QUOTE) ? CharacterClass.NONE : CharacterClass.FORCE_TERMINATION;
break;
case CharCode.ASTERISK:
// `*` terminates a link if the link began with `*`
chClass = (linkBeginChCode === CharCode.ASTERISK) ? CharacterClass.FORCE_TERMINATION : CharacterClass.NONE;
break;
default:
chClass = classifier.get(chCode);
}
// Check if character terminates link
if (chClass === CharacterClass.FORCE_TERMINATION) {
return LinkComputer._createLink(classifier, line, i, linkBeginIndex, j, handler);
}
} else if (state === State.END) {
let chClass: CharacterClass;
if (chCode === CharCode.OPEN_SQUARE_BRACKET) {
// Allow for the authority part to contain ipv6 addresses which contain [ and ]
hasOpenSquareBracket = true;
chClass = CharacterClass.NONE;
} else {
chClass = classifier.get(chCode);
}
// Check if character terminates link
if (chClass === CharacterClass.FORCE_TERMINATION) {
return;
}
state = State.ACCEPT;
} else {
state = stateMachine.nextState(state, chCode);
if (state === State.INVALID) {
// Two spaces in a row, return
if (chCode === CharCode.SPACE && j > 0 && line.charCodeAt(j - 1) === CharCode.SPACE) {
return;
}
// Reset state machine
state = State.START;
hasOpenParens = false;
hasOpenSquareBracket = false;
hasOpenCurlyBracket = false;
// Move to the left
linkBeginIndex--;
break;
}
}
j++;
}
if (state === State.ACCEPT) {
return LinkComputer._createLink(classifier, line, i, linkBeginIndex, len, handler);
}
}
}
}
@@ -44,9 +44,13 @@ export class WebLinksAddon implements ITerminalAddon {
public activate(terminal: Terminal): void {
this._terminal = terminal;
this._linkProvider = new WebLinkProvider(this._terminal, this._handler);
// this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, this._handler, this._options);
// if ('registerLinkProvider' in this._terminal) {
this._linkProvider = new WebLinkProvider(this._terminal, strictUrlRegex, this._handler);
this._terminal.registerLinkProvider(this._linkProvider);
// } else {
// this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, this._handler, this._options);
// }
}
public dispose(): void {
@@ -1,237 +0,0 @@
// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/
/**
* An inlined enum containing useful character codes (to be used with String.charCodeAt).
* Please leave the const keyword such that it gets inlined when compiled to JavaScript!
*/
export const enum CharCode {
NULL = 0,
/**
* The `\b` character.
*/
BACKSPACE = 8,
/**
* The `\t` character.
*/
TAB = 9,
/**
* The `\n` character.
*/
LINE_FEED = 10,
/**
* The `\r` character.
*/
CARRIAGE_RETURN = 13,
SPACE = 32,
/**
* The `!` character.
*/
EXCLAMATION_MARK = 33,
/**
* The `"` character.
*/
DOUBLE_QUOTE = 34,
/**
* The `#` character.
*/
HASH = 35,
/**
* The `$` character.
*/
DOLLAR_SIGN = 36,
/**
* The `%` character.
*/
PERCENT_SIGN = 37,
/**
* The `&` character.
*/
AMPERSAND = 38,
/**
* The `'` character.
*/
SINGLE_QUOTE = 39,
/**
* The `(` character.
*/
OPEN_PAREN = 40,
/**
* The `)` character.
*/
CLOSE_PAREN = 41,
/**
* The `*` character.
*/
ASTERISK = 42,
/**
* The `+` character.
*/
PLUS = 43,
/**
* The `,` character.
*/
COMMA = 44,
/**
* The `-` character.
*/
DASH = 45,
/**
* The `.` character.
*/
PERIOD = 46,
/**
* The `/` character.
*/
SLASH = 47,
DIGIT_0 = 48,
DIGIT_1 = 49,
DIGIT_2 = 50,
DIGIT_3 = 51,
DIGIT_4 = 52,
DIGIT_5 = 53,
DIGIT_6 = 54,
DIGIT_7 = 55,
DIGIT_8 = 56,
DIGIT_9 = 57,
/**
* The `:` character.
*/
COLON = 58,
/**
* The `;` character.
*/
SEMICOLON = 59,
/**
* The `<` character.
*/
LESS_THAN = 60,
/**
* The `=` character.
*/
EQUALS = 61,
/**
* The `>` character.
*/
GREATER_THAN = 62,
/**
* The `?` character.
*/
QUESTION_MARK = 63,
/**
* The `@` character.
*/
AT_SIGN = 64,
A = 65,
B = 66,
C = 67,
D = 68,
E = 69,
F = 70,
G = 71,
H = 72,
I = 73,
J = 74,
K = 75,
L = 76,
M = 77,
N = 78,
O = 79,
P = 80,
Q = 81,
R = 82,
S = 83,
T = 84,
U = 85,
V = 86,
W = 87,
X = 88,
Y = 89,
Z = 90,
/**
* The `[` character.
*/
OPEN_SQUARE_BRACKET = 91,
/**
* The `\` character.
*/
BACK_SLASH = 92,
/**
* The `]` character.
*/
CLOSE_SQUARE_BRACKET = 93,
/**
* The `^` character.
*/
CARET = 94,
/**
* The `_` character.
*/
UNDERLINE = 95,
/**
* The ``(`)`` character.
*/
BACK_TICK = 96,
a = 97,
b = 98,
c = 99,
d = 100,
e = 101,
f = 102,
g = 103,
h = 104,
i = 105,
j = 106,
k = 107,
l = 108,
m = 109,
n = 110,
o = 111,
p = 112,
q = 113,
r = 114,
s = 115,
t = 116,
u = 117,
v = 118,
w = 119,
x = 120,
y = 121,
z = 122,
/**
* The `{` character.
*/
OPEN_CURLY_BRACE = 123,
/**
* The `|` character.
*/
PIPE = 124,
/**
* The `}` character.
*/
CLOSE_CURLY_BRACE = 125,
/**
* The `~` character.
*/
TILDE = 126,
/**
* Unicode Character 'LINE SEPARATOR' (U+2028)
* http://www.fileformat.info/info/unicode/char/2028/index.htm
*/
LINE_SEPARATOR_2028 = 8232,
U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE'
/**
* UTF-8 BOM
* Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)
* http://www.fileformat.info/info/unicode/char/feff/index.htm
*/
UTF8_BOM = 65279
}
@@ -1,116 +0,0 @@
/**
* A fast character classifier that uses a compact array for ASCII values.
*/
export class CharacterClassifier<T extends number> {
/**
* Maintain a compact (fully initialized ASCII map for quickly classifying ASCII characters - used more often in code).
*/
private _asciiMap: Uint8Array;
/**
* The entire map (sparse array).
*/
private _map: Map<number, number>;
private _defaultValue: number;
constructor(newDefaultValue: T) {
const defaultValue = toUint8(newDefaultValue);
this._defaultValue = defaultValue;
this._asciiMap = CharacterClassifier._createAsciiMap(defaultValue);
this._map = new Map<number, number>();
}
private static _createAsciiMap(defaultValue: number): Uint8Array {
const asciiMap: Uint8Array = new Uint8Array(256);
for (let i = 0; i < 256; i++) {
asciiMap[i] = defaultValue;
}
return asciiMap;
}
public set(charCode: number, newValue: T): void {
const value = toUint8(newValue);
if (charCode >= 0 && charCode < 256) {
this._asciiMap[charCode] = value;
} else {
this._map.set(charCode, value);
}
}
public get(charCode: number): T {
if (charCode >= 0 && charCode < 256) {
return <T>this._asciiMap[charCode];
}
return <T>(this._map.get(charCode) || this._defaultValue);
}
}
const enum Boolean {
FALSE = 0,
TRUE = 1
}
export class CharacterSet {
private readonly _actual: CharacterClassifier<Boolean>;
constructor() {
this._actual = new CharacterClassifier<Boolean>(Boolean.FALSE);
}
public add(charCode: number): void {
this._actual.set(charCode, Boolean.TRUE);
}
public has(charCode: number): boolean {
return (this._actual.get(charCode) === Boolean.TRUE);
}
}
export const enum Constants {
/**
* MAX SMI (SMall Integer) as defined in v8.
* one bit is lost for boxing/unboxing flag.
* one bit is lost for sign flag.
* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
*/
MAX_SAFE_SMALL_INTEGER = 1 << 30,
/**
* MIN SMI (SMall Integer) as defined in v8.
* one bit is lost for boxing/unboxing flag.
* one bit is lost for sign flag.
* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
*/
MIN_SAFE_SMALL_INTEGER = -(1 << 30),
/**
* Max unsigned integer that fits on 8 bits.
*/
MAX_UINT_8 = 255, // 2^8 - 1
/**
* Max unsigned integer that fits on 16 bits.
*/
MAX_UINT_16 = 65535, // 2^16 - 1
/**
* Max unsigned integer that fits on 32 bits.
*/
MAX_UINT_32 = 4294967295, // 2^32 - 1
UNICODE_SUPPLEMENTARY_PLANE_BEGIN = 0x010000
}
export function toUint8(v: number): number {
if (v < 0) {
return 0;
}
if (v > Constants.MAX_UINT_8) {
return Constants.MAX_UINT_8;
}
return v | 0;
}
+31 -25
View File
@@ -43,7 +43,11 @@ export class Linkifier2 implements ILinkifier2 {
}
private _onMouseMove(event: MouseEvent): void {
const position = this._positionFromMouseEvent(event);
if (!this._element || !this._mouseService) {
return;
}
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
if (!position) {
return;
@@ -53,32 +57,34 @@ export class Linkifier2 implements ILinkifier2 {
// Check the cache for a link and determine if we need to show or hide tooltip
let foundLink = false;
this._linkCache.forEach((cachedLink, i) => {
const isInPosition = this._linkAtPosition(cachedLink.link, position);
const range = cachedLink.link.range;
if (isInPosition && !cachedLink.mouseOver) {
for (let i = 0; i < this._linkCache.length; i++) {
const cachedLink = this._linkCache[i].link;
const isInPosition = this._linkAtPosition(cachedLink, position);
const range = cachedLink.range;
if (isInPosition && !this._linkCache[i].mouseOver) {
// Show the tooltip
this._onShowTooltip.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x - 1, range.end.y - scrollOffset - 1, undefined));
this._element!.classList.add('xterm-cursor-pointer');
this._element.classList.add('xterm-cursor-pointer');
if (cachedLink.link.showTooltip) {
cachedLink.link.showTooltip(event, cachedLink.link.url);
if (cachedLink.showTooltip) {
cachedLink.showTooltip(event, cachedLink.url);
}
this._linkCache[i].mouseOver = true;
foundLink = true;
} else if (!isInPosition && cachedLink.mouseOver) {
} else if (!isInPosition && this._linkCache[i].mouseOver) {
// Hide the tooltip
this._onHideTooltip.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x - 1, range.end.y - scrollOffset - 1, undefined));
this._element!.classList.remove('xterm-cursor-pointer');
this._element.classList.remove('xterm-cursor-pointer');
if (cachedLink.link.hideTooltip) {
cachedLink.link.hideTooltip(event, cachedLink.link.url);
if (cachedLink.hideTooltip) {
cachedLink.hideTooltip(event, cachedLink.url);
}
this._linkCache[i].mouseOver = false;
}
});
}
if (foundLink) {
return;
@@ -91,7 +97,11 @@ export class Linkifier2 implements ILinkifier2 {
}
private _onMouseDown(event: MouseEvent): void {
const position = this._positionFromMouseEvent(event);
if (!this._element || !this._mouseService) {
return;
}
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
if (!position) {
return;
@@ -106,7 +116,7 @@ export class Linkifier2 implements ILinkifier2 {
private _handleNewLink(link: ILink | undefined): void {
if (link && !this._linkCache.find(cachedLink => cachedLink.link = link)) {
this._linkCache.push({ link: link, mouseOver: false });
this._linkCache.push({ link, mouseOver: false });
}
}
@@ -116,22 +126,18 @@ export class Linkifier2 implements ILinkifier2 {
* @param position
*/
private _linkAtPosition(link: ILink, position: IBufferCellPosition): boolean {
return link.range.start.x <= position.x
&& link.range.start.y <= position.y
&& link.range.end.x >= position.x
&& link.range.end.y >= position.y;
return link.range.start.x <= position.x &&
link.range.start.y <= position.y &&
link.range.end.x >= position.x &&
link.range.end.y >= position.y;
}
/**
* Get the buffer position from a mouse event
* @param event
*/
private _positionFromMouseEvent(event: MouseEvent): IBufferCellPosition | undefined {
if (!this._element) {
return;
}
const coords = this._mouseService!.getCoords(event, this._element, this._bufferService.cols, this._bufferService.rows);
private _positionFromMouseEvent(event: MouseEvent, element: HTMLElement, mouseService: IMouseService): IBufferCellPosition | undefined {
const coords = mouseService.getCoords(event, element, this._bufferService.cols, this._bufferService.rows);
if (!coords) {
return;
}