monaco-editor-core
Version:
A browser based code editor
271 lines (270 loc) • 13.4 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CharacterClassifier } from '../core/characterClassifier.js';
class Uint8Matrix {
constructor(rows, cols, defaultValue) {
const data = new Uint8Array(rows * cols);
for (let i = 0, len = rows * cols; i < len; i++) {
data[i] = defaultValue;
}
this._data = data;
this.rows = rows;
this.cols = cols;
}
get(row, col) {
return this._data[row * this.cols + col];
}
set(row, col, value) {
this._data[row * this.cols + col] = value;
}
}
export class StateMachine {
constructor(edges) {
let maxCharCode = 0;
let maxState = 0 /* State.Invalid */;
for (let i = 0, len = edges.length; i < len; 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, 0 /* State.Invalid */);
for (let i = 0, len = edges.length; i < len; i++) {
const [from, chCode, to] = edges[i];
states.set(from, chCode, to);
}
this._states = states;
this._maxCharCode = maxCharCode;
}
nextState(currentState, chCode) {
if (chCode < 0 || chCode >= this._maxCharCode) {
return 0 /* State.Invalid */;
}
return this._states.get(currentState, chCode);
}
}
// State machine for http:// or https:// or file://
let _stateMachine = null;
function getStateMachine() {
if (_stateMachine === null) {
_stateMachine = new StateMachine([
[1 /* State.Start */, 104 /* CharCode.h */, 2 /* State.H */],
[1 /* State.Start */, 72 /* CharCode.H */, 2 /* State.H */],
[1 /* State.Start */, 102 /* CharCode.f */, 6 /* State.F */],
[1 /* State.Start */, 70 /* CharCode.F */, 6 /* State.F */],
[2 /* State.H */, 116 /* CharCode.t */, 3 /* State.HT */],
[2 /* State.H */, 84 /* CharCode.T */, 3 /* State.HT */],
[3 /* State.HT */, 116 /* CharCode.t */, 4 /* State.HTT */],
[3 /* State.HT */, 84 /* CharCode.T */, 4 /* State.HTT */],
[4 /* State.HTT */, 112 /* CharCode.p */, 5 /* State.HTTP */],
[4 /* State.HTT */, 80 /* CharCode.P */, 5 /* State.HTTP */],
[5 /* State.HTTP */, 115 /* CharCode.s */, 9 /* State.BeforeColon */],
[5 /* State.HTTP */, 83 /* CharCode.S */, 9 /* State.BeforeColon */],
[5 /* State.HTTP */, 58 /* CharCode.Colon */, 10 /* State.AfterColon */],
[6 /* State.F */, 105 /* CharCode.i */, 7 /* State.FI */],
[6 /* State.F */, 73 /* CharCode.I */, 7 /* State.FI */],
[7 /* State.FI */, 108 /* CharCode.l */, 8 /* State.FIL */],
[7 /* State.FI */, 76 /* CharCode.L */, 8 /* State.FIL */],
[8 /* State.FIL */, 101 /* CharCode.e */, 9 /* State.BeforeColon */],
[8 /* State.FIL */, 69 /* CharCode.E */, 9 /* State.BeforeColon */],
[9 /* State.BeforeColon */, 58 /* CharCode.Colon */, 10 /* State.AfterColon */],
[10 /* State.AfterColon */, 47 /* CharCode.Slash */, 11 /* State.AlmostThere */],
[11 /* State.AlmostThere */, 47 /* CharCode.Slash */, 12 /* State.End */],
]);
}
return _stateMachine;
}
let _classifier = null;
function getClassifier() {
if (_classifier === null) {
_classifier = new CharacterClassifier(0 /* CharacterClass.None */);
// allow-any-unicode-next-line
const FORCE_TERMINATION_CHARACTERS = ' \t<>\'\"、。。、,.:;‘〈「『〔([{「」}])〕』」〉’`~…';
for (let i = 0; i < FORCE_TERMINATION_CHARACTERS.length; i++) {
_classifier.set(FORCE_TERMINATION_CHARACTERS.charCodeAt(i), 1 /* CharacterClass.ForceTermination */);
}
const CANNOT_END_WITH_CHARACTERS = '.,;:';
for (let i = 0; i < CANNOT_END_WITH_CHARACTERS.length; i++) {
_classifier.set(CANNOT_END_WITH_CHARACTERS.charCodeAt(i), 2 /* CharacterClass.CannotEndIn */);
}
}
return _classifier;
}
export class LinkComputer {
static _createLink(classifier, line, lineNumber, linkBeginIndex, linkEndIndex) {
// 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 !== 2 /* CharacterClass.CannotEndIn */) {
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 === 40 /* CharCode.OpenParen */ && lastCharCodeInLink === 41 /* CharCode.CloseParen */)
|| (charCodeBeforeLink === 91 /* CharCode.OpenSquareBracket */ && lastCharCodeInLink === 93 /* CharCode.CloseSquareBracket */)
|| (charCodeBeforeLink === 123 /* CharCode.OpenCurlyBrace */ && lastCharCodeInLink === 125 /* CharCode.CloseCurlyBrace */)) {
// 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: {
startLineNumber: lineNumber,
startColumn: linkBeginIndex + 1,
endLineNumber: lineNumber,
endColumn: lastIncludedCharIndex + 2
},
url: line.substring(linkBeginIndex, lastIncludedCharIndex + 1)
};
}
static computeLinks(model, stateMachine = getStateMachine()) {
const classifier = getClassifier();
const result = [];
for (let i = 1, lineCount = model.getLineCount(); i <= lineCount; i++) {
const line = model.getLineContent(i);
const len = line.length;
let j = 0;
let linkBeginIndex = 0;
let linkBeginChCode = 0;
let state = 1 /* State.Start */;
let hasOpenParens = false;
let hasOpenSquareBracket = false;
let inSquareBrackets = false;
let hasOpenCurlyBracket = false;
while (j < len) {
let resetStateMachine = false;
const chCode = line.charCodeAt(j);
if (state === 13 /* State.Accept */) {
let chClass;
switch (chCode) {
case 40 /* CharCode.OpenParen */:
hasOpenParens = true;
chClass = 0 /* CharacterClass.None */;
break;
case 41 /* CharCode.CloseParen */:
chClass = (hasOpenParens ? 0 /* CharacterClass.None */ : 1 /* CharacterClass.ForceTermination */);
break;
case 91 /* CharCode.OpenSquareBracket */:
inSquareBrackets = true;
hasOpenSquareBracket = true;
chClass = 0 /* CharacterClass.None */;
break;
case 93 /* CharCode.CloseSquareBracket */:
inSquareBrackets = false;
chClass = (hasOpenSquareBracket ? 0 /* CharacterClass.None */ : 1 /* CharacterClass.ForceTermination */);
break;
case 123 /* CharCode.OpenCurlyBrace */:
hasOpenCurlyBracket = true;
chClass = 0 /* CharacterClass.None */;
break;
case 125 /* CharCode.CloseCurlyBrace */:
chClass = (hasOpenCurlyBracket ? 0 /* CharacterClass.None */ : 1 /* CharacterClass.ForceTermination */);
break;
// The following three rules make it that ' or " or ` are allowed inside links
// only if the link is wrapped by some other quote character
case 39 /* CharCode.SingleQuote */:
case 34 /* CharCode.DoubleQuote */:
case 96 /* CharCode.BackTick */:
if (linkBeginChCode === chCode) {
chClass = 1 /* CharacterClass.ForceTermination */;
}
else if (linkBeginChCode === 39 /* CharCode.SingleQuote */ || linkBeginChCode === 34 /* CharCode.DoubleQuote */ || linkBeginChCode === 96 /* CharCode.BackTick */) {
chClass = 0 /* CharacterClass.None */;
}
else {
chClass = 1 /* CharacterClass.ForceTermination */;
}
break;
case 42 /* CharCode.Asterisk */:
// `*` terminates a link if the link began with `*`
chClass = (linkBeginChCode === 42 /* CharCode.Asterisk */) ? 1 /* CharacterClass.ForceTermination */ : 0 /* CharacterClass.None */;
break;
case 124 /* CharCode.Pipe */:
// `|` terminates a link if the link began with `|`
chClass = (linkBeginChCode === 124 /* CharCode.Pipe */) ? 1 /* CharacterClass.ForceTermination */ : 0 /* CharacterClass.None */;
break;
case 32 /* CharCode.Space */:
// ` ` allow space in between [ and ]
chClass = (inSquareBrackets ? 0 /* CharacterClass.None */ : 1 /* CharacterClass.ForceTermination */);
break;
default:
chClass = classifier.get(chCode);
}
// Check if character terminates link
if (chClass === 1 /* CharacterClass.ForceTermination */) {
result.push(LinkComputer._createLink(classifier, line, i, linkBeginIndex, j));
resetStateMachine = true;
}
}
else if (state === 12 /* State.End */) {
let chClass;
if (chCode === 91 /* CharCode.OpenSquareBracket */) {
// Allow for the authority part to contain ipv6 addresses which contain [ and ]
hasOpenSquareBracket = true;
chClass = 0 /* CharacterClass.None */;
}
else {
chClass = classifier.get(chCode);
}
// Check if character terminates link
if (chClass === 1 /* CharacterClass.ForceTermination */) {
resetStateMachine = true;
}
else {
state = 13 /* State.Accept */;
}
}
else {
state = stateMachine.nextState(state, chCode);
if (state === 0 /* State.Invalid */) {
resetStateMachine = true;
}
}
if (resetStateMachine) {
state = 1 /* State.Start */;
hasOpenParens = false;
hasOpenSquareBracket = false;
hasOpenCurlyBracket = false;
// Record where the link started
linkBeginIndex = j + 1;
linkBeginChCode = chCode;
}
j++;
}
if (state === 13 /* State.Accept */) {
result.push(LinkComputer._createLink(classifier, line, i, linkBeginIndex, len));
}
}
return result;
}
}
/**
* Returns an array of all links contains in the provided
* document. *Note* that this operation is computational
* expensive and should not run in the UI thread.
*/
export function computeLinks(model) {
if (!model || typeof model.getLineCount !== 'function' || typeof model.getLineContent !== 'function') {
// Unknown caller!
return [];
}
return LinkComputer.computeLinks(model);
}