linkComputer.ts 5.5 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;
}

A
Alex Dima 已提交
16 17 18 19 20 21 22
// State machine for http:// or https:// or file://
const STATE_MAP:{[ch:string]:number}[] = [];
const START_STATE = 1;
const END_STATE = 12;
const ACCEPT_STATE = 13;

STATE_MAP[1] = { 'h': 2, 'H': 2, 'f': 6, 'F': 6 };
J
Johannes Rieken 已提交
23 24 25
STATE_MAP[2] = { 't': 3, 'T': 3 };
STATE_MAP[3] = { 't': 4, 'T': 4 };
STATE_MAP[4] = { 'p': 5, 'P': 5 };
A
Alex Dima 已提交
26 27 28 29 30 31 32
STATE_MAP[5] = { 's': 9, 'S': 9, ':': 10 };
STATE_MAP[6] = { 'i': 7, 'I': 7 };
STATE_MAP[7] = { 'l': 8, 'L': 8 };
STATE_MAP[8] = { 'e': 9, 'E': 9 };
STATE_MAP[9] = { ':': 10 };
STATE_MAP[10] = { '/': 11 };
STATE_MAP[11] = { '/': END_STATE };
E
Erich Gamma 已提交
33 34 35 36 37 38 39

enum CharacterClass {
	None = 0,
	ForceTermination = 1,
	CannotEndIn = 2
}

40 41
const classifier = (function() {
	let result = new CharacterClassifier(CharacterClass.None);
42

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

48 49 50
	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);
51 52
	}

53 54
	return result;
})();
E
Erich Gamma 已提交
55

56
class LinkComputer {
57

58
	private static _createLink(line:string, lineNumber:number, linkBeginIndex:number, linkEndIndex:number):ILink {
59 60 61 62
		// Do not allow to end link in certain characters...
		let lastIncludedCharIndex = linkEndIndex - 1;
		do {
			const chCode = line.charCodeAt(lastIncludedCharIndex);
63
			const chClass = classifier.get(chCode);
64 65 66 67 68 69
			if (chClass !== CharacterClass.CannotEndIn) {
				break;
			}
			lastIncludedCharIndex--;
		} while (lastIncludedCharIndex > linkBeginIndex);

E
Erich Gamma 已提交
70 71 72 73 74
		return {
			range: {
				startLineNumber: lineNumber,
				startColumn: linkBeginIndex + 1,
				endLineNumber: lineNumber,
75
				endColumn: lastIncludedCharIndex + 2
E
Erich Gamma 已提交
76
			},
77
			url: line.substring(linkBeginIndex, lastIncludedCharIndex + 1)
E
Erich Gamma 已提交
78 79 80
		};
	}

81
	public static computeLinks(model:ILinkComputerTarget):ILink[] {
A
Alex Dima 已提交
82 83 84 85
		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 已提交
86

A
Alex Dima 已提交
87 88 89 90 91 92
			let j = 0;
			let linkBeginIndex = 0;
			let state = START_STATE;
			let hasOpenParens = false;
			let hasOpenSquareBracket = false;
			let hasOpenCurlyBracket = false;
E
Erich Gamma 已提交
93 94 95

			while (j < len) {

A
Alex Dima 已提交
96
				let resetStateMachine = false;
97

A
Alex Dima 已提交
98
				if (state === ACCEPT_STATE) {
99
					const chCode = line.charCodeAt(j);
A
Alex Dima 已提交
100
					let chClass:CharacterClass;
101
					switch (chCode) {
A
Alex Dima 已提交
102
						case CharCode.OpenParen:
103 104 105
							hasOpenParens = true;
							chClass = CharacterClass.None;
							break;
A
Alex Dima 已提交
106
						case CharCode.CloseParen:
107 108
							chClass = (hasOpenParens ? CharacterClass.None : CharacterClass.ForceTermination);
							break;
A
Alex Dima 已提交
109
						case CharCode.OpenSquareBracket:
110 111 112
							hasOpenSquareBracket = true;
							chClass = CharacterClass.None;
							break;
A
Alex Dima 已提交
113
						case CharCode.CloseSquareBracket:
114 115
							chClass = (hasOpenSquareBracket ? CharacterClass.None : CharacterClass.ForceTermination);
							break;
A
Alex Dima 已提交
116
						case CharCode.OpenCurlyBrace:
117 118 119
							hasOpenCurlyBracket = true;
							chClass = CharacterClass.None;
							break;
A
Alex Dima 已提交
120
						case CharCode.CloseCurlyBrace:
121 122 123
							chClass = (hasOpenCurlyBracket ? CharacterClass.None : CharacterClass.ForceTermination);
							break;
						default:
124
							chClass = classifier.get(chCode);
125
					}
E
Erich Gamma 已提交
126 127 128

					// Check if character terminates link
					if (chClass === CharacterClass.ForceTermination) {
129
						result.push(LinkComputer._createLink(line, i, linkBeginIndex, j));
E
Erich Gamma 已提交
130 131 132
						resetStateMachine = true;
					}
				} else if (state === END_STATE) {
133
					const chCode = line.charCodeAt(j);
134
					const chClass = classifier.get(chCode);
E
Erich Gamma 已提交
135 136 137 138 139 140 141 142

					// Check if character terminates link
					if (chClass === CharacterClass.ForceTermination) {
						resetStateMachine = true;
					} else {
						state = ACCEPT_STATE;
					}
				} else {
143
					const ch = line.charAt(j);
E
Erich Gamma 已提交
144 145 146 147 148 149 150 151 152
					if (STATE_MAP[state].hasOwnProperty(ch)) {
						state = STATE_MAP[state][ch];
					} else {
						resetStateMachine = true;
					}
				}

				if (resetStateMachine) {
					state = START_STATE;
153 154 155
					hasOpenParens = false;
					hasOpenSquareBracket = false;
					hasOpenCurlyBracket = false;
E
Erich Gamma 已提交
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178

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

				j++;
			}

			if (state === ACCEPT_STATE) {
				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.
 */
179
export function computeLinks(model:ILinkComputerTarget):ILink[] {
E
Erich Gamma 已提交
180 181 182 183 184 185
	if (!model || typeof model.getLineCount !== 'function' || typeof model.getLineContent !== 'function') {
		// Unknown caller!
		return [];
	}
	return LinkComputer.computeLinks(model);
}