viewLineRenderer.ts 7.8 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
import { LineParts } from 'vs/editor/common/core/lineParts';
A
Alex Dima 已提交
10 11

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

	lineContent: string;
	tabSize: number;
16
	spaceWidth: number;
17
	stopRenderingLineAfter: number;
18
	renderWhitespace: 'none' | 'boundary' | 'all';
19
	renderControlCharacters: boolean;
A
Alex Dima 已提交
20
	lineParts: LineParts;
A
Alex Dima 已提交
21 22 23 24

	constructor(
		lineContent: string,
		tabSize: number,
25
		spaceWidth: number,
A
Alex Dima 已提交
26
		stopRenderingLineAfter: number,
27
		renderWhitespace: 'none' | 'boundary' | 'all',
28
		renderControlCharacters: boolean,
A
Alex Dima 已提交
29
		lineParts: LineParts
A
Alex Dima 已提交
30 31 32
	) {
		this.lineContent = lineContent;
		this.tabSize = tabSize;
33
		this.spaceWidth = spaceWidth;
A
Alex Dima 已提交
34 35
		this.stopRenderingLineAfter = stopRenderingLineAfter;
		this.renderWhitespace = renderWhitespace;
36
		this.renderControlCharacters = renderControlCharacters;
A
Alex Dima 已提交
37 38 39 40 41 42 43 44 45 46 47 48 49
		this.lineParts = lineParts;
	}

	public equals(other: RenderLineInput): boolean {
		return (
			this.lineContent === other.lineContent
			&& this.tabSize === other.tabSize
			&& this.spaceWidth === other.spaceWidth
			&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
			&& this.renderWhitespace === other.renderWhitespace
			&& this.renderControlCharacters === other.renderControlCharacters
			&& this.lineParts.equals(other.lineParts)
		);
A
Alex Dima 已提交
50
	}
51 52
}

A
Alex Dima 已提交
53
export class RenderLineOutput {
A
Alex Dima 已提交
54
	_renderLineOutputBrand: void;
A
Alex Dima 已提交
55 56 57 58 59

	readonly charOffsetInPart: number[];
	readonly lastRenderedPartIndex: number;
	readonly output: string;
	readonly isWhitespaceOnly: boolean;
A
Alex Dima 已提交
60

61
	constructor(charOffsetInPart: number[], lastRenderedPartIndex: number, output: string, isWhitespaceOnly: boolean) {
A
Alex Dima 已提交
62 63 64
		this.charOffsetInPart = charOffsetInPart;
		this.lastRenderedPartIndex = lastRenderedPartIndex;
		this.output = output;
65
		this.isWhitespaceOnly = isWhitespaceOnly;
A
Alex Dima 已提交
66
	}
67 68
}

J
Johannes Rieken 已提交
69
export function renderLine(input: RenderLineInput): RenderLineOutput {
70
	const lineText = input.lineContent;
A
Alex Dima 已提交
71
	const lineTextLength = lineText.length;
72
	const tabSize = input.tabSize;
73
	const spaceWidth = input.spaceWidth;
A
Alex Dima 已提交
74
	const actualLineParts = input.lineParts.parts;
A
Alex Dima 已提交
75
	const renderWhitespace = input.renderWhitespace;
76
	const renderControlCharacters = input.renderControlCharacters;
A
Alex Dima 已提交
77
	const charBreakIndex = (input.stopRenderingLineAfter === -1 ? lineTextLength : input.stopRenderingLineAfter - 1);
78 79

	if (lineTextLength === 0) {
A
Alex Dima 已提交
80 81 82
		return new RenderLineOutput(
			[],
			0,
83
			// This is basically for IE's hit test to work
84 85
			'<span><span>&nbsp;</span></span>',
			true
A
Alex Dima 已提交
86
		);
87 88 89 90 91 92
	}

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

A
Alex Dima 已提交
93
	return renderLineActual(lineText, lineTextLength, tabSize, spaceWidth, actualLineParts, renderWhitespace, renderControlCharacters, charBreakIndex);
A
Alex Dima 已提交
94 95
}

J
Johannes Rieken 已提交
96
function isWhitespace(type: string): boolean {
97
	return (type.indexOf('vs-whitespace') >= 0);
98 99
}

100 101 102
function isControlCharacter(characterCode: number): boolean {
	return characterCode < 32;
}
A
Alex Dima 已提交
103 104

const _controlCharacterSequenceConversionStart = 9216;
105 106 107 108
function controlCharacterToPrintable(characterCode: number): string {
	return String.fromCharCode(_controlCharacterSequenceConversionStart + characterCode);
}

109
function renderLineActual(lineText: string, lineTextLength: number, tabSize: number, spaceWidth: number, actualLineParts: ViewLineToken[], renderWhitespace: 'none' | 'boundary' | 'all', renderControlCharacters: boolean, charBreakIndex: number): RenderLineOutput {
A
Alex Dima 已提交
110 111 112 113
	lineTextLength = +lineTextLength;
	tabSize = +tabSize;
	charBreakIndex = +charBreakIndex;

A
Alex Dima 已提交
114
	let charIndex = 0;
A
Alex Dima 已提交
115
	let out = '';
A
Alex Dima 已提交
116 117
	let charOffsetInPartArr: number[] = [];
	let charOffsetInPart = 0;
118
	let tabsCharDelta = 0;
119
	let isWhitespaceOnly = /^\s*$/.test(lineText);
120

A
Alex Dima 已提交
121
	out += '<span>';
A
Alex Dima 已提交
122 123 124
	for (let partIndex = 0, partIndexLen = actualLineParts.length; partIndex < partIndexLen; partIndex++) {
		let part = actualLineParts[partIndex];

125
		let parsRendersWhitespace = (renderWhitespace !== 'none' && isWhitespace(part.type));
126

A
Alex Dima 已提交
127 128
		let toCharIndex = lineTextLength;
		if (partIndex + 1 < partIndexLen) {
A
Alex Dima 已提交
129 130
			let nextPart = actualLineParts[partIndex + 1];
			toCharIndex = Math.min(lineTextLength, nextPart.startIndex);
131 132
		}

A
Alex Dima 已提交
133
		charOffsetInPart = 0;
134
		if (parsRendersWhitespace) {
135 136 137 138 139 140

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

A
Alex Dima 已提交
142
				if (charCode === CharCode.Tab) {
A
Alex Dima 已提交
143 144 145 146
					let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
					tabsCharDelta += insertSpacesCount - 1;
					charOffsetInPart += insertSpacesCount - 1;
					if (insertSpacesCount > 0) {
147
						partContent += '&rarr;';
148
						partContentCnt++;
A
Alex Dima 已提交
149 150 151
						insertSpacesCount--;
					}
					while (insertSpacesCount > 0) {
152 153
						partContent += '&nbsp;';
						partContentCnt++;
A
Alex Dima 已提交
154 155
						insertSpacesCount--;
					}
156
				} else {
A
Alex Dima 已提交
157
					// must be CharCode.Space
158
					partContent += '&middot;';
159 160 161
					partContentCnt++;
				}

J
Johannes Rieken 已提交
162
				charOffsetInPart++;
163 164

				if (charIndex >= charBreakIndex) {
J
Johannes Rieken 已提交
165
					out += '<span class="token ' + part.type + '" style="width:' + (spaceWidth * partContentCnt) + 'px">';
166 167 168
					out += partContent;
					out += '&hellip;</span></span>';
					charOffsetInPartArr[charIndex] = charOffsetInPart;
A
Alex Dima 已提交
169 170 171
					return new RenderLineOutput(
						charOffsetInPartArr,
						partIndex,
172 173
						out,
						isWhitespaceOnly
A
Alex Dima 已提交
174
					);
175
				}
A
Alex Dima 已提交
176
			}
J
Johannes Rieken 已提交
177
			out += '<span class="token ' + part.type + '" style="width:' + (spaceWidth * partContentCnt) + 'px">';
178 179 180 181 182 183 184 185
			out += partContent;
			out += '</span>';
		} else {
			out += '<span class="token ';
			out += part.type;
			out += '">';

			for (; charIndex < toCharIndex; charIndex++) {
A
Alex Dima 已提交
186
				charOffsetInPartArr[charIndex] = charOffsetInPart;
187 188 189
				let charCode = lineText.charCodeAt(charIndex);

				switch (charCode) {
A
Alex Dima 已提交
190
					case CharCode.Tab:
191 192 193 194 195 196 197 198 199
						let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
						tabsCharDelta += insertSpacesCount - 1;
						charOffsetInPart += insertSpacesCount - 1;
						while (insertSpacesCount > 0) {
							out += '&nbsp;';
							insertSpacesCount--;
						}
						break;

A
Alex Dima 已提交
200
					case CharCode.Space:
201 202 203
						out += '&nbsp;';
						break;

A
Alex Dima 已提交
204
					case CharCode.LessThan:
205 206 207
						out += '&lt;';
						break;

A
Alex Dima 已提交
208
					case CharCode.GreaterThan:
209 210 211
						out += '&gt;';
						break;

A
Alex Dima 已提交
212
					case CharCode.Ampersand:
213 214 215
						out += '&amp;';
						break;

A
Alex Dima 已提交
216
					case CharCode.Null:
217 218 219
						out += '&#00;';
						break;

A
Alex Dima 已提交
220 221
					case CharCode.UTF8_BOM:
					case CharCode.LINE_SEPARATOR_2028:
222 223 224
						out += '\ufffd';
						break;

A
Alex Dima 已提交
225
					case CharCode.CarriageReturn:
226 227 228 229 230
						// zero width space, because carriage return would introduce a line break
						out += '&#8203';
						break;

					default:
231 232
						if (renderControlCharacters && isControlCharacter(charCode)) {
							out += controlCharacterToPrintable(charCode);
233 234 235
						} else {
							out += lineText.charAt(charIndex);
						}
236 237
				}

J
Johannes Rieken 已提交
238
				charOffsetInPart++;
239 240 241 242

				if (charIndex >= charBreakIndex) {
					out += '&hellip;</span></span>';
					charOffsetInPartArr[charIndex] = charOffsetInPart;
A
Alex Dima 已提交
243 244 245
					return new RenderLineOutput(
						charOffsetInPartArr,
						partIndex,
246 247
						out,
						isWhitespaceOnly
A
Alex Dima 已提交
248
					);
249
				}
A
Alex Dima 已提交
250
			}
251 252

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

255
	}
A
Alex Dima 已提交
256
	out += '</span>';
257 258 259

	// 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 已提交
260
	charOffsetInPartArr.push(charOffsetInPart);
261

A
Alex Dima 已提交
262 263 264
	return new RenderLineOutput(
		charOffsetInPartArr,
		actualLineParts.length - 1,
265 266
		out,
		isWhitespaceOnly
A
Alex Dima 已提交
267
	);
268
}