monospaceLineBreaksComputer.ts ×24

Frontier kind: Code frontier

unlabeled · c_c608264119a2

18 tests · 15297 LOC · 44 files · introduces 0 tests · 170 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
24 ranges170 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1333 ranges15297 lines · 44 files · Browse complete extent
All tests (intent)
18 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: 170 introduced LOC across 24 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts 170 introduced LOC · 24 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- monospaceLineBreaksComputer.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 { CharCode } from '../../../base/common/charCode.js';
7 > import * as strings from '../../../base/common/strings.js';
8 > import { WrappingIndent, IComputedEditorOptions, EditorOption } from '../config/editorOptions.js';
9 > import { CharacterClassifier } from '../core/characterClassifier.js';
10 > import { FontInfo } from '../config/fontInfo.js';
11 > import { LineInjectedText } from '../textModelEvents.js';
12 > import { InjectedTextOptions } from '../model.js';
13 > import { ILineBreaksComputerFactory, ILineBreaksComputer, ModelLineProjectionData, ILineBreaksComputerContext } from '../modelLineProjectionData.js';
14 >
15 > export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFactory {
16 > public static create(options: IComputedEditorOptions): MonospaceLineBreaksComputerFactory {
17 return new MonospaceLineBreaksComputerFactory(
18 options.get(EditorOption.wordWrapBreakBeforeCharacters),
20 );
21 }
23 > private readonly classifier: WrappingCharacterClassifier;
24 >
25 > constructor(breakBeforeChars: string, breakAfterChars: string) {
26 > this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars);
27 > }
28 >
29 > public createLineBreaksComputer(context: ILineBreaksComputerContext, fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', wrapOnEscapedLineFeeds: boolean): ILineBreaksComputer {
30 > const lineNumbers: number[] = [];
31 > const previousBreakingData: (ModelLineProjectionData | null)[] = [];
32 > return {
33 > addRequest: (lineNumber: number, previousLineBreakData: ModelLineProjectionData | null) => {
34 > lineNumbers.push(lineNumber);
35 > previousBreakingData.push(previousLineBreakData);
36 > },
37 > finalize: () => {
38 > const columnsForFullWidthChar = fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth;
39 > const result: (ModelLineProjectionData | null)[] = [];
40 > for (let i = 0, len = lineNumbers.length; i < len; i++) {
41 > const lineNumber = lineNumbers[i];
42 > const injectedText = context.getLineInjectedText(lineNumber);
43 > const lineText = context.getLineContent(lineNumber);
44 > const previousLineBreakData = previousBreakingData[i];
45 > const isLineFeedWrappingEnabled = wrapOnEscapedLineFeeds && lineText.includes('"') && lineText.includes('\\n');
46 > if (previousLineBreakData && !previousLineBreakData.injectionOptions && !injectedText && !isLineFeedWrappingEnabled) {
47 result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, lineText, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak);
49 > result[i] = createLineBreaks(this.classifier, lineText, injectedText, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak, isLineFeedWrappingEnabled);
50 > }
51 > }
52 > arrPool1.length = 0;
53 > arrPool2.length = 0;
54 > return result;
55 > }
56 > };
57 > }
58 > }
59 >
60 > const enum CharacterClass {
61 > NONE = 0,
62 > BREAK_BEFORE = 1,
63 > BREAK_AFTER = 2,
64 > BREAK_IDEOGRAPHIC = 3 // for Han and Kana.
65 > }
66 >
67 > class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
68 >
69 > constructor(BREAK_BEFORE: string, BREAK_AFTER: string) {
70 > super(CharacterClass.NONE);
71 >
72 > for (let i = 0; i < BREAK_BEFORE.length; i++) {
73 this.set(BREAK_BEFORE.charCodeAt(i), CharacterClass.BREAK_BEFORE);
74 }
76 > for (let i = 0; i < BREAK_AFTER.length; i++) {
77 > this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
78 > }
79 > }
80 >
81 > public override get(charCode: number): CharacterClass {
82 > if (charCode >= 0 && charCode < 256) {
83 return <CharacterClass>this._asciiMap[charCode];
85 // Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges:
86 // 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
97 return <CharacterClass>(this._map.get(charCode) || this._defaultValue);
98 }
100 > }
101 >
102 > let arrPool1: number[] = [];
103 > let arrPool2: number[] = [];
104 >
105 function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: ModelLineProjectionData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): ModelLineProjectionData | null {
106 if (firstLineBreakColumn === -1) {
356 return previousBreakingData;
357 }
359 > function createLineBreaks(classifier: WrappingCharacterClassifier, _lineText: string, injectedTexts: LineInjectedText[] | null, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', wrapOnEscapedLineFeeds: boolean): ModelLineProjectionData | null {
360 > const lineText = LineInjectedText.applyInjectedText(_lineText, injectedTexts);
361 >
362 > let injectionOptions: InjectedTextOptions[] | null;
363 > let injectionOffsets: number[] | null;
364 > if (injectedTexts && injectedTexts.length > 0) {
365 injectionOptions = injectedTexts.map(t => t.options);
366 injectionOffsets = injectedTexts.map(text => text.column - 1);
368 > injectionOptions = null;
369 > injectionOffsets = null;
370 > }
371 >
372 > if (firstLineBreakColumn === -1) {
373 if (!injectionOptions) {
374 return null;
378 return new ModelLineProjectionData(injectionOffsets, injectionOptions, [lineText.length], [], 0);
379 }
381 > const len = lineText.length;
382 > if (len <= 1) {
383 if (!injectionOptions) {
384 return null;
388 return new ModelLineProjectionData(injectionOffsets, injectionOptions, [lineText.length], [], 0);
389 }
391 > const isKeepAll = (wordBreak === 'keepAll');
392 > const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
393 > const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
394 >
395 > const breakingOffsets: number[] = [];
396 > const breakingOffsetsVisibleColumn: number[] = [];
397 > let breakingOffsetsCount: number = 0;
398 > let breakOffset = 0;
399 > let breakOffsetVisibleColumn = 0;
400 >
401 > let breakingColumn = firstLineBreakColumn;
402 > let prevCharCode = lineText.charCodeAt(0);
403 > let prevCharCodeClass = classifier.get(prevCharCode);
404 > let visibleColumn = computeCharWidth(prevCharCode, 0, tabSize, columnsForFullWidthChar);
405 >
406 > let startOffset = 1;
407 > if (strings.isHighSurrogate(prevCharCode)) {
408 // A surrogate pair must always be considered as a single unit, so it is never to be broken
409 visibleColumn += 1;
412 startOffset++;
413 }
415 > for (let i = startOffset; i < len; i++) {
416 > const charStartOffset = i;
417 > const charCode = lineText.charCodeAt(i);
418 > let charCodeClass: CharacterClass;
419 > let charWidth: number;
420 > let wrapEscapedLineFeed = false;
421 >
422 > if (strings.isHighSurrogate(charCode)) {
423 // A surrogate pair must always be considered as a single unit, so it is never to be broken
424 i++;
425 charCodeClass = CharacterClass.NONE;
426 charWidth = 2;
428 charCodeClass = classifier.get(charCode);
429 charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
430 }
432 > // literal \n shall trigger a softwrap
433 > if (wrapOnEscapedLineFeeds && isEscapedLineBreakAtPosition(lineText, i)) {
434 breakOffset = charStartOffset;
435 breakOffsetVisibleColumn = visibleColumn;
436 wrapEscapedLineFeed = true;
437 > } else if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass, isKeepAll)) { monospaceLineBreaksComputer.ts
438 breakOffset = charStartOffset;
439 breakOffsetVisibleColumn = visibleColumn;
440 }
442 > visibleColumn += charWidth;
443 >
444 > // check if adding character at `i` will go over the breaking column
445 > if (visibleColumn > breakingColumn || wrapEscapedLineFeed) {
446 // We need to break at least before character at `i`:
447
458 breakOffset = 0;
459 }
461 > prevCharCode = charCode;
462 > prevCharCodeClass = charCodeClass;
463 > }
464 >
465 > if (breakingOffsetsCount === 0 && (!injectedTexts || injectedTexts.length === 0)) {
466 return null;
467 }
473 return new ModelLineProjectionData(injectionOffsets, injectionOptions, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength);
474 }
476 > function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number {
477 > if (charCode === CharCode.Tab) {
478 return (tabSize - (visibleColumn % tabSize));
479 }
480 > if (strings.isFullWidthCharacter(charCode)) { monospaceLineBreaksComputer.ts
481 return columnsForFullWidthChar;
482 }
483 > if (charCode < 32) { monospaceLineBreaksComputer.ts
484 // when using `editor.renderControlCharacters`, the substitutions are often wide
485 return columnsForFullWidthChar;
487 return 1;
488 }
490 function tabCharacterWidth(visibleColumn: number, tabSize: number): number {
491 return (tabSize - (visibleColumn % tabSize));
492 }
494 > /**
495 > * Checks if the current position in the text should trigger a soft wrap due to escaped line feeds.
496 > * This handles the wrapOnEscapedLineFeeds feature which allows \n sequences in strings to trigger wrapping.
497 > */
498 function isEscapedLineBreakAtPosition(lineText: string, i: number): boolean {
499 if (i >= 2 && lineText.charAt(i - 1) === 'n') {
510 return false;
511 }
513 > /**
514 > * Kinsoku Shori : Don't break after a leading character, like an open bracket
515 > * Kinsoku Shori : Don't break before a trailing character, like a period
516 > */
517 > function canBreak(prevCharCode: number, prevCharCodeClass: CharacterClass, charCode: number, charCodeClass: CharacterClass, isKeepAll: boolean): boolean {
518 > return (
519 > charCode !== CharCode.Space
520 > && (
521 > (prevCharCodeClass === CharacterClass.BREAK_AFTER && charCodeClass !== CharacterClass.BREAK_AFTER) // break at the end of multiple BREAK_AFTER
522 || (prevCharCodeClass !== CharacterClass.BREAK_BEFORE && charCodeClass === CharacterClass.BREAK_BEFORE) // break at the start of multiple BREAK_BEFORE
523 || (!isKeepAll && prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER)
524 || (!isKeepAll && charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE)
526 > );
527 > }
528 >
529 > function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): number {
530 > let wrappedTextIndentLength = 0;
531 > if (wrappingIndent !== WrappingIndent.None) {
532 const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
533 if (firstNonWhitespaceIndex !== -1) {