viewLineRenderer.ts ×28

Frontier kind: Code frontier

unlabeled · c_1415442ac87b

72 tests · 8752 LOC · 43 files · introduces 0 tests · 273 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
28 ranges273 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1064 ranges8752 lines · 43 files · Browse complete extent
All tests (intent)
72 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

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 native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 273 introduced LOC across 28 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/editor/common/viewLayout/viewLineRenderer.ts 273 introduced LOC · 28 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- viewLineRenderer.ts
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
127 : renderWhitespace === 'boundary'
132 ? RenderWhitespace.Trailing
133 : RenderWhitespace.None
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
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;
172 return true;
173 }
175 > public equals(other: RenderLineInput): boolean {
176 return (
177 this.useMonospaceOptimizations === other.useMonospaceOptimizations
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,
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;
225 }
227 > private static getCharIndex(partData: number): number {
228 return (partData & CharacterMappingConstants.CHAR_INDEX_MASK) >>> CharacterMappingConstants.CHAR_INDEX_OFFSET;
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 = (
243 (partIndex << CharacterMappingConstants.PART_INDEX_OFFSET)
247 this._horizontalOffset[column - 1] = horizontalOffset;
248 }
250 > public getHorizontalOffset(column: number): number {
251 if (this._horizontalOffset.length === 0) {
252 // No characters on this line
255 return this._horizontalOffset[column - 1];
256 }
258 > private charOffsetToPartData(charOffset: number): number {
259 if (this.length === 0) {
260 return 0;
268 return this._data[charOffset];
269 }
271 > public getDomPosition(column: number): DomPosition {
272 const partData = this.charOffsetToPartData(column - 1);
273 const partIndex = CharacterMapping.getPartIndex(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);
280 return charOffset + 1;
281 }
283 > private partDataToCharOffset(partIndex: number, partLength: number, charIndex: number): number {
284 if (this.length === 0) {
285 return 0;
340 return max;
341 }
343 > public inflate() {
344 const result: [number, number, number][] = [];
345 for (let i = 0; i < this.length; i++) {
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) {
376
377 if (input.lineDecorations.length > 0) {
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,
445 public readonly canUseHalfwidthRightwardsArrow: boolean,
460 //
461 }
463 >
464 function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput {
465 const lineContent = input.lineContent;
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[] {
541 const result: LinePart[] = [];
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[] {
582 let lastTokenEndIndex = 0;
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[] {
648 if (tokens.length === 0) {
683 return result;
684 }
686 function isControlCharacter(charCode: number): boolean {
687 if (charCode < 32) {
717 return false;
718 }
720 function extractControlCharacters(lineContent: string, tokens: LinePart[]): LinePart[] {
721 const result: LinePart[] = [];
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[] {
753
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[] {
921 _lineDecorations.sort(LineDecoration.compare);
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 {
980 const fontIsMonospace = input.fontIsMonospace;
1198 return new RenderLineOutput(characterMapping, containsForeignElements);
1199 }
1201 function to4CharHex(n: number): string {
1202 return n.toString(16).toUpperCase().padStart(4, '0');
1203 }
1205 function renderOverflowingCharCount(n: number): string {
1206 if (n < 1024) {