src/vs/editor/common/viewLayout/viewLineRenderer.ts

1213 LOC · 1111 covered · 102 uncovered · 240 ranges · 143 concepts · 90 introducers · 72 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- viewLineRenderer.ts ×28
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as nls from '../../../nls.js';
7 > import { CharCode } from '../../../base/common/charCode.js';
8 > import * as strings from '../../../base/common/strings.js';
9 > import { IViewLineTokens } from '../tokens/lineTokens.js';
10 > import { StringBuilder } from '../core/stringBuilder.js';
11 > import { LineDecoration, LineDecorationsNormalizer } from './lineDecorations.js';
12 > import { LinePart, LinePartMetadata } from './linePart.js';
13 > import { OffsetRange } from '../core/ranges/offsetRange.js';
14 > import { InlineDecorationType } from '../viewModel/inlineDecorations.js';
15 > import { TextDirection } from '../model.js';
16 >
17 > export const enum RenderWhitespace {
18 > None = 0,
19 > Boundary = 1,
20 > Selection = 2,
21 > Trailing = 3,
22 > All = 4
23 > }
24 >
25 > export interface IRenderLineInputOptions {
26 > useMonospaceOptimizations: boolean;
27 > canUseHalfwidthRightwardsArrow: boolean;
28 > lineContent: string;
29 > continuesWithWrappedLine: boolean;
30 > isBasicASCII: boolean;
31 > containsRTL: boolean;
32 > fauxIndentLength: number;
33 > lineTokens: IViewLineTokens;
34 > lineDecorations: LineDecoration[];
35 > tabSize: number;
36 > startVisibleColumn: number;
37 > spaceWidth: number;
38 > middotWidth: number;
39 > wsmiddotWidth: number;
40 > stopRenderingLineAfter: number;
41 > renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all';
42 > renderControlCharacters: boolean;
43 > fontLigatures: boolean;
44 > selectionsOnLine: OffsetRange[] | null;
45 > textDirection: TextDirection | null;
46 > verticalScrollbarSize: number;
47 > renderNewLineWhenEmpty: boolean;
48 > }
49 >
50 > export class RenderLineInput {
51 >
52 > public readonly useMonospaceOptimizations: boolean;
53 > public readonly canUseHalfwidthRightwardsArrow: boolean;
54 > public readonly lineContent: string;
55 > public readonly continuesWithWrappedLine: boolean;
56 > public readonly isBasicASCII: boolean;
57 > public readonly containsRTL: boolean;
58 > public readonly fauxIndentLength: number;
59 > public readonly lineTokens: IViewLineTokens;
60 > public readonly lineDecorations: LineDecoration[];
61 > public readonly tabSize: number;
62 > public readonly startVisibleColumn: number;
63 > public readonly spaceWidth: number;
64 > public readonly renderSpaceWidth: number;
65 > public readonly renderSpaceCharCode: number;
66 > public readonly stopRenderingLineAfter: number;
67 > public readonly renderWhitespace: RenderWhitespace;
68 > public readonly renderControlCharacters: boolean;
69 > public readonly fontLigatures: boolean;
70 > public readonly textDirection: TextDirection | null;
71 > public readonly verticalScrollbarSize: number;
72 >
73 > /**
74 > * Defined only when renderWhitespace is 'selection'. Selections are non-overlapping,
75 > * and ordered by position within the line.
76 > */
77 > public readonly selectionsOnLine: OffsetRange[] | null;
78 > /**
79 > * When rendering an empty line, whether to render a new line instead
80 > */
81 > public readonly renderNewLineWhenEmpty: boolean;
82 >
83 > public get isLTR(): boolean {
84 return !this.containsRTL && this.textDirection !== TextDirection.RTL;
85 }
87 > constructor(
88 > useMonospaceOptimizations: boolean,
89 > canUseHalfwidthRightwardsArrow: boolean,
90 > lineContent: string,
91 > continuesWithWrappedLine: boolean,
92 > isBasicASCII: boolean,
93 > containsRTL: boolean,
94 > fauxIndentLength: number,
95 > lineTokens: IViewLineTokens,
96 > lineDecorations: LineDecoration[],
97 > tabSize: number,
98 > startVisibleColumn: number,
99 > spaceWidth: number,
100 > middotWidth: number,
101 > wsmiddotWidth: number,
102 > stopRenderingLineAfter: number,
103 > renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all',
104 > renderControlCharacters: boolean,
105 > fontLigatures: boolean,
106 > selectionsOnLine: OffsetRange[] | null,
107 > textDirection: TextDirection | null,
108 > verticalScrollbarSize: number,
109 > renderNewLineWhenEmpty: boolean = false,
110 > ) {
111 > this.useMonospaceOptimizations = useMonospaceOptimizations;
112 > this.canUseHalfwidthRightwardsArrow = canUseHalfwidthRightwardsArrow;
113 > this.lineContent = lineContent;
114 > this.continuesWithWrappedLine = continuesWithWrappedLine;
115 > this.isBasicASCII = isBasicASCII;
116 > this.containsRTL = containsRTL;
117 > this.fauxIndentLength = fauxIndentLength;
118 > this.lineTokens = lineTokens;
119 > this.lineDecorations = lineDecorations.sort(LineDecoration.compare);
120 > this.tabSize = tabSize;
121 > this.startVisibleColumn = startVisibleColumn;
122 > this.spaceWidth = spaceWidth;
123 > this.stopRenderingLineAfter = stopRenderingLineAfter;
124 > this.renderWhitespace = (
125 > renderWhitespace === 'all'
126 > ? RenderWhitespace.All viewLineRenderer.ts ×1
127 > : renderWhitespace === 'boundary' viewLineRenderer.ts ×1
128 > ? RenderWhitespace.Boundary viewLineRenderer.ts ×3
129 > : renderWhitespace === 'selection' viewLineRenderer.ts ×1
130 > ? RenderWhitespace.Selection viewLineRenderer.ts ×1
131 > : renderWhitespace === 'trailing' viewLineRenderer.ts ×1
132 > ? RenderWhitespace.Trailing viewLineRenderer.ts ×2
133 > : RenderWhitespace.None viewLineRenderer.ts ×1
135 > this.renderControlCharacters = renderControlCharacters;
136 > this.fontLigatures = fontLigatures;
137 > this.selectionsOnLine = selectionsOnLine && selectionsOnLine.sort((a, b) => a.start < b.start ? -1 : 1);
138 > this.renderNewLineWhenEmpty = renderNewLineWhenEmpty;
139 > this.textDirection = textDirection;
140 > this.verticalScrollbarSize = verticalScrollbarSize;
141 >
142 > const wsmiddotDiff = Math.abs(wsmiddotWidth - spaceWidth);
143 > const middotDiff = Math.abs(middotWidth - spaceWidth);
144 > if (wsmiddotDiff < middotDiff) {
145 this.renderSpaceWidth = wsmiddotWidth;
146 this.renderSpaceCharCode = 0x2E31; // U+2E31 - WORD SEPARATOR MIDDLE DOT
147 > } else { viewLineRenderer.ts ×28
148 > this.renderSpaceWidth = middotWidth;
149 > this.renderSpaceCharCode = 0xB7; // U+00B7 - MIDDLE DOT
150 > }
151 > }
152 >
153 > private sameSelection(otherSelections: OffsetRange[] | null): boolean {
154 if (this.selectionsOnLine === null) {
155 return otherSelections === null;
156 }
157
158 if (otherSelections === null) {
159 return false;
160 }
161
162 if (otherSelections.length !== this.selectionsOnLine.length) {
163 return false;
164 }
165
166 for (let i = 0; i < this.selectionsOnLine.length; i++) {
167 if (!this.selectionsOnLine[i].equals(otherSelections[i])) {
168 return false;
169 }
170 }
171
172 return true;
173 }
175 > public equals(other: RenderLineInput): boolean {
176 return (
177 this.useMonospaceOptimizations === other.useMonospaceOptimizations
178 && this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
179 && this.lineContent === other.lineContent
180 && this.continuesWithWrappedLine === other.continuesWithWrappedLine
181 && this.isBasicASCII === other.isBasicASCII
182 && this.containsRTL === other.containsRTL
183 && this.fauxIndentLength === other.fauxIndentLength
184 && this.tabSize === other.tabSize
185 && this.startVisibleColumn === other.startVisibleColumn
186 && this.spaceWidth === other.spaceWidth
187 && this.renderSpaceWidth === other.renderSpaceWidth
188 && this.renderSpaceCharCode === other.renderSpaceCharCode
189 && this.stopRenderingLineAfter === other.stopRenderingLineAfter
190 && this.renderWhitespace === other.renderWhitespace
191 && this.renderControlCharacters === other.renderControlCharacters
192 && this.fontLigatures === other.fontLigatures
193 && LineDecoration.equalsArr(this.lineDecorations, other.lineDecorations)
194 && this.lineTokens.equals(other.lineTokens)
195 && this.sameSelection(other.selectionsOnLine)
196 && this.textDirection === other.textDirection
197 && this.verticalScrollbarSize === other.verticalScrollbarSize
198 && this.renderNewLineWhenEmpty === other.renderNewLineWhenEmpty
199 );
200 }
202 >
203 > const enum CharacterMappingConstants {
204 > PART_INDEX_MASK = 0b11111111111111110000000000000000,
205 > CHAR_INDEX_MASK = 0b00000000000000001111111111111111,
206 >
207 > CHAR_INDEX_OFFSET = 0,
208 > PART_INDEX_OFFSET = 16
209 > }
210 >
211 > export class DomPosition {
212 > constructor(
213 > public readonly partIndex: number, viewLineRenderer.ts ×7
214 > public readonly charIndex: number
215 > ) { }
217 >
218 > /**
219 > * Provides a both direction mapping between a line's character and its rendered position.
220 > */
221 > export class CharacterMapping {
222 >
223 > private static getPartIndex(partData: number): number {
224 > return (partData & CharacterMappingConstants.PART_INDEX_MASK) >>> CharacterMappingConstants.PART_INDEX_OFFSET; viewLineRenderer.ts ×2
225 > }
227 > private static getCharIndex(partData: number): number {
228 > return (partData & CharacterMappingConstants.CHAR_INDEX_MASK) >>> CharacterMappingConstants.CHAR_INDEX_OFFSET; viewLineRenderer.ts ×2
229 > }
231 > public readonly length: number;
232 > private readonly _data: Uint32Array;
233 > private readonly _horizontalOffset: Uint32Array;
234 >
235 > constructor(length: number, partCount: number) {
236 > this.length = length;
237 > this._data = new Uint32Array(this.length);
238 > this._horizontalOffset = new Uint32Array(this.length);
239 > }
240 >
241 > public setColumnInfo(column: number, partIndex: number, charIndex: number, horizontalOffset: number): void {
242 > const partData = ( viewLineRenderer.ts ×1
243 > (partIndex << CharacterMappingConstants.PART_INDEX_OFFSET)
244 > | (charIndex << CharacterMappingConstants.CHAR_INDEX_OFFSET)
245 > ) >>> 0;
246 > this._data[column - 1] = partData;
247 > this._horizontalOffset[column - 1] = horizontalOffset;
248 > }
250 > public getHorizontalOffset(column: number): number {
251 > if (this._horizontalOffset.length === 0) { viewLineRenderer.ts ×7
252 // No characters on this line
253 return 0;
254 }
255 > return this._horizontalOffset[column - 1]; viewLineRenderer.ts ×7
256 > }
258 > private charOffsetToPartData(charOffset: number): number {
259 > if (this.length === 0) { viewLineRenderer.ts ×7
260 return 0;
261 }
262 > if (charOffset < 0) { viewLineRenderer.ts ×7
263 return this._data[0];
264 }
265 > if (charOffset >= this.length) { viewLineRenderer.ts ×7
266 return this._data[this.length - 1];
267 }
268 > return this._data[charOffset]; viewLineRenderer.ts ×7
269 > }
271 > public getDomPosition(column: number): DomPosition {
272 > const partData = this.charOffsetToPartData(column - 1); viewLineRenderer.ts ×7
273 > const partIndex = CharacterMapping.getPartIndex(partData);
274 > const charIndex = CharacterMapping.getCharIndex(partData);
275 > return new DomPosition(partIndex, charIndex);
276 > }
278 > public getColumn(domPosition: DomPosition, partLength: number): number {
279 > const charOffset = this.partDataToCharOffset(domPosition.partIndex, partLength, domPosition.charIndex); viewLineRenderer.ts ×7
280 > return charOffset + 1;
281 > }
283 > private partDataToCharOffset(partIndex: number, partLength: number, charIndex: number): number {
284 > if (this.length === 0) { viewLineRenderer.ts ×7
285 return 0;
286 }
288 > const searchEntry = (
289 > (partIndex << CharacterMappingConstants.PART_INDEX_OFFSET)
290 > | (charIndex << CharacterMappingConstants.CHAR_INDEX_OFFSET)
291 > ) >>> 0;
292 >
293 > let min = 0;
294 > let max = this.length - 1;
295 > while (min + 1 < max) {
296 > const mid = ((min + max) >>> 1); viewLineRenderer.ts ×1
297 > const midEntry = this._data[mid];
298 > if (midEntry === searchEntry) {
299 > return mid;
300 > } else if (midEntry > searchEntry) {
301 > max = mid;
302 > } else {
303 > min = mid;
304 > }
305 > }
307 > if (min === max) {
308 return min;
309 }
311 > const minEntry = this._data[min];
312 > const maxEntry = this._data[max];
313 >
314 > if (minEntry === searchEntry) {
315 > return min;
316 > }
317 > if (maxEntry === searchEntry) {
318 > return max;
319 > }
321 > const minPartIndex = CharacterMapping.getPartIndex(minEntry);
322 > const minCharIndex = CharacterMapping.getCharIndex(minEntry);
323 >
324 > const maxPartIndex = CharacterMapping.getPartIndex(maxEntry);
325 > let maxCharIndex: number;
326 >
327 > if (minPartIndex !== maxPartIndex) {
328 > // sitting between parts viewLineRenderer.ts ×1
329 > maxCharIndex = partLength;
330 > } else { viewLineRenderer.ts ×4
331 > maxCharIndex = CharacterMapping.getCharIndex(maxEntry); viewLineRenderer.ts ×1
332 > }
334 > const minEntryDistance = charIndex - minCharIndex;
335 > const maxEntryDistance = maxCharIndex - charIndex;
336 >
337 > if (minEntryDistance <= maxEntryDistance) {
338 > return min; viewLineRenderer.ts ×1
339 > }
340 > return max; viewLineRenderer.ts ×4
343 > public inflate() {
344 > const result: [number, number, number][] = []; viewLineRenderer.ts ×1
345 > for (let i = 0; i < this.length; i++) {
346 > const partData = this._data[i];
347 > const partIndex = CharacterMapping.getPartIndex(partData);
348 > const charIndex = CharacterMapping.getCharIndex(partData);
349 > const visibleColumn = this._horizontalOffset[i];
350 > result.push([partIndex, charIndex, visibleColumn]);
351 > }
352 > return result;
353 > }
355 >
356 > export const enum ForeignElementType {
357 > None = 0,
358 > Before = 1,
359 > After = 2
360 > }
361 >
362 > export class RenderLineOutput {
363 > _renderLineOutputBrand: void = undefined;
364 >
365 > readonly characterMapping: CharacterMapping;
366 > readonly containsForeignElements: ForeignElementType;
367 >
368 > constructor(characterMapping: CharacterMapping, containsForeignElements: ForeignElementType) {
369 > this.characterMapping = characterMapping;
370 > this.containsForeignElements = containsForeignElements;
371 > }
372 > }
373 >
374 > export function renderViewLine(input: RenderLineInput, sb: StringBuilder): RenderLineOutput {
375 > if (input.lineContent.length === 0) {
377 > if (input.lineDecorations.length > 0) {
378 > // This line is empty, but it contains inline decorations viewLineRenderer.ts ×2
379 > sb.appendString(`<span>`);
380 >
381 > let beforeCount = 0;
382 > let afterCount = 0;
383 > let containsForeignElements = ForeignElementType.None;
384 > for (const lineDecoration of input.lineDecorations) {
385 > if (lineDecoration.type === InlineDecorationType.Before || lineDecoration.type === InlineDecorationType.After) {
386 > sb.appendString(`<span class="`);
387 > sb.appendString(lineDecoration.className);
388 > sb.appendString(`"></span>`);
389 >
390 > if (lineDecoration.type === InlineDecorationType.Before) {
391 > containsForeignElements |= ForeignElementType.Before;
392 > beforeCount++;
393 > }
394 > if (lineDecoration.type === InlineDecorationType.After) {
395 > containsForeignElements |= ForeignElementType.After; viewLineRenderer.ts ×1
396 > afterCount++;
397 > }
399 > }
400 >
401 > sb.appendString(`</span>`);
402 >
403 > const characterMapping = new CharacterMapping(1, beforeCount + afterCount);
404 > characterMapping.setColumnInfo(1, beforeCount, 0, 0);
405 >
406 > return new RenderLineOutput(
407 > characterMapping,
408 > containsForeignElements
409 > );
410 > }
412 > // completely empty line
413 > if (input.renderNewLineWhenEmpty) {
414 sb.appendString('<span><span>\n</span></span>');
415 > } else { viewLineRenderer.ts ×2
416 > sb.appendString('<span><span></span></span>');
417 > }
418 > return new RenderLineOutput(
419 > new CharacterMapping(0, 0),
420 > ForeignElementType.None
421 > );
422 > }
424 > return _renderLine(resolveRenderLineInput(input), sb);
425 > }
427 > export class RenderLineOutput2 {
428 > constructor(
429 > public readonly characterMapping: CharacterMapping,
430 > public readonly html: string,
431 > public readonly containsForeignElements: ForeignElementType
432 > ) {
433 > }
434 > }
435 >
436 > export function renderViewLine2(input: RenderLineInput): RenderLineOutput2 {
437 > const sb = new StringBuilder(10000);
438 > const out = renderViewLine(input, sb);
439 > return new RenderLineOutput2(out.characterMapping, sb.build(), out.containsForeignElements);
440 > }
441 >
442 > class ResolvedRenderLineInput {
443 > constructor(
444 > public readonly fontIsMonospace: boolean, viewLineRenderer.ts ×22
445 > public readonly canUseHalfwidthRightwardsArrow: boolean,
446 > public readonly lineContent: string,
447 > public readonly len: number,
448 > public readonly isOverflowing: boolean,
449 > public readonly overflowingCharCount: number,
450 > public readonly parts: LinePart[],
451 > public readonly containsForeignElements: ForeignElementType,
452 > public readonly fauxIndentLength: number,
453 > public readonly tabSize: number,
454 > public readonly startVisibleColumn: number,
455 > public readonly spaceWidth: number,
456 > public readonly renderSpaceCharCode: number,
457 > public readonly renderWhitespace: RenderWhitespace,
458 > public readonly renderControlCharacters: boolean,
459 > ) {
460 > //
461 > }
463 >
464 > function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput { viewLineRenderer.ts ×22
465 > const lineContent = input.lineContent;
466 >
467 > let isOverflowing: boolean;
468 > let overflowingCharCount: number;
469 > let len: number;
470 >
471 > if (input.stopRenderingLineAfter !== -1 && input.stopRenderingLineAfter < lineContent.length) {
472 > isOverflowing = true; viewLineRenderer.ts ×3
473 > overflowingCharCount = lineContent.length - input.stopRenderingLineAfter;
474 > len = input.stopRenderingLineAfter;
475 > } else { viewLineRenderer.ts ×22
476 > isOverflowing = false; viewLineRenderer.ts ×1
477 > overflowingCharCount = 0;
478 > len = lineContent.length;
479 > }
481 > let tokens = transformAndRemoveOverflowing(lineContent, input.containsRTL, input.lineTokens, input.fauxIndentLength, len);
482 > if (input.renderControlCharacters && !input.isBasicASCII) {
483 > // Calling `extractControlCharacters` before adding (possibly empty) line parts viewLineRenderer.ts ×3
484 > // for inline decorations. `extractControlCharacters` removes empty line parts.
485 > tokens = extractControlCharacters(lineContent, tokens);
486 > }
487 > if (input.renderWhitespace === RenderWhitespace.All || viewLineRenderer.ts ×22
488 > input.renderWhitespace === RenderWhitespace.Boundary || viewLineRenderer.ts ×1
489 > (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine) || viewLineRenderer.ts ×1
490 > (input.renderWhitespace === RenderWhitespace.Trailing && !input.continuesWithWrappedLine) viewLineRenderer.ts ×1
492 > tokens = _applyRenderWhitespace(input, lineContent, len, tokens); viewLineRenderer.ts ×18
493 > }
494 > let containsForeignElements = ForeignElementType.None; viewLineRenderer.ts ×22
495 > if (input.lineDecorations.length > 0) {
496 > for (let i = 0, len = input.lineDecorations.length; i < len; i++) { viewLineRenderer.ts ×8
497 > const lineDecoration = input.lineDecorations[i];
498 > if (lineDecoration.type === InlineDecorationType.RegularAffectingLetterSpacing) {
499 // Pretend there are foreign elements... although not 100% accurate.
500 containsForeignElements |= ForeignElementType.Before;
501 > } else if (lineDecoration.type === InlineDecorationType.Before) { viewLineRenderer.ts ×8
502 > containsForeignElements |= ForeignElementType.Before; lineDecorations.ts ×1
503 > } else if (lineDecoration.type === InlineDecorationType.After) { viewLineRenderer.ts ×8
504 > containsForeignElements |= ForeignElementType.After; lineDecorations.ts ×1
505 > }
507 > tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations);
508 > }
509 > if (!input.containsRTL) { viewLineRenderer.ts ×22
510 > // We can never split RTL text, as it ruins the rendering viewLineRenderer.ts ×4
511 > tokens = splitLargeTokens(lineContent, tokens, !input.isBasicASCII || input.fontLigatures);
512 > } else { viewLineRenderer.ts ×22
513 > // Split the first token if it contains both leading whitespace and RTL text viewLineRenderer.ts ×4
514 > tokens = splitLeadingWhitespaceFromRTL(lineContent, tokens);
515 > }
517 > return new ResolvedRenderLineInput(
518 > input.useMonospaceOptimizations,
519 > input.canUseHalfwidthRightwardsArrow,
520 > lineContent,
521 > len,
522 > isOverflowing,
523 > overflowingCharCount,
524 > tokens,
525 > containsForeignElements,
526 > input.fauxIndentLength,
527 > input.tabSize,
528 > input.startVisibleColumn,
529 > input.spaceWidth,
530 > input.renderSpaceCharCode,
531 > input.renderWhitespace,
532 > input.renderControlCharacters
533 > );
534 > }
536 > /**
537 > * In the rendering phase, characters are always looped until token.endIndex.
538 > * Ensure that all tokens end before `len` and the last one ends precisely at `len`.
539 > */
540 > function transformAndRemoveOverflowing(lineContent: string, lineContainsRTL: boolean, tokens: IViewLineTokens, fauxIndentLength: number, len: number): LinePart[] { viewLineRenderer.ts ×22
541 > const result: LinePart[] = [];
542 > let resultLen = 0;
543 >
544 > // The faux indent part of the line should have no token type
545 > if (fauxIndentLength > 0) {
546 > result[resultLen++] = new LinePart(fauxIndentLength, '', 0, false); viewLineRenderer.ts ×1
547 > }
548 > let startOffset = fauxIndentLength; viewLineRenderer.ts ×22
549 > for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) {
550 > const endIndex = tokens.getEndOffset(tokenIndex);
551 > if (endIndex <= fauxIndentLength) {
552 // The faux indent part of the line should have no token type
553 continue;
554 }
555 > const type = tokens.getClassName(tokenIndex); viewLineRenderer.ts ×22
556 > if (endIndex >= len) {
557 > const tokenContainsRTL = (lineContainsRTL ? strings.containsRTL(lineContent.substring(startOffset, len)) : false);
558 > result[resultLen++] = new LinePart(len, type, 0, tokenContainsRTL);
559 > break;
560 > }
561 > const tokenContainsRTL = (lineContainsRTL ? strings.containsRTL(lineContent.substring(startOffset, endIndex)) : false);
562 > result[resultLen++] = new LinePart(endIndex, type, 0, tokenContainsRTL);
563 > startOffset = endIndex;
564 > }
565 >
566 > return result;
567 > }
569 > /**
570 > * written as a const enum to get value inlining.
571 > */
572 > const enum Constants {
573 > LongToken = 50
574 > }
575 >
576 > /**
577 > * See https://github.com/microsoft/vscode/issues/6885.
578 > * It appears that having very large spans causes very slow reading of character positions.
579 > * So here we try to avoid that.
580 > */
581 > function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: boolean): LinePart[] { viewLineRenderer.ts ×4
582 > let lastTokenEndIndex = 0;
583 > const result: LinePart[] = [];
584 > let resultLen = 0;
585 >
586 > if (onlyAtSpaces) {
587 > // Split only at spaces => we need to walk each character viewLineRenderer.ts ×3
588 > for (let i = 0, len = tokens.length; i < len; i++) {
589 > const token = tokens[i];
590 > const tokenEndIndex = token.endIndex;
591 > if (lastTokenEndIndex + Constants.LongToken < tokenEndIndex) {
592 > const tokenType = token.type; viewLineRenderer.ts ×3
593 > const tokenMetadata = token.metadata;
594 > const tokenContainsRTL = token.containsRTL;
595 >
596 > let lastSpaceOffset = -1;
597 > let currTokenStart = lastTokenEndIndex;
598 > for (let j = lastTokenEndIndex; j < tokenEndIndex; j++) {
599 > if (lineContent.charCodeAt(j) === CharCode.Space) {
600 > lastSpaceOffset = j; viewLineRenderer.ts ×2
601 > }
602 > if (lastSpaceOffset !== -1 && j - currTokenStart >= Constants.LongToken) { viewLineRenderer.ts ×3
603 > // Split at `lastSpaceOffset` + 1 viewLineRenderer.ts ×2
604 > result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType, tokenMetadata, tokenContainsRTL);
605 > currTokenStart = lastSpaceOffset + 1;
606 > lastSpaceOffset = -1;
607 > }
609 > if (currTokenStart !== tokenEndIndex) {
610 > result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
611 > }
612 > } else { viewLineRenderer.ts ×3
613 > result[resultLen++] = token; viewLineRenderer.ts ×1
614 > }
616 > lastTokenEndIndex = tokenEndIndex;
617 > }
618 > } else { viewLineRenderer.ts ×4
619 > // Split anywhere => we don't need to walk each character viewLineRenderer.ts ×2
620 > for (let i = 0, len = tokens.length; i < len; i++) {
621 > const token = tokens[i];
622 > const tokenEndIndex = token.endIndex;
623 > const diff = (tokenEndIndex - lastTokenEndIndex);
624 > if (diff > Constants.LongToken) {
625 > const tokenType = token.type; viewLineRenderer.ts ×1
626 > const tokenMetadata = token.metadata;
627 > const tokenContainsRTL = token.containsRTL;
628 > const piecesCount = Math.ceil(diff / Constants.LongToken);
629 > for (let j = 1; j < piecesCount; j++) {
630 > const pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken);
631 > result[resultLen++] = new LinePart(pieceEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
632 > }
633 > result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
634 > } else { viewLineRenderer.ts ×2
635 > result[resultLen++] = token;
636 > }
637 > lastTokenEndIndex = tokenEndIndex;
638 > }
639 > }
641 > return result;
642 > }
644 > /**
645 > * Splits leading whitespace from the first token if it contains RTL text.
646 > */
647 > function splitLeadingWhitespaceFromRTL(lineContent: string, tokens: LinePart[]): LinePart[] { viewLineRenderer.ts ×4
648 > if (tokens.length === 0) {
649 return tokens;
650 }
652 > const firstToken = tokens[0];
653 > if (!firstToken.containsRTL) {
654 > return tokens; viewLineRenderer.ts ×1
655 > }
657 > // Check if the first token starts with whitespace
658 > const firstTokenEndIndex = firstToken.endIndex;
659 > let firstNonWhitespaceIndex = 0;
660 > for (let i = 0; i < firstTokenEndIndex; i++) {
661 > const charCode = lineContent.charCodeAt(i);
662 > if (charCode !== CharCode.Space && charCode !== CharCode.Tab) {
663 > firstNonWhitespaceIndex = i;
664 > break;
665 > }
666 > }
667 >
668 > if (firstNonWhitespaceIndex === 0) {
669 > // No leading whitespace
670 > return tokens;
671 > }
672
673 // Split the first token into leading whitespace and the rest
674 const result: LinePart[] = [];
675 result.push(new LinePart(firstNonWhitespaceIndex, firstToken.type, firstToken.metadata, false));
676 result.push(new LinePart(firstTokenEndIndex, firstToken.type, firstToken.metadata, firstToken.containsRTL));
677
678 // Add remaining tokens
679 for (let i = 1; i < tokens.length; i++) {
680 result.push(tokens[i]);
681 }
682
683 return result;
684 }
686 > function isControlCharacter(charCode: number): boolean { viewLineRenderer.ts ×4
687 > if (charCode < 32) {
688 return (charCode !== CharCode.Tab);
689 }
690 > if (charCode === 127) { viewLineRenderer.ts ×4
691 // DEL
692 return true;
693 }
695 > if (
696 > (charCode >= 0x202A && charCode <= 0x202E)
697 > || (charCode >= 0x2066 && charCode <= 0x2069)
698 > || (charCode >= 0x200E && charCode <= 0x200F)
699 > || charCode === 0x061C
700 > ) {
701 > // Unicode Directional Formatting Characters viewLineRenderer.ts ×4
702 > // LRE U+202A LEFT-TO-RIGHT EMBEDDING
703 > // RLE U+202B RIGHT-TO-LEFT EMBEDDING
704 > // PDF U+202C POP DIRECTIONAL FORMATTING
705 > // LRO U+202D LEFT-TO-RIGHT OVERRIDE
706 > // RLO U+202E RIGHT-TO-LEFT OVERRIDE
707 > // LRI U+2066 LEFT-TO-RIGHT ISOLATE
708 > // RLI U+2067 RIGHT-TO-LEFT ISOLATE
709 > // FSI U+2068 FIRST STRONG ISOLATE
710 > // PDI U+2069 POP DIRECTIONAL ISOLATE
711 > // LRM U+200E LEFT-TO-RIGHT MARK
712 > // RLM U+200F RIGHT-TO-LEFT MARK
713 > // ALM U+061C ARABIC LETTER MARK
714 > return true;
715 > }
717 > return false;
718 > }
720 > function extractControlCharacters(lineContent: string, tokens: LinePart[]): LinePart[] { viewLineRenderer.ts ×3
721 > const result: LinePart[] = [];
722 > let lastLinePart: LinePart = new LinePart(0, '', 0, false);
723 > let charOffset = 0;
724 > for (const token of tokens) {
725 > const tokenEndIndex = token.endIndex;
726 > for (; charOffset < tokenEndIndex; charOffset++) {
727 > const charCode = lineContent.charCodeAt(charOffset);
728 > if (isControlCharacter(charCode)) {
729 > if (charOffset > lastLinePart.endIndex) { viewLineRenderer.ts ×4
730 > // emit previous part if it has text
731 > lastLinePart = new LinePart(charOffset, token.type, token.metadata, token.containsRTL);
732 > result.push(lastLinePart);
733 > }
734 > lastLinePart = new LinePart(charOffset + 1, 'mtkcontrol', token.metadata, false);
735 > result.push(lastLinePart);
736 > }
738 > if (charOffset > lastLinePart.endIndex) {
739 > // emit previous part if it has text
740 > lastLinePart = new LinePart(tokenEndIndex, token.type, token.metadata, token.containsRTL);
741 > result.push(lastLinePart);
742 > }
743 > }
744 > return result;
745 > }
747 > /**
748 > * Whitespace is rendered by "replacing" tokens with a special-purpose `mtkw` type that is later recognized in the rendering phase.
749 > * 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;.
750 > * The rendering phase will generate `style="width:..."` for these tokens.
751 > */
752 > function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len: number, tokens: LinePart[]): LinePart[] { viewLineRenderer.ts ×18
753 >
754 > const continuesWithWrappedLine = input.continuesWithWrappedLine;
755 > const fauxIndentLength = input.fauxIndentLength;
756 > const tabSize = input.tabSize;
757 > const startVisibleColumn = input.startVisibleColumn;
758 > const useMonospaceOptimizations = input.useMonospaceOptimizations;
759 > const selections = input.selectionsOnLine;
760 > const onlyBoundary = (input.renderWhitespace === RenderWhitespace.Boundary);
761 > const onlyTrailing = (input.renderWhitespace === RenderWhitespace.Trailing);
762 > const generateLinePartForEachWhitespace = (input.renderSpaceWidth !== input.spaceWidth);
763 >
764 > const result: LinePart[] = [];
765 > let resultLen = 0;
766 > let tokenIndex = 0;
767 > let tokenType = tokens[tokenIndex].type;
768 > let tokenContainsRTL = tokens[tokenIndex].containsRTL;
769 > let tokenEndIndex = tokens[tokenIndex].endIndex;
770 > const tokensLength = tokens.length;
771 >
772 > let lineIsEmptyOrWhitespace = false;
773 > let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
774 > let lastNonWhitespaceIndex: number;
775 > if (firstNonWhitespaceIndex === -1) {
776 > lineIsEmptyOrWhitespace = true; viewLineRenderer.ts ×1
777 > firstNonWhitespaceIndex = len;
778 > lastNonWhitespaceIndex = len;
779 > } else { viewLineRenderer.ts ×18
780 > lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent); viewLineRenderer.ts ×3
781 > }
783 > let wasInWhitespace = false;
784 > let currentSelectionIndex = 0;
785 > let currentSelection = selections && selections[currentSelectionIndex];
786 > let tmpIndent = startVisibleColumn % tabSize;
787 > for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
788 > const chCode = lineContent.charCodeAt(charIndex);
789 >
790 > if (currentSelection && currentSelection.endExclusive <= charIndex) {
791 > currentSelectionIndex++; viewLineRenderer.ts ×1
792 > currentSelection = selections && selections[currentSelectionIndex];
793 > }
795 > let isInWhitespace: boolean;
796 > if (charIndex < firstNonWhitespaceIndex || charIndex > lastNonWhitespaceIndex) {
797 > // in leading or trailing whitespace viewLineRenderer.ts ×1
798 > isInWhitespace = true;
799 > } else if (chCode === CharCode.Tab) { viewLineRenderer.ts ×18
800 > // a tab character is rendered both in all and boundary cases viewLineRenderer.ts ×2
801 > isInWhitespace = true;
802 > } else if (chCode === CharCode.Space) { viewLineRenderer.ts ×3
803 > // hit a space character viewLineRenderer.ts ×3
804 > if (onlyBoundary) {
805 > // rendering only boundary whitespace viewLineRenderer.ts ×3
806 > if (wasInWhitespace) {
807 > isInWhitespace = true; viewLineRenderer.ts ×1
808 > } else { viewLineRenderer.ts ×3
809 > const nextChCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : CharCode.Null);
810 > isInWhitespace = (nextChCode === CharCode.Space || nextChCode === CharCode.Tab);
811 > }
812 > } else { viewLineRenderer.ts ×3
813 > isInWhitespace = true; viewLineRenderer.ts ×1
814 > }
815 > } else { viewLineRenderer.ts ×3
816 > isInWhitespace = false;
817 > }
819 > // If rendering whitespace on selection, check that the charIndex falls within a selection
820 > if (isInWhitespace && selections) {
821 > isInWhitespace = !!currentSelection && currentSelection.start <= charIndex && charIndex < currentSelection.endExclusive; viewLineRenderer.ts ×1
822 > }
824 > // If rendering only trailing whitespace, check that the charIndex points to trailing whitespace.
825 > if (isInWhitespace && onlyTrailing) {
826 > isInWhitespace = lineIsEmptyOrWhitespace || charIndex > lastNonWhitespaceIndex; viewLineRenderer.ts ×2
827 > }
829 > if (isInWhitespace && tokenContainsRTL) {
830 > // If the token contains RTL text, breaking it up into multiple line parts viewLineRenderer.ts ×1
831 > // to render whitespace might affect the browser's bidi layout.
832 > //
833 > // We render whitespace in such tokens only if the whitespace
834 > // is the leading or the trailing whitespace of the line,
835 > // which doesn't affect the browser's bidi layout.
836 > if (charIndex >= firstNonWhitespaceIndex && charIndex <= lastNonWhitespaceIndex) {
837 > isInWhitespace = false;
838 > }
839 > }
841 > if (wasInWhitespace) {
842 > // was in whitespace token viewLineRenderer.ts ×5
843 > if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
844 > // leaving whitespace token or entering a new indent viewLineRenderer.ts ×3
845 > if (generateLinePartForEachWhitespace) {
846 > const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength); viewLineRenderer.ts ×1
847 > for (let i = lastEndIndex + 1; i <= charIndex; i++) {
848 > result[resultLen++] = new LinePart(i, 'mtkw', LinePartMetadata.IS_WHITESPACE, false);
849 > }
850 > } else { viewLineRenderer.ts ×3
851 > result[resultLen++] = new LinePart(charIndex, 'mtkw', LinePartMetadata.IS_WHITESPACE, false); viewLineRenderer.ts ×1
852 > }
853 > tmpIndent = tmpIndent % tabSize; viewLineRenderer.ts ×3
854 > }
855 > } else { viewLineRenderer.ts ×18
856 > // was in regular token
857 > if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) {
858 > result[resultLen++] = new LinePart(charIndex, tokenType, 0, tokenContainsRTL); viewLineRenderer.ts ×3
859 > tmpIndent = tmpIndent % tabSize;
860 > }
862 >
863 > if (chCode === CharCode.Tab) {
864 > tmpIndent = tabSize; viewLineRenderer.ts ×1
865 > } else if (strings.isFullWidthCharacter(chCode)) { viewLineRenderer.ts ×18
866 > tmpIndent += 2; viewLineRenderer.ts ×2
867 > } else { viewLineRenderer.ts ×18
868 > tmpIndent++;
869 > }
870 >
871 > wasInWhitespace = isInWhitespace;
872 >
873 > while (charIndex === tokenEndIndex) {
874 > tokenIndex++; viewLineRenderer.ts ×2
875 > if (tokenIndex < tokensLength) {
876 > tokenType = tokens[tokenIndex].type;
877 > tokenContainsRTL = tokens[tokenIndex].containsRTL;
878 > tokenEndIndex = tokens[tokenIndex].endIndex;
879 > } else {
880 break;
881 }
884 >
885 > let generateWhitespace = false;
886 > if (wasInWhitespace) {
887 > // was in whitespace token viewLineRenderer.ts ×3
888 > if (continuesWithWrappedLine && onlyBoundary) {
889 > const lastCharCode = (len > 0 ? lineContent.charCodeAt(len - 1) : CharCode.Null); viewLineRenderer.ts ×1
890 > const prevCharCode = (len > 1 ? lineContent.charCodeAt(len - 2) : CharCode.Null);
891 > const isSingleTrailingSpace = (lastCharCode === CharCode.Space && (prevCharCode !== CharCode.Space && prevCharCode !== CharCode.Tab));
892 > if (!isSingleTrailingSpace) {
893 generateWhitespace = true;
894 }
895 > } else { viewLineRenderer.ts ×3
896 > generateWhitespace = true; viewLineRenderer.ts ×3
897 > }
900 > if (generateWhitespace) {
901 > if (generateLinePartForEachWhitespace) { viewLineRenderer.ts ×3
902 const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
903 for (let i = lastEndIndex + 1; i <= len; i++) {
904 result[resultLen++] = new LinePart(i, 'mtkw', LinePartMetadata.IS_WHITESPACE, false);
905 }
906 > } else { viewLineRenderer.ts ×3
907 > result[resultLen++] = new LinePart(len, 'mtkw', LinePartMetadata.IS_WHITESPACE, false);
908 > }
909 > } else { viewLineRenderer.ts ×18
910 > result[resultLen++] = new LinePart(len, tokenType, 0, tokenContainsRTL); viewLineRenderer.ts ×1
911 > }
913 > return result;
914 > }
916 > /**
917 > * Inline decorations are "merged" on top of tokens.
918 > * Special care must be taken when multiple inline decorations are at play and they overlap.
919 > */
920 > function _applyInlineDecorations(lineContent: string, len: number, tokens: LinePart[], _lineDecorations: LineDecoration[]): LinePart[] { viewLineRenderer.ts ×8
921 > _lineDecorations.sort(LineDecoration.compare);
922 > const lineDecorations = LineDecorationsNormalizer.normalize(lineContent, _lineDecorations);
923 > const lineDecorationsLen = lineDecorations.length;
924 >
925 > let lineDecorationIndex = 0;
926 > const result: LinePart[] = [];
927 > let resultLen = 0;
928 > let lastResultEndIndex = 0;
929 > for (let tokenIndex = 0, len = tokens.length; tokenIndex < len; tokenIndex++) {
930 > const token = tokens[tokenIndex];
931 > const tokenEndIndex = token.endIndex;
932 > const tokenType = token.type;
933 > const tokenMetadata = token.metadata;
934 > const tokenContainsRTL = token.containsRTL;
935 >
936 > while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) {
937 > const lineDecoration = lineDecorations[lineDecorationIndex]; viewLineRenderer.ts ×3
938 >
939 > if (lineDecoration.startOffset > lastResultEndIndex) {
940 > lastResultEndIndex = lineDecoration.startOffset; viewLineRenderer.ts ×1
941 > result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
942 > }
944 > if (lineDecoration.endOffset + 1 <= tokenEndIndex) {
945 > // This line decoration ends before this token ends
946 > lastResultEndIndex = lineDecoration.endOffset + 1;
947 > result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata, tokenContainsRTL);
948 > lineDecorationIndex++;
949 > } else {
950 > // This line decoration continues on to the next token viewLineRenderer.ts ×1
951 > lastResultEndIndex = tokenEndIndex;
952 > result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata, tokenContainsRTL);
953 > break;
954 > }
957 > if (tokenEndIndex > lastResultEndIndex) {
958 > lastResultEndIndex = tokenEndIndex; viewLineRenderer.ts ×1
959 > result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
960 > }
962 >
963 > const lastTokenEndIndex = tokens[tokens.length - 1].endIndex;
964 > if (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
965 > while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) { viewLineRenderer.ts ×1
966 > const lineDecoration = lineDecorations[lineDecorationIndex];
967 > result[resultLen++] = new LinePart(lastResultEndIndex, lineDecoration.className, lineDecoration.metadata, false);
968 > lineDecorationIndex++;
969 > }
970 > }
972 > return result;
973 > }
975 > /**
976 > * This function is on purpose not split up into multiple functions to allow runtime type inference (i.e. performance reasons).
977 > * Notice how all the needed data is fully resolved and passed in (i.e. no other calls).
978 > */
979 > function _renderLine(input: ResolvedRenderLineInput, sb: StringBuilder): RenderLineOutput { viewLineRenderer.ts ×22
980 > const fontIsMonospace = input.fontIsMonospace;
981 > const canUseHalfwidthRightwardsArrow = input.canUseHalfwidthRightwardsArrow;
982 > const containsForeignElements = input.containsForeignElements;
983 > const lineContent = input.lineContent;
984 > const len = input.len;
985 > const isOverflowing = input.isOverflowing;
986 > const overflowingCharCount = input.overflowingCharCount;
987 > const parts = input.parts;
988 > const fauxIndentLength = input.fauxIndentLength;
989 > const tabSize = input.tabSize;
990 > const startVisibleColumn = input.startVisibleColumn;
991 > const spaceWidth = input.spaceWidth;
992 > const renderSpaceCharCode = input.renderSpaceCharCode;
993 > const renderWhitespace = input.renderWhitespace;
994 > const renderControlCharacters = input.renderControlCharacters;
995 >
996 > const characterMapping = new CharacterMapping(len + 1, parts.length);
997 > let lastCharacterMappingDefined = false;
998 >
999 > let charIndex = 0;
1000 > let visibleColumn = startVisibleColumn;
1001 > let charOffsetInPart = 0; // the character offset in the current part
1002 > let charHorizontalOffset = 0; // the character horizontal position in terms of chars relative to line start
1003 >
1004 > let partDisplacement = 0;
1005 >
1006 > sb.appendString('<span>');
1007 >
1008 > for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) {
1009 >
1010 > const part = parts[partIndex];
1011 > const partEndIndex = part.endIndex;
1012 > const partType = part.type;
1013 > const partContainsRTL = part.containsRTL;
1014 > const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && part.isWhitespace());
1015 > const partRendersWhitespaceWithWidth = partRendersWhitespace && !fontIsMonospace && (partType === 'mtkw'/*only whitespace*/ || !containsForeignElements);
1016 > const partIsEmptyAndHasPseudoAfter = (charIndex === partEndIndex && part.isPseudoAfter());
1017 > charOffsetInPart = 0;
1018 >
1019 > sb.appendString('<span ');
1020 > if (partContainsRTL) {
1021 > sb.appendString('style="unicode-bidi:isolate" '); viewLineRenderer.ts ×4
1022 > }
1023 > sb.appendString('class="'); viewLineRenderer.ts ×22
1024 > sb.appendString(partRendersWhitespaceWithWidth ? 'mtkz' : partType);
1025 > sb.appendASCIICharCode(CharCode.DoubleQuote);
1026 >
1027 > if (partRendersWhitespace) {
1029 > let partWidth = 0;
1030 > {
1031 > let _charIndex = charIndex;
1032 > let _visibleColumn = visibleColumn;
1033 >
1034 > for (; _charIndex < partEndIndex; _charIndex++) {
1035 > const charCode = lineContent.charCodeAt(_charIndex);
1036 > const charWidth = (charCode === CharCode.Tab ? (tabSize - (_visibleColumn % tabSize)) : 1) | 0;
1037 > partWidth += charWidth;
1038 > if (_charIndex >= fauxIndentLength) {
1039 > _visibleColumn += charWidth;
1040 > }
1041 > }
1042 > }
1043 >
1044 > if (partRendersWhitespaceWithWidth) {
1045 > sb.appendString(' style="width:'); viewLineRenderer.ts ×1
1046 > sb.appendString(String(spaceWidth * partWidth));
1047 > sb.appendString('px"');
1048 > }
1049 > sb.appendASCIICharCode(CharCode.GreaterThan); viewLineRenderer.ts ×5
1050 >
1051 > for (; charIndex < partEndIndex; charIndex++) {
1052 > characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, charHorizontalOffset);
1053 > partDisplacement = 0;
1054 > const charCode = lineContent.charCodeAt(charIndex);
1055 >
1056 > let producedCharacters: number;
1057 > let charWidth: number;
1058 >
1059 > if (charCode === CharCode.Tab) {
1060 > producedCharacters = (tabSize - (visibleColumn % tabSize)) | 0; viewLineRenderer.ts ×2
1061 > charWidth = producedCharacters;
1062 >
1063 > if (!canUseHalfwidthRightwardsArrow || charWidth > 1) {
1064 > sb.appendCharCode(0x2192); // RIGHTWARDS ARROW
1065 > } else {
1066 > sb.appendCharCode(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW viewLineRenderer.ts ×1
1067 > }
1068 > for (let space = 2; space <= charWidth; space++) { viewLineRenderer.ts ×2
1069 > sb.appendCharCode(0xA0); // &nbsp;
1070 > }
1071 >
1072 > } else { // must be CharCode.Space viewLineRenderer.ts ×5
1073 > producedCharacters = 2; viewLineRenderer.ts ×1
1074 > charWidth = 1;
1075 >
1076 > sb.appendCharCode(renderSpaceCharCode); // &middot; or word separator middle dot
1077 > sb.appendCharCode(0x200C); // ZERO WIDTH NON-JOINER
1078 > }
1080 > charOffsetInPart += producedCharacters;
1081 > charHorizontalOffset += charWidth;
1082 > if (charIndex >= fauxIndentLength) {
1083 > visibleColumn += charWidth;
1084 > }
1085 > }
1086 >
1087 > } else { viewLineRenderer.ts ×22
1089 > sb.appendASCIICharCode(CharCode.GreaterThan);
1090 >
1091 > for (; charIndex < partEndIndex; charIndex++) {
1092 > characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, charHorizontalOffset);
1093 > partDisplacement = 0;
1094 > const charCode = lineContent.charCodeAt(charIndex);
1095 >
1096 > let producedCharacters = 1;
1097 > let charWidth = 1;
1098 >
1099 > switch (charCode) {
1100 > case CharCode.Tab:
1101 > producedCharacters = (tabSize - (visibleColumn % tabSize)); viewLineRenderer.ts ×1
1102 > charWidth = producedCharacters;
1103 > for (let space = 1; space <= producedCharacters; space++) {
1104 > sb.appendCharCode(0xA0); // &nbsp;
1105 > }
1106 > break;
1108 > case CharCode.Space:
1109 > sb.appendCharCode(0xA0); // &nbsp; viewLineRenderer.ts ×1
1110 > break;
1112 > case CharCode.LessThan:
1113 > sb.appendString('&lt;'); viewLineRenderer.ts ×2
1114 > break;
1116 > case CharCode.GreaterThan:
1117 > sb.appendString('&gt;'); viewLineRenderer.ts ×2
1118 > break;
1120 > case CharCode.Ampersand:
1121 > sb.appendString('&amp;'); viewLineRenderer.ts ×1
1122 > break;
1124 > case CharCode.Null:
1125 > if (renderControlCharacters) { viewLineRenderer.ts ×3
1126 > // See https://unicode-table.com/en/blocks/control-pictures/ viewLineRenderer.ts ×2
1127 > sb.appendCharCode(9216);
1128 > } else { viewLineRenderer.ts ×3
1129 > sb.appendString('&#00;'); viewLineRenderer.ts ×1
1130 > }
1131 > break; viewLineRenderer.ts ×3
1133 > case CharCode.UTF8_BOM:
1134 > case CharCode.LINE_SEPARATOR:
1135 > case CharCode.PARAGRAPH_SEPARATOR:
1136 > case CharCode.NEXT_LINE:
1137 > sb.appendCharCode(0xFFFD); viewLineRenderer.ts ×1
1138 > break;
1140 > default:
1141 > if (strings.isFullWidthCharacter(charCode)) { viewLineRenderer.ts ×5
1142 > charWidth++; viewLineRenderer.ts ×1
1143 > }
1144 > // See https://unicode-table.com/en/blocks/control-pictures/ viewLineRenderer.ts ×5
1145 > if (renderControlCharacters && charCode < 32) {
1146 sb.appendCharCode(9216 + charCode);
1147 > } else if (renderControlCharacters && charCode === 127) { viewLineRenderer.ts ×5
1148 > // DEL viewLineRenderer.ts ×2
1149 > sb.appendCharCode(9249);
1150 > } else if (renderControlCharacters && isControlCharacter(charCode)) { viewLineRenderer.ts ×5
1151 > sb.appendString('[U+'); viewLineRenderer.ts ×4
1152 > sb.appendString(to4CharHex(charCode));
1153 > sb.appendString(']');
1154 > producedCharacters = 8;
1155 > charWidth = producedCharacters;
1156 > } else { viewLineRenderer.ts ×5
1157 > sb.appendCharCode(charCode);
1158 > }
1160 >
1161 > charOffsetInPart += producedCharacters;
1162 > charHorizontalOffset += charWidth;
1163 > if (charIndex >= fauxIndentLength) {
1164 > visibleColumn += charWidth;
1165 > }
1166 > }
1167 > }
1169 > if (partIsEmptyAndHasPseudoAfter) {
1170 > partDisplacement++; viewLineRenderer.ts ×1
1171 > } else { viewLineRenderer.ts ×22
1172 > partDisplacement = 0;
1173 > }
1174 >
1175 > if (charIndex >= len && !lastCharacterMappingDefined && part.isPseudoAfter()) {
1176 > lastCharacterMappingDefined = true; viewLineRenderer.ts ×1
1177 > characterMapping.setColumnInfo(charIndex + 1, partIndex, charOffsetInPart, charHorizontalOffset);
1178 > }
1180 > sb.appendString('</span>');
1181 >
1182 > }
1183 >
1184 > if (!lastCharacterMappingDefined) {
1185 > // When getting client rects for the last character, we will position the viewLineRenderer.ts ×1
1186 > // text range at the end of the span, insteaf of at the beginning of next span
1187 > characterMapping.setColumnInfo(len + 1, parts.length - 1, charOffsetInPart, charHorizontalOffset);
1188 > }
1190 > if (isOverflowing) {
1191 > sb.appendString('<span class="mtkoverflow">'); viewLineRenderer.ts ×3
1192 > sb.appendString(nls.localize('showMore', "Show more ({0})", renderOverflowingCharCount(overflowingCharCount)));
1193 > sb.appendString('</span>');
1194 > }
1196 > sb.appendString('</span>');
1197 >
1198 > return new RenderLineOutput(characterMapping, containsForeignElements);
1199 > }
1201 > function to4CharHex(n: number): string { viewLineRenderer.ts ×4
1202 > return n.toString(16).toUpperCase().padStart(4, '0');
1203 > }
1205 > function renderOverflowingCharCount(n: number): string { viewLineRenderer.ts ×3
1206 > if (n < 1024) {
1207 > return nls.localize('overflow.chars', "{0} chars", n);
1208 > }
1209 if (n < 1024 * 1024) {
1210 return `${(n / 1024).toFixed(1)} KB`;
1211 }
1212 return `${(n / 1024 / 1024).toFixed(1)} MB`;
1213 }