viewLineRenderer.ts 7.3 KB
Newer Older
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';

J
Johannes Rieken 已提交
7 8
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
import { CharCode } from 'vs/base/common/charCode';
A
Alex Dima 已提交
9 10

export class RenderLineInput {
A
Alex Dima 已提交
11
	_renderLineInputBrand: void;
12 13 14

	lineContent: string;
	tabSize: number;
15
	spaceWidth: number;
16
	stopRenderingLineAfter: number;
17
	renderWhitespace: 'none' | 'boundary' | 'all';
18
	renderControlCharacters: boolean;
19
	parts: ViewLineToken[];
A
Alex Dima 已提交
20 21 22 23

	constructor(
		lineContent: string,
		tabSize: number,
24
		spaceWidth: number,
A
Alex Dima 已提交
25
		stopRenderingLineAfter: number,
26
		renderWhitespace: 'none' | 'boundary' | 'all',
27
		renderControlCharacters: boolean,
28
		parts: ViewLineToken[]
A
Alex Dima 已提交
29 30 31
	) {
		this.lineContent = lineContent;
		this.tabSize = tabSize;
32
		this.spaceWidth = spaceWidth;
A
Alex Dima 已提交
33 34
		this.stopRenderingLineAfter = stopRenderingLineAfter;
		this.renderWhitespace = renderWhitespace;
35
		this.renderControlCharacters = renderControlCharacters;
A
Alex Dima 已提交
36 37
		this.parts = parts;
	}
38 39
}

A
Alex Dima 已提交
40
export class RenderLineOutput {
A
Alex Dima 已提交
41
	_renderLineOutputBrand: void;
42 43
	charOffsetInPart: number[];
	lastRenderedPartIndex: number;
A
Alex Dima 已提交
44
	output: string;
45
	isWhitespaceOnly: boolean;
A
Alex Dima 已提交
46

47
	constructor(charOffsetInPart: number[], lastRenderedPartIndex: number, output: string, isWhitespaceOnly: boolean) {
A
Alex Dima 已提交
48 49 50
		this.charOffsetInPart = charOffsetInPart;
		this.lastRenderedPartIndex = lastRenderedPartIndex;
		this.output = output;
51
		this.isWhitespaceOnly = isWhitespaceOnly;
A
Alex Dima 已提交
52
	}
53 54
}

J
Johannes Rieken 已提交
55
export function renderLine(input: RenderLineInput): RenderLineOutput {
56
	const lineText = input.lineContent;
A
Alex Dima 已提交
57
	const lineTextLength = lineText.length;
58
	const tabSize = input.tabSize;
59
	const spaceWidth = input.spaceWidth;
A
Alex Dima 已提交
60 61
	const actualLineParts = input.parts;
	const renderWhitespace = input.renderWhitespace;
62
	const renderControlCharacters = input.renderControlCharacters;
A
Alex Dima 已提交
63
	const charBreakIndex = (input.stopRenderingLineAfter === -1 ? lineTextLength : input.stopRenderingLineAfter - 1);
64 65

	if (lineTextLength === 0) {
A
Alex Dima 已提交
66 67 68
		return new RenderLineOutput(
			[],
			0,
69
			// This is basically for IE's hit test to work
70 71
			'<span><span>&nbsp;</span></span>',
			true
A
Alex Dima 已提交
72
		);
73 74 75 76 77 78
	}

	if (actualLineParts.length === 0) {
		throw new Error('Cannot render non empty line without line parts!');
	}

79
	return renderLineActual(lineText, lineTextLength, tabSize, spaceWidth, actualLineParts.slice(0), renderWhitespace, renderControlCharacters, charBreakIndex);
A
Alex Dima 已提交
80 81
}

J
Johannes Rieken 已提交
82
function isWhitespace(type: string): boolean {
83 84 85
	return (type.indexOf('whitespace') >= 0);
}

86 87 88
function isControlCharacter(characterCode: number): boolean {
	return characterCode < 32;
}
A
Alex Dima 已提交
89 90

const _controlCharacterSequenceConversionStart = 9216;
91 92 93 94
function controlCharacterToPrintable(characterCode: number): string {
	return String.fromCharCode(_controlCharacterSequenceConversionStart + characterCode);
}

95
function renderLineActual(lineText: string, lineTextLength: number, tabSize: number, spaceWidth: number, actualLineParts: ViewLineToken[], renderWhitespace: 'none' | 'boundary' | 'all', renderControlCharacters: boolean, charBreakIndex: number): RenderLineOutput {
A
Alex Dima 已提交
96 97 98 99
	lineTextLength = +lineTextLength;
	tabSize = +tabSize;
	charBreakIndex = +charBreakIndex;

A
Alex Dima 已提交
100
	let charIndex = 0;
A
Alex Dima 已提交
101
	let out = '';
A
Alex Dima 已提交
102 103
	let charOffsetInPartArr: number[] = [];
	let charOffsetInPart = 0;
104
	let tabsCharDelta = 0;
105
	let isWhitespaceOnly = /^\s*$/.test(lineText);
106

A
Alex Dima 已提交
107
	out += '<span>';
A
Alex Dima 已提交
108 109 110
	for (let partIndex = 0, partIndexLen = actualLineParts.length; partIndex < partIndexLen; partIndex++) {
		let part = actualLineParts[partIndex];

111
		let parsRendersWhitespace = (renderWhitespace !== 'none' && isWhitespace(part.type));
112

A
Alex Dima 已提交
113 114
		let toCharIndex = lineTextLength;
		if (partIndex + 1 < partIndexLen) {
A
Alex Dima 已提交
115 116
			let nextPart = actualLineParts[partIndex + 1];
			toCharIndex = Math.min(lineTextLength, nextPart.startIndex);
117 118
		}

A
Alex Dima 已提交
119
		charOffsetInPart = 0;
120
		if (parsRendersWhitespace) {
121 122 123 124 125 126

			let partContentCnt = 0;
			let partContent = '';
			for (; charIndex < toCharIndex; charIndex++) {
				charOffsetInPartArr[charIndex] = charOffsetInPart;
				let charCode = lineText.charCodeAt(charIndex);
A
Alex Dima 已提交
127

A
Alex Dima 已提交
128
				if (charCode === CharCode.Tab) {
A
Alex Dima 已提交
129 130 131 132
					let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
					tabsCharDelta += insertSpacesCount - 1;
					charOffsetInPart += insertSpacesCount - 1;
					if (insertSpacesCount > 0) {
133
						partContent += '&rarr;';
134
						partContentCnt++;
A
Alex Dima 已提交
135 136 137
						insertSpacesCount--;
					}
					while (insertSpacesCount > 0) {
138 139
						partContent += '&nbsp;';
						partContentCnt++;
A
Alex Dima 已提交
140 141
						insertSpacesCount--;
					}
142
				} else {
A
Alex Dima 已提交
143
					// must be CharCode.Space
144
					partContent += '&middot;';
145 146 147
					partContentCnt++;
				}

J
Johannes Rieken 已提交
148
				charOffsetInPart++;
149 150

				if (charIndex >= charBreakIndex) {
J
Johannes Rieken 已提交
151
					out += '<span class="token ' + part.type + '" style="width:' + (spaceWidth * partContentCnt) + 'px">';
152 153 154
					out += partContent;
					out += '&hellip;</span></span>';
					charOffsetInPartArr[charIndex] = charOffsetInPart;
A
Alex Dima 已提交
155 156 157
					return new RenderLineOutput(
						charOffsetInPartArr,
						partIndex,
158 159
						out,
						isWhitespaceOnly
A
Alex Dima 已提交
160
					);
161
				}
A
Alex Dima 已提交
162
			}
J
Johannes Rieken 已提交
163
			out += '<span class="token ' + part.type + '" style="width:' + (spaceWidth * partContentCnt) + 'px">';
164 165 166 167 168 169 170 171
			out += partContent;
			out += '</span>';
		} else {
			out += '<span class="token ';
			out += part.type;
			out += '">';

			for (; charIndex < toCharIndex; charIndex++) {
A
Alex Dima 已提交
172
				charOffsetInPartArr[charIndex] = charOffsetInPart;
173 174 175
				let charCode = lineText.charCodeAt(charIndex);

				switch (charCode) {
A
Alex Dima 已提交
176
					case CharCode.Tab:
177 178 179 180 181 182 183 184 185
						let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
						tabsCharDelta += insertSpacesCount - 1;
						charOffsetInPart += insertSpacesCount - 1;
						while (insertSpacesCount > 0) {
							out += '&nbsp;';
							insertSpacesCount--;
						}
						break;

A
Alex Dima 已提交
186
					case CharCode.Space:
187 188 189
						out += '&nbsp;';
						break;

A
Alex Dima 已提交
190
					case CharCode.LessThan:
191 192 193
						out += '&lt;';
						break;

A
Alex Dima 已提交
194
					case CharCode.GreaterThan:
195 196 197
						out += '&gt;';
						break;

A
Alex Dima 已提交
198
					case CharCode.Ampersand:
199 200 201
						out += '&amp;';
						break;

A
Alex Dima 已提交
202
					case CharCode.Null:
203 204 205
						out += '&#00;';
						break;

A
Alex Dima 已提交
206 207
					case CharCode.UTF8_BOM:
					case CharCode.LINE_SEPARATOR_2028:
208 209 210
						out += '\ufffd';
						break;

A
Alex Dima 已提交
211
					case CharCode.CarriageReturn:
212 213 214 215 216
						// zero width space, because carriage return would introduce a line break
						out += '&#8203';
						break;

					default:
217 218
						if (renderControlCharacters && isControlCharacter(charCode)) {
							out += controlCharacterToPrintable(charCode);
219 220 221
						} else {
							out += lineText.charAt(charIndex);
						}
222 223
				}

J
Johannes Rieken 已提交
224
				charOffsetInPart++;
225 226 227 228

				if (charIndex >= charBreakIndex) {
					out += '&hellip;</span></span>';
					charOffsetInPartArr[charIndex] = charOffsetInPart;
A
Alex Dima 已提交
229 230 231
					return new RenderLineOutput(
						charOffsetInPartArr,
						partIndex,
232 233
						out,
						isWhitespaceOnly
A
Alex Dima 已提交
234
					);
235
				}
A
Alex Dima 已提交
236
			}
237 238

			out += '</span>';
A
Alex Dima 已提交
239
		}
240

241
	}
A
Alex Dima 已提交
242
	out += '</span>';
243 244 245

	// When getting client rects for the last character, we will position the
	// text range at the end of the span, insteaf of at the beginning of next span
A
Alex Dima 已提交
246
	charOffsetInPartArr.push(charOffsetInPart);
247

A
Alex Dima 已提交
248 249 250
	return new RenderLineOutput(
		charOffsetInPartArr,
		actualLineParts.length - 1,
251 252
		out,
		isWhitespaceOnly
A
Alex Dima 已提交
253
	);
254
}