viewLineRenderer.ts 23.4 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';

A
Alex Dima 已提交
7
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
J
Johannes Rieken 已提交
8
import { CharCode } from 'vs/base/common/charCode';
A
Alex Dima 已提交
9
import { LineDecoration, LineDecorationsNormalizer } from 'vs/editor/common/viewLayout/lineDecorations';
A
Alex Dima 已提交
10
import * as strings from 'vs/base/common/strings';
11
import { IStringBuilder, createStringBuilder } from 'vs/editor/common/core/stringBuilder';
12
import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel';
A
Alex Dima 已提交
13

A
Alex Dima 已提交
14 15 16 17 18 19
export const enum RenderWhitespace {
	None = 0,
	Boundary = 1,
	All = 2
}

A
Alex Dima 已提交
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
class LinePart {
	_linePartBrand: void;

	/**
	 * last char index of this token (not inclusive).
	 */
	public readonly endIndex: number;
	public readonly type: string;

	constructor(endIndex: number, type: string) {
		this.endIndex = endIndex;
		this.type = type;
	}
}

A
Alex Dima 已提交
35
export class RenderLineInput {
36

37
	public readonly useMonospaceOptimizations: boolean;
38
	public readonly lineContent: string;
39
	public readonly mightContainRTL: boolean;
40
	public readonly fauxIndentLength: number;
A
Alex Dima 已提交
41
	public readonly lineTokens: ViewLineToken[];
A
Alex Dima 已提交
42
	public readonly lineDecorations: LineDecoration[];
43 44 45
	public readonly tabSize: number;
	public readonly spaceWidth: number;
	public readonly stopRenderingLineAfter: number;
A
Alex Dima 已提交
46
	public readonly renderWhitespace: RenderWhitespace;
47
	public readonly renderControlCharacters: boolean;
48
	public readonly fontLigatures: boolean;
A
Alex Dima 已提交
49 50

	constructor(
51
		useMonospaceOptimizations: boolean,
A
Alex Dima 已提交
52
		lineContent: string,
53
		mightContainRTL: boolean,
54
		fauxIndentLength: number,
A
Alex Dima 已提交
55
		lineTokens: ViewLineToken[],
A
Alex Dima 已提交
56
		lineDecorations: LineDecoration[],
A
Alex Dima 已提交
57
		tabSize: number,
58
		spaceWidth: number,
A
Alex Dima 已提交
59
		stopRenderingLineAfter: number,
60
		renderWhitespace: 'none' | 'boundary' | 'all',
61
		renderControlCharacters: boolean,
62
		fontLigatures: boolean
A
Alex Dima 已提交
63
	) {
64
		this.useMonospaceOptimizations = useMonospaceOptimizations;
A
Alex Dima 已提交
65
		this.lineContent = lineContent;
66
		this.mightContainRTL = mightContainRTL;
67
		this.fauxIndentLength = fauxIndentLength;
68 69
		this.lineTokens = lineTokens;
		this.lineDecorations = lineDecorations;
A
Alex Dima 已提交
70
		this.tabSize = tabSize;
71
		this.spaceWidth = spaceWidth;
A
Alex Dima 已提交
72
		this.stopRenderingLineAfter = stopRenderingLineAfter;
A
Alex Dima 已提交
73 74 75 76 77 78 79
		this.renderWhitespace = (
			renderWhitespace === 'all'
				? RenderWhitespace.All
				: renderWhitespace === 'boundary'
					? RenderWhitespace.Boundary
					: RenderWhitespace.None
		);
80
		this.renderControlCharacters = renderControlCharacters;
81
		this.fontLigatures = fontLigatures;
A
Alex Dima 已提交
82 83
	}

A
Alex Dima 已提交
84
	public equals(other: RenderLineInput): boolean {
A
Alex Dima 已提交
85
		return (
86
			this.useMonospaceOptimizations === other.useMonospaceOptimizations
87
			&& this.lineContent === other.lineContent
88
			&& this.mightContainRTL === other.mightContainRTL
89
			&& this.fauxIndentLength === other.fauxIndentLength
A
Alex Dima 已提交
90 91 92 93 94
			&& this.tabSize === other.tabSize
			&& this.spaceWidth === other.spaceWidth
			&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
			&& this.renderWhitespace === other.renderWhitespace
			&& this.renderControlCharacters === other.renderControlCharacters
95
			&& this.fontLigatures === other.fontLigatures
A
Alex Dima 已提交
96
			&& LineDecoration.equalsArr(this.lineDecorations, other.lineDecorations)
A
Alex Dima 已提交
97
			&& ViewLineToken.equalsArr(this.lineTokens, other.lineTokens)
A
Alex Dima 已提交
98
		);
A
Alex Dima 已提交
99
	}
100 101
}

102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
export const enum CharacterMappingConstants {
	PART_INDEX_MASK = 0b11111111111111110000000000000000,
	CHAR_INDEX_MASK = 0b00000000000000001111111111111111,

	CHAR_INDEX_OFFSET = 0,
	PART_INDEX_OFFSET = 16
}

/**
 * Provides a both direction mapping between a line's character and its rendered position.
 */
export class CharacterMapping {

	public static getPartIndex(partData: number): number {
		return (partData & CharacterMappingConstants.PART_INDEX_MASK) >>> CharacterMappingConstants.PART_INDEX_OFFSET;
	}

	public static getCharIndex(partData: number): number {
		return (partData & CharacterMappingConstants.CHAR_INDEX_MASK) >>> CharacterMappingConstants.CHAR_INDEX_OFFSET;
	}

	public readonly length: number;
124 125
	private readonly _data: Uint32Array;
	private readonly _absoluteOffsets: Uint32Array;
126 127

	constructor(length: number, partCount: number) {
128 129
		this.length = length;
		this._data = new Uint32Array(this.length);
130
		this._absoluteOffsets = new Uint32Array(this.length);
131 132
	}

133
	public setPartData(charOffset: number, partIndex: number, charIndex: number, partAbsoluteOffset: number): void {
134 135 136 137 138
		let partData = (
			(partIndex << CharacterMappingConstants.PART_INDEX_OFFSET)
			| (charIndex << CharacterMappingConstants.CHAR_INDEX_OFFSET)
		) >>> 0;
		this._data[charOffset] = partData;
139
		this._absoluteOffsets[charOffset] = partAbsoluteOffset + charIndex;
140 141
	}

142 143
	public getAbsoluteOffsets(): Uint32Array {
		return this._absoluteOffsets;
144 145
	}

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
	public charOffsetToPartData(charOffset: number): number {
		if (this.length === 0) {
			return 0;
		}
		if (charOffset < 0) {
			return this._data[0];
		}
		if (charOffset >= this.length) {
			return this._data[this.length - 1];
		}
		return this._data[charOffset];
	}

	public partDataToCharOffset(partIndex: number, partLength: number, charIndex: number): number {
		if (this.length === 0) {
			return 0;
		}

		let searchEntry = (
			(partIndex << CharacterMappingConstants.PART_INDEX_OFFSET)
			| (charIndex << CharacterMappingConstants.CHAR_INDEX_OFFSET)
		) >>> 0;

		let min = 0;
		let max = this.length - 1;
		while (min + 1 < max) {
			let mid = ((min + max) >>> 1);
			let midEntry = this._data[mid];
			if (midEntry === searchEntry) {
				return mid;
			} else if (midEntry > searchEntry) {
				max = mid;
			} else {
				min = mid;
			}
		}

		if (min === max) {
			return min;
		}

		let minEntry = this._data[min];
		let maxEntry = this._data[max];

		if (minEntry === searchEntry) {
			return min;
		}
		if (maxEntry === searchEntry) {
			return max;
		}

		let minPartIndex = CharacterMapping.getPartIndex(minEntry);
		let minCharIndex = CharacterMapping.getCharIndex(minEntry);

		let maxPartIndex = CharacterMapping.getPartIndex(maxEntry);
		let maxCharIndex: number;

		if (minPartIndex !== maxPartIndex) {
			// sitting between parts
			maxCharIndex = partLength;
		} else {
			maxCharIndex = CharacterMapping.getCharIndex(maxEntry);
		}

		let minEntryDistance = charIndex - minCharIndex;
		let maxEntryDistance = maxCharIndex - charIndex;

		if (minEntryDistance <= maxEntryDistance) {
			return min;
		}
		return max;
	}
}

A
Alex Dima 已提交
220
export class RenderLineOutput {
A
Alex Dima 已提交
221
	_renderLineOutputBrand: void;
A
Alex Dima 已提交
222

223
	readonly characterMapping: CharacterMapping;
224
	readonly containsRTL: boolean;
225
	readonly containsForeignElements: boolean;
A
Alex Dima 已提交
226

227
	constructor(characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean) {
228
		this.characterMapping = characterMapping;
229
		this.containsRTL = containsRTL;
230
		this.containsForeignElements = containsForeignElements;
A
Alex Dima 已提交
231
	}
232 233
}

234
export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): RenderLineOutput {
A
Alex Dima 已提交
235
	if (input.lineContent.length === 0) {
236 237 238 239

		let containsForeignElements = false;

		// This is basically for IE's hit test to work
240
		let content: string = '<span><span>\u00a0</span></span>';
241 242 243 244 245 246

		if (input.lineDecorations.length > 0) {
			// This line is empty, but it contains inline decorations
			let classNames: string[] = [];
			for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
				const lineDecoration = input.lineDecorations[i];
247
				if (lineDecoration.type !== InlineDecorationType.Regular) {
248
					classNames.push(input.lineDecorations[i].className);
249 250 251 252 253
					containsForeignElements = true;
				}
			}

			if (containsForeignElements) {
254
				content = `<span><span class="${classNames.join(' ')}"></span></span>`;
255 256 257
			}
		}

258
		sb.appendASCIIString(content);
A
Alex Dima 已提交
259
		return new RenderLineOutput(
260
			new CharacterMapping(0, 0),
261
			false,
262
			containsForeignElements
A
Alex Dima 已提交
263
		);
264 265
	}

266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
	return _renderLine(resolveRenderLineInput(input), sb);
}

export class RenderLineOutput2 {
	constructor(
		public readonly characterMapping: CharacterMapping,
		public readonly html: string,
		public readonly containsRTL: boolean,
		public readonly containsForeignElements: boolean
	) {
	}
}

export function renderViewLine2(input: RenderLineInput): RenderLineOutput2 {
	let sb = createStringBuilder(10000);
	let out = renderViewLine(input, sb);
	return new RenderLineOutput2(out.characterMapping, sb.build(), out.containsRTL, out.containsForeignElements);
A
Alex Dima 已提交
283 284 285 286
}

class ResolvedRenderLineInput {
	constructor(
287
		public readonly fontIsMonospace: boolean,
A
Alex Dima 已提交
288 289 290
		public readonly lineContent: string,
		public readonly len: number,
		public readonly isOverflowing: boolean,
A
Alex Dima 已提交
291
		public readonly parts: LinePart[],
292
		public readonly containsForeignElements: boolean,
A
Alex Dima 已提交
293
		public readonly tabSize: number,
294
		public readonly containsRTL: boolean,
A
Alex Dima 已提交
295 296 297 298 299
		public readonly spaceWidth: number,
		public readonly renderWhitespace: RenderWhitespace,
		public readonly renderControlCharacters: boolean,
	) {
		//
300
	}
A
Alex Dima 已提交
301
}
302

A
Alex Dima 已提交
303
function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput {
304
	const useMonospaceOptimizations = input.useMonospaceOptimizations;
A
Alex Dima 已提交
305
	const lineContent = input.lineContent;
306

A
Alex Dima 已提交
307 308
	let isOverflowing: boolean;
	let len: number;
A
Alex Dima 已提交
309

A
Alex Dima 已提交
310 311 312 313 314 315 316
	if (input.stopRenderingLineAfter !== -1 && input.stopRenderingLineAfter < lineContent.length) {
		isOverflowing = true;
		len = input.stopRenderingLineAfter;
	} else {
		isOverflowing = false;
		len = lineContent.length;
	}
317

318
	let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
A
Alex Dima 已提交
319
	if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary) {
320
		tokens = _applyRenderWhitespace(lineContent, len, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.renderWhitespace === RenderWhitespace.Boundary);
A
Alex Dima 已提交
321
	}
322
	let containsForeignElements = false;
A
Alex Dima 已提交
323
	if (input.lineDecorations.length > 0) {
324 325
		for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
			const lineDecoration = input.lineDecorations[i];
326
			if (lineDecoration.type !== InlineDecorationType.Regular) {
327 328 329 330
				containsForeignElements = true;
				break;
			}
		}
A
Alex Dima 已提交
331 332
		tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations);
	}
333
	let containsRTL = false;
334
	if (input.mightContainRTL) {
335 336
		containsRTL = strings.containsRTL(lineContent);
	}
337
	if (!containsRTL && !input.fontLigatures) {
338
		tokens = splitLargeTokens(lineContent, tokens);
339 340
	}

A
Alex Dima 已提交
341
	return new ResolvedRenderLineInput(
342
		useMonospaceOptimizations,
A
Alex Dima 已提交
343 344 345 346
		lineContent,
		len,
		isOverflowing,
		tokens,
347
		containsForeignElements,
A
Alex Dima 已提交
348
		input.tabSize,
349
		containsRTL,
A
Alex Dima 已提交
350 351 352 353
		input.spaceWidth,
		input.renderWhitespace,
		input.renderControlCharacters
	);
354 355
}

356 357 358 359
/**
 * In the rendering phase, characters are always looped until token.endIndex.
 * Ensure that all tokens end before `len` and the last one ends precisely at `len`.
 */
360 361 362 363 364 365 366 367
function transformAndRemoveOverflowing(tokens: ViewLineToken[], fauxIndentLength: number, len: number): LinePart[] {
	let result: LinePart[] = [], resultLen = 0;

	// The faux indent part of the line should have no token type
	if (fauxIndentLength > 0) {
		result[resultLen++] = new LinePart(fauxIndentLength, '');
	}

368
	for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) {
A
Alex Dima 已提交
369 370
		const token = tokens[tokenIndex];
		const endIndex = token.endIndex;
371 372 373 374
		if (endIndex <= fauxIndentLength) {
			// The faux indent part of the line should have no token type
			continue;
		}
A
Alex Dima 已提交
375 376
		const type = token.getType();
		if (endIndex >= len) {
377
			result[resultLen++] = new LinePart(len, type);
378 379
			break;
		}
380
		result[resultLen++] = new LinePart(endIndex, type);
381
	}
382

383 384 385
	return result;
}

386 387 388 389 390 391 392 393 394 395 396 397
/**
 * written as a const enum to get value inlining.
 */
const enum Constants {
	LongToken = 50
}

/**
 * See https://github.com/Microsoft/vscode/issues/6885.
 * It appears that having very large spans causes very slow reading of character positions.
 * So here we try to avoid that.
 */
398
function splitLargeTokens(lineContent: string, tokens: LinePart[]): LinePart[] {
399
	let lastTokenEndIndex = 0;
A
Alex Dima 已提交
400
	let result: LinePart[] = [], resultLen = 0;
401 402 403 404 405 406 407 408 409
	for (let i = 0, len = tokens.length; i < len; i++) {
		const token = tokens[i];
		const tokenEndIndex = token.endIndex;
		let diff = (tokenEndIndex - lastTokenEndIndex);
		if (diff > Constants.LongToken) {
			const tokenType = token.type;
			const piecesCount = Math.ceil(diff / Constants.LongToken);
			for (let j = 1; j < piecesCount; j++) {
				let pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken);
410 411 412 413 414
				let lastCharInPiece = lineContent.charCodeAt(pieceEndIndex - 1);
				if (strings.isHighSurrogate(lastCharInPiece)) {
					// Don't cut in the middle of a surrogate pair
					pieceEndIndex--;
				}
A
Alex Dima 已提交
415
				result[resultLen++] = new LinePart(pieceEndIndex, tokenType);
416
			}
A
Alex Dima 已提交
417
			result[resultLen++] = new LinePart(tokenEndIndex, tokenType);
418 419 420 421 422 423 424 425 426 427 428 429 430 431
		} else {
			result[resultLen++] = token;
		}
		lastTokenEndIndex = tokenEndIndex;
	}

	return result;
}

/**
 * Whitespace is rendered by "replacing" tokens with a special-purpose `vs-whitespace` type that is later recognized in the rendering phase.
 * Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (&rarr; or &middot;) do not have the same width as &nbsp;.
 * The rendering phase will generate `style="width:..."` for these tokens.
 */
A
Alex Dima 已提交
432
function _applyRenderWhitespace(lineContent: string, len: number, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, onlyBoundary: boolean): LinePart[] {
A
Alex Dima 已提交
433

A
Alex Dima 已提交
434
	let result: LinePart[] = [], resultLen = 0;
A
Alex Dima 已提交
435 436 437
	let tokenIndex = 0;
	let tokenType = tokens[tokenIndex].type;
	let tokenEndIndex = tokens[tokenIndex].endIndex;
438

A
Alex Dima 已提交
439 440 441 442 443 444 445 446 447
	let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
	let lastNonWhitespaceIndex: number;
	if (firstNonWhitespaceIndex === -1) {
		// The entire line is whitespace
		firstNonWhitespaceIndex = len;
		lastNonWhitespaceIndex = len;
	} else {
		lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
	}
448

A
Alex Dima 已提交
449 450 451 452 453 454 455 456
	let tmpIndent = 0;
	for (let charIndex = 0; charIndex < fauxIndentLength; charIndex++) {
		const chCode = lineContent.charCodeAt(charIndex);
		if (chCode === CharCode.Tab) {
			tmpIndent = tabSize;
		} else {
			tmpIndent++;
		}
457
	}
A
Alex Dima 已提交
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
	tmpIndent = tmpIndent % tabSize;

	let wasInWhitespace = false;
	for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
		const chCode = lineContent.charCodeAt(charIndex);

		let isInWhitespace: boolean;
		if (charIndex < firstNonWhitespaceIndex || charIndex > lastNonWhitespaceIndex) {
			// in leading or trailing whitespace
			isInWhitespace = true;
		} else if (chCode === CharCode.Tab) {
			// a tab character is rendered both in all and boundary cases
			isInWhitespace = true;
		} else if (chCode === CharCode.Space) {
			// hit a space character
			if (onlyBoundary) {
				// rendering only boundary whitespace
				if (wasInWhitespace) {
					isInWhitespace = true;
				} else {
					const nextChCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : CharCode.Null);
					isInWhitespace = (nextChCode === CharCode.Space || nextChCode === CharCode.Tab);
				}
			} else {
				isInWhitespace = true;
			}
		} else {
			isInWhitespace = false;
		}

		if (wasInWhitespace) {
			// was in whitespace token
490
			if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
A
Alex Dima 已提交
491
				// leaving whitespace token or entering a new indent
A
Alex Dima 已提交
492
				result[resultLen++] = new LinePart(charIndex, 'vs-whitespace');
A
Alex Dima 已提交
493 494 495 496 497
				tmpIndent = tmpIndent % tabSize;
			}
		} else {
			// was in regular token
			if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) {
A
Alex Dima 已提交
498
				result[resultLen++] = new LinePart(charIndex, tokenType);
A
Alex Dima 已提交
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
				tmpIndent = tmpIndent % tabSize;
			}
		}

		if (chCode === CharCode.Tab) {
			tmpIndent = tabSize;
		} else {
			tmpIndent++;
		}

		wasInWhitespace = isInWhitespace;

		if (charIndex === tokenEndIndex) {
			tokenIndex++;
			tokenType = tokens[tokenIndex].type;
			tokenEndIndex = tokens[tokenIndex].endIndex;
		}
	}

	if (wasInWhitespace) {
		// was in whitespace token
A
Alex Dima 已提交
520
		result[resultLen++] = new LinePart(len, 'vs-whitespace');
A
Alex Dima 已提交
521 522
	} else {
		// was in regular token
A
Alex Dima 已提交
523
		result[resultLen++] = new LinePart(len, tokenType);
A
Alex Dima 已提交
524 525 526
	}

	return result;
527 528
}

529 530 531 532
/**
 * Inline decorations are "merged" on top of tokens.
 * Special care must be taken when multiple inline decorations are at play and they overlap.
 */
A
Alex Dima 已提交
533 534
function _applyInlineDecorations(lineContent: string, len: number, tokens: LinePart[], _lineDecorations: LineDecoration[]): LinePart[] {
	_lineDecorations.sort(LineDecoration.compare);
535
	const lineDecorations = LineDecorationsNormalizer.normalize(lineContent, _lineDecorations);
A
Alex Dima 已提交
536
	const lineDecorationsLen = lineDecorations.length;
A
Alex Dima 已提交
537

A
Alex Dima 已提交
538
	let lineDecorationIndex = 0;
A
Alex Dima 已提交
539
	let result: LinePart[] = [], resultLen = 0, lastResultEndIndex = 0;
A
Alex Dima 已提交
540 541 542 543
	for (let tokenIndex = 0, len = tokens.length; tokenIndex < len; tokenIndex++) {
		const token = tokens[tokenIndex];
		const tokenEndIndex = token.endIndex;
		const tokenType = token.type;
544

A
Alex Dima 已提交
545 546
		while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) {
			const lineDecoration = lineDecorations[lineDecorationIndex];
547

A
Alex Dima 已提交
548 549
			if (lineDecoration.startOffset > lastResultEndIndex) {
				lastResultEndIndex = lineDecoration.startOffset;
A
Alex Dima 已提交
550
				result[resultLen++] = new LinePart(lastResultEndIndex, tokenType);
A
Alex Dima 已提交
551
			}
A
Alex Dima 已提交
552

553
			if (lineDecoration.endOffset + 1 <= tokenEndIndex) {
554
				// This line decoration ends before this token ends
A
Alex Dima 已提交
555
				lastResultEndIndex = lineDecoration.endOffset + 1;
A
Alex Dima 已提交
556
				result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
A
Alex Dima 已提交
557 558
				lineDecorationIndex++;
			} else {
559 560
				// This line decoration continues on to the next token
				lastResultEndIndex = tokenEndIndex;
A
Alex Dima 已提交
561
				result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
A
Alex Dima 已提交
562 563 564
				break;
			}
		}
565

A
Alex Dima 已提交
566 567
		if (tokenEndIndex > lastResultEndIndex) {
			lastResultEndIndex = tokenEndIndex;
A
Alex Dima 已提交
568
			result[resultLen++] = new LinePart(lastResultEndIndex, tokenType);
569
		}
A
Alex Dima 已提交
570
	}
571

572 573 574 575
	if (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === tokens[tokens.length - 1].endIndex) {
		result[resultLen++] = new LinePart(lastResultEndIndex, lineDecorations[lineDecorationIndex].className);
	}

A
Alex Dima 已提交
576 577 578
	return result;
}

579 580 581 582
/**
 * This function is on purpose not split up into multiple functions to allow runtime type inference (i.e. performance reasons).
 * Notice how all the needed data is fully resolved and passed in (i.e. no other calls).
 */
583
function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): RenderLineOutput {
584
	const fontIsMonospace = input.fontIsMonospace;
585
	const containsForeignElements = input.containsForeignElements;
A
Alex Dima 已提交
586 587 588
	const lineContent = input.lineContent;
	const len = input.len;
	const isOverflowing = input.isOverflowing;
A
Alex Dima 已提交
589
	const parts = input.parts;
A
Alex Dima 已提交
590
	const tabSize = input.tabSize;
591
	const containsRTL = input.containsRTL;
A
Alex Dima 已提交
592 593 594 595
	const spaceWidth = input.spaceWidth;
	const renderWhitespace = input.renderWhitespace;
	const renderControlCharacters = input.renderControlCharacters;

A
Alex Dima 已提交
596
	const characterMapping = new CharacterMapping(len + 1, parts.length);
A
Alex Dima 已提交
597 598 599 600 601

	let charIndex = 0;
	let tabsCharDelta = 0;
	let charOffsetInPart = 0;

602 603 604
	let prevPartContentCnt = 0;
	let partAbsoluteOffset = 0;

605 606
	sb.appendASCIIString('<span>');

A
Alex Dima 已提交
607
	for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) {
608 609
		partAbsoluteOffset += prevPartContentCnt;

A
Alex Dima 已提交
610 611 612 613
		const part = parts[partIndex];
		const partEndIndex = part.endIndex;
		const partType = part.type;
		const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('vs-whitespace') >= 0));
A
Alex Dima 已提交
614
		charOffsetInPart = 0;
A
Alex Dima 已提交
615

616 617 618 619
		sb.appendASCIIString('<span class="');
		sb.appendASCIIString(partType);
		sb.appendASCII(CharCode.DoubleQuote);

A
Alex Dima 已提交
620
		if (partRendersWhitespace) {
621 622

			let partContentCnt = 0;
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
			{
				let _charIndex = charIndex;
				let _tabsCharDelta = tabsCharDelta;

				for (; _charIndex < partEndIndex; _charIndex++) {
					const charCode = lineContent.charCodeAt(_charIndex);

					if (charCode === CharCode.Tab) {
						let insertSpacesCount = tabSize - (_charIndex + _tabsCharDelta) % tabSize;
						_tabsCharDelta += insertSpacesCount - 1;
						partContentCnt += insertSpacesCount;
					} else {
						partContentCnt++;
					}
				}
			}

640 641 642 643 644 645 646
			if (!fontIsMonospace) {
				const partIsOnlyWhitespace = (partType === 'vs-whitespace');
				if (partIsOnlyWhitespace || !containsForeignElements) {
					sb.appendASCIIString(' style="width:');
					sb.appendASCIIString(String(spaceWidth * partContentCnt));
					sb.appendASCIIString('px"');
				}
647 648 649
			}
			sb.appendASCII(CharCode.GreaterThan);

A
Alex Dima 已提交
650
			for (; charIndex < partEndIndex; charIndex++) {
651
				characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
A
Alex Dima 已提交
652
				const charCode = lineContent.charCodeAt(charIndex);
A
Alex Dima 已提交
653

A
Alex Dima 已提交
654
				if (charCode === CharCode.Tab) {
A
Alex Dima 已提交
655 656 657 658
					let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
					tabsCharDelta += insertSpacesCount - 1;
					charOffsetInPart += insertSpacesCount - 1;
					if (insertSpacesCount > 0) {
659
						sb.write1(0x2192); // &rarr;
A
Alex Dima 已提交
660 661 662
						insertSpacesCount--;
					}
					while (insertSpacesCount > 0) {
663
						sb.write1(0xA0); // &nbsp;
A
Alex Dima 已提交
664 665
						insertSpacesCount--;
					}
666
				} else {
A
Alex Dima 已提交
667
					// must be CharCode.Space
668
					sb.write1(0xb7); // &middot;
669 670
				}

J
Johannes Rieken 已提交
671
				charOffsetInPart++;
A
Alex Dima 已提交
672
			}
A
Alex Dima 已提交
673

674
			prevPartContentCnt = partContentCnt;
A
Alex Dima 已提交
675

676
		} else {
677 678

			let partContentCnt = 0;
679 680 681 682 683

			if (containsRTL) {
				sb.appendASCIIString(' dir="ltr"');
			}
			sb.appendASCII(CharCode.GreaterThan);
684

A
Alex Dima 已提交
685
			for (; charIndex < partEndIndex; charIndex++) {
686
				characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
A
Alex Dima 已提交
687
				const charCode = lineContent.charCodeAt(charIndex);
688 689

				switch (charCode) {
A
Alex Dima 已提交
690
					case CharCode.Tab:
691 692 693 694
						let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
						tabsCharDelta += insertSpacesCount - 1;
						charOffsetInPart += insertSpacesCount - 1;
						while (insertSpacesCount > 0) {
695
							sb.write1(0xA0); // &nbsp;
696
							partContentCnt++;
697 698 699 700
							insertSpacesCount--;
						}
						break;

A
Alex Dima 已提交
701
					case CharCode.Space:
702
						sb.write1(0xA0); // &nbsp;
703
						partContentCnt++;
704 705
						break;

A
Alex Dima 已提交
706
					case CharCode.LessThan:
707
						sb.appendASCIIString('&lt;');
708
						partContentCnt++;
709 710
						break;

A
Alex Dima 已提交
711
					case CharCode.GreaterThan:
712
						sb.appendASCIIString('&gt;');
713
						partContentCnt++;
714 715
						break;

A
Alex Dima 已提交
716
					case CharCode.Ampersand:
717
						sb.appendASCIIString('&amp;');
718
						partContentCnt++;
719 720
						break;

A
Alex Dima 已提交
721
					case CharCode.Null:
722
						sb.appendASCIIString('&#00;');
723
						partContentCnt++;
724 725
						break;

A
Alex Dima 已提交
726 727
					case CharCode.UTF8_BOM:
					case CharCode.LINE_SEPARATOR_2028:
728
						sb.write1(0xfffd);
729
						partContentCnt++;
730 731 732
						break;

					default:
A
Alex Dima 已提交
733
						if (renderControlCharacters && charCode < 32) {
734
							sb.write1(9216 + charCode);
735
							partContentCnt++;
736
						} else {
737
							sb.write1(charCode);
738
							partContentCnt++;
739
						}
740 741
				}

J
Johannes Rieken 已提交
742
				charOffsetInPart++;
A
Alex Dima 已提交
743
			}
A
Alex Dima 已提交
744

745
			prevPartContentCnt = partContentCnt;
A
Alex Dima 已提交
746
		}
747 748 749

		sb.appendASCIIString('</span>');

750 751 752 753
	}

	// 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
754
	characterMapping.setPartData(len, parts.length - 1, charOffsetInPart, partAbsoluteOffset);
755

A
Alex Dima 已提交
756
	if (isOverflowing) {
757
		sb.appendASCIIString('<span>&hellip;</span>');
758
	}
A
Alex Dima 已提交
759

760
	sb.appendASCIIString('</span>');
761

762
	return new RenderLineOutput(characterMapping, containsRTL, containsForeignElements);
763
}