linkComputer.ts 7.1 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

7
import {ILink} from 'vs/editor/common/modes';
A
Alex Dima 已提交
8
import {CharCode} from 'vs/base/common/charCode';
9
import {CharacterClassifier} from 'vs/editor/common/core/characterClassifier';
E
Erich Gamma 已提交
10 11 12 13 14 15

export interface ILinkComputerTarget {
	getLineCount(): number;
	getLineContent(lineNumber:number): string;
}

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
const enum State {
	Invalid = 0,
	Start = 1,
	H = 2,
	HT = 3,
	HTT = 4,
	HTTP = 5,
	F = 6,
	FI = 7,
	FIL = 8,
	BeforeColon = 9,
	AfterColon = 10,
	AlmostThere = 11,
	End = 12,
	Accept = 13
}

type Edge = [State,number,State];

class StateMachine {

	private _states: State[][];
	private _maxCharCode: number;

	constructor(edges:Edge[]) {
		let maxCharCode = 0;
		let maxState = State.Invalid;
		for (let i = 0, len = edges.length; i < len; i++) {
			let [from, chCode, to] = edges[i];
			if (chCode > maxCharCode) {
				maxCharCode = chCode;
			}
			if (from > maxState) {
				maxState = from;
			}
			if (to > maxState) {
				maxState = to;
			}
		}

		let states:number[][] = [];
		for (let i = 0; i <= maxState; i++) {
			let tmp:number[] = [];
			for (let j = 0; j <= maxCharCode; j++) {
				tmp[j] = State.Invalid;
			}
			states[i] = tmp;
		}

		for (let i = 0, len = edges.length; i < len; i++) {
			let [from, chCode, to] = edges[i];
			states[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[currentState][chCode];
	}
}

A
Alex Dima 已提交
82
// State machine for http:// or https:// or file://
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
let stateMachine = new StateMachine([
	[State.Start, CharCode.h, State.H],
	[State.Start, CharCode.H, State.H],
	[State.Start, CharCode.f, State.F],
	[State.Start, CharCode.F, State.F],

	[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.BeforeColon],
	[State.HTTP, CharCode.S, State.BeforeColon],
	[State.HTTP, CharCode.Colon, State.AfterColon],

	[State.F, CharCode.i, State.FI],
	[State.F, CharCode.I, State.FI],

	[State.FI, CharCode.l, State.FIL],
	[State.FI, CharCode.L, State.FIL],

	[State.FIL, CharCode.e, State.BeforeColon],
	[State.FIL, CharCode.E, State.BeforeColon],

	[State.BeforeColon, CharCode.Colon, State.AfterColon],

	[State.AfterColon, CharCode.Slash, State.AlmostThere],

	[State.AlmostThere, CharCode.Slash, State.End],
]);

const enum CharacterClass {
E
Erich Gamma 已提交
119 120 121 122 123
	None = 0,
	ForceTermination = 1,
	CannotEndIn = 2
}

124 125
const classifier = (function() {
	let result = new CharacterClassifier(CharacterClass.None);
126

127 128 129
	const FORCE_TERMINATION_CHARACTERS = ' \t<>\'\"、。。、,.:;?!@#$%&*‘“〈《「『【〔([{「」}])〕】』」》〉”’`~…';
	for (let i = 0; i < FORCE_TERMINATION_CHARACTERS.length; i++) {
		result.set(FORCE_TERMINATION_CHARACTERS.charCodeAt(i), CharacterClass.ForceTermination);
130 131
	}

132 133 134
	const CANNOT_END_WITH_CHARACTERS = '.,;';
	for (let i = 0; i < CANNOT_END_WITH_CHARACTERS.length; i++) {
		result.set(CANNOT_END_WITH_CHARACTERS.charCodeAt(i), CharacterClass.CannotEndIn);
135 136
	}

137 138
	return result;
})();
E
Erich Gamma 已提交
139

140
class LinkComputer {
141

142
	private static _createLink(line:string, lineNumber:number, linkBeginIndex:number, linkEndIndex:number):ILink {
143 144 145 146
		// Do not allow to end link in certain characters...
		let lastIncludedCharIndex = linkEndIndex - 1;
		do {
			const chCode = line.charCodeAt(lastIncludedCharIndex);
147
			const chClass = classifier.get(chCode);
148 149 150 151 152 153
			if (chClass !== CharacterClass.CannotEndIn) {
				break;
			}
			lastIncludedCharIndex--;
		} while (lastIncludedCharIndex > linkBeginIndex);

E
Erich Gamma 已提交
154 155 156 157 158
		return {
			range: {
				startLineNumber: lineNumber,
				startColumn: linkBeginIndex + 1,
				endLineNumber: lineNumber,
159
				endColumn: lastIncludedCharIndex + 2
E
Erich Gamma 已提交
160
			},
161
			url: line.substring(linkBeginIndex, lastIncludedCharIndex + 1)
E
Erich Gamma 已提交
162 163 164
		};
	}

165
	public static computeLinks(model:ILinkComputerTarget):ILink[] {
A
Alex Dima 已提交
166 167 168 169
		let result:ILink[] = [];
		for (let i = 1, lineCount = model.getLineCount(); i <= lineCount; i++) {
			const line = model.getLineContent(i);
			const len = line.length;
E
Erich Gamma 已提交
170

A
Alex Dima 已提交
171 172
			let j = 0;
			let linkBeginIndex = 0;
173
			let state = State.Start;
A
Alex Dima 已提交
174 175 176
			let hasOpenParens = false;
			let hasOpenSquareBracket = false;
			let hasOpenCurlyBracket = false;
E
Erich Gamma 已提交
177 178 179

			while (j < len) {

A
Alex Dima 已提交
180
				let resetStateMachine = false;
181
				const chCode = line.charCodeAt(j);
182

183
				if (state === State.Accept) {
A
Alex Dima 已提交
184
					let chClass:CharacterClass;
185
					switch (chCode) {
A
Alex Dima 已提交
186
						case CharCode.OpenParen:
187 188 189
							hasOpenParens = true;
							chClass = CharacterClass.None;
							break;
A
Alex Dima 已提交
190
						case CharCode.CloseParen:
191 192
							chClass = (hasOpenParens ? CharacterClass.None : CharacterClass.ForceTermination);
							break;
A
Alex Dima 已提交
193
						case CharCode.OpenSquareBracket:
194 195 196
							hasOpenSquareBracket = true;
							chClass = CharacterClass.None;
							break;
A
Alex Dima 已提交
197
						case CharCode.CloseSquareBracket:
198 199
							chClass = (hasOpenSquareBracket ? CharacterClass.None : CharacterClass.ForceTermination);
							break;
A
Alex Dima 已提交
200
						case CharCode.OpenCurlyBrace:
201 202 203
							hasOpenCurlyBracket = true;
							chClass = CharacterClass.None;
							break;
A
Alex Dima 已提交
204
						case CharCode.CloseCurlyBrace:
205 206 207
							chClass = (hasOpenCurlyBracket ? CharacterClass.None : CharacterClass.ForceTermination);
							break;
						default:
208
							chClass = classifier.get(chCode);
209
					}
E
Erich Gamma 已提交
210 211 212

					// Check if character terminates link
					if (chClass === CharacterClass.ForceTermination) {
213
						result.push(LinkComputer._createLink(line, i, linkBeginIndex, j));
E
Erich Gamma 已提交
214 215
						resetStateMachine = true;
					}
216
				} else if (state === State.End) {
217
					const chClass = classifier.get(chCode);
E
Erich Gamma 已提交
218 219 220 221 222

					// Check if character terminates link
					if (chClass === CharacterClass.ForceTermination) {
						resetStateMachine = true;
					} else {
223
						state = State.Accept;
E
Erich Gamma 已提交
224 225
					}
				} else {
226 227
					state = stateMachine.nextState(state, chCode);
					if (state === State.Invalid) {
E
Erich Gamma 已提交
228 229 230 231 232
						resetStateMachine = true;
					}
				}

				if (resetStateMachine) {
233
					state = State.Start;
234 235 236
					hasOpenParens = false;
					hasOpenSquareBracket = false;
					hasOpenCurlyBracket = false;
E
Erich Gamma 已提交
237 238 239 240 241 242 243 244

					// Record where the link started
					linkBeginIndex = j + 1;
				}

				j++;
			}

245
			if (state === State.Accept) {
E
Erich Gamma 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259
				result.push(LinkComputer._createLink(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.
 */
260
export function computeLinks(model:ILinkComputerTarget):ILink[] {
E
Erich Gamma 已提交
261 262 263 264 265 266
	if (!model || typeof model.getLineCount !== 'function' || typeof model.getLineContent !== 'function') {
		// Unknown caller!
		return [];
	}
	return LinkComputer.computeLinks(model);
}