textEdit.ts ×35

Frontier kind: Code frontier

unlabeled · c_c831643f4b7f

3238 tests · 5936 LOC · 34 files · introduces 0 tests · 110 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
35 ranges110 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
954 ranges5936 lines · 34 files · Browse complete extent
All tests (intent)
3238 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: 110 introduced LOC across 35 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/editor/common/core/edits/textEdit.ts 110 introduced LOC · 35 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- textEdit.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 { compareBy, equals } from '../../../../base/common/arrays.js';
7 > import { assertFn, checkAdjacentItems } from '../../../../base/common/assert.js';
8 > import { BugIndicatingError } from '../../../../base/common/errors.js';
9 > import { commonPrefixLength, commonSuffixLength } from '../../../../base/common/strings.js';
10 > import { ISingleEditOperation } from '../editOperation.js';
11 > import { BaseStringEdit, StringReplacement } from './stringEdit.js';
12 > import { Position } from '../position.js';
13 > import { Range } from '../range.js';
14 > import { TextLength } from '../text/textLength.js';
15 > import { AbstractText, StringText } from '../text/abstractText.js';
16 > import { IEquatable } from '../../../../base/common/equals.js';
17 >
18 > export class TextEdit {
19 > public static fromStringEdit(edit: BaseStringEdit, initialState: AbstractText): TextEdit {
20 > const edits = edit.replacements.map(e => TextReplacement.fromStringReplacement(e, initialState));
21 > return new TextEdit(edits);
22 > }
23 >
24 > public static replace(originalRange: Range, newText: string): TextEdit {
25 return new TextEdit([new TextReplacement(originalRange, newText)]);
26 }
28 > public static delete(range: Range): TextEdit {
29 return new TextEdit([new TextReplacement(range, '')]);
30 }
32 > public static insert(position: Position, newText: string): TextEdit {
33 return new TextEdit([new TextReplacement(Range.fromPositions(position, position), newText)]);
34 }
36 > public static fromParallelReplacementsUnsorted(replacements: readonly TextReplacement[]): TextEdit {
37 const r = replacements.slice().sort(compareBy(i => i.range, Range.compareRangesUsingStarts));
38 return new TextEdit(r);
39 }
41 > constructor(
42 public readonly replacements: readonly TextReplacement[]
43 ) {
44 assertFn(() => checkAdjacentItems(replacements, (a, b) => a.range.getEndPosition().isBeforeOrEqual(b.range.getStartPosition())));
45 }
47 > /**
48 > * Joins touching edits and removes empty edits.
49 > */
50 > normalize(): TextEdit {
51 const replacements: TextReplacement[] = [];
52 for (const r of this.replacements) {
60 return new TextEdit(replacements);
61 }
63 > mapPosition(position: Position): Position | Range {
64 let lineDelta = 0;
65 let curLine = 0;
101 return new Position(position.lineNumber + lineDelta, position.column + (position.lineNumber + lineDelta === curLine ? columnDeltaInCurLine : 0));
102 }
103 > textEdit.ts
104 > mapRange(range: Range): Range {
105 function getStart(p: Position | Range) {
106 return p instanceof Position ? p : p.getStartPosition();
116 return rangeFromPositions(start, end);
117 }
118 > textEdit.ts
119 > // TODO: `doc` is not needed for this!
120 > inverseMapPosition(positionAfterEdit: Position, doc: AbstractText): Position | Range {
121 const reversed = this.inverse(doc);
122 return reversed.mapPosition(positionAfterEdit);
123 }
124 > textEdit.ts
125 > inverseMapRange(range: Range, doc: AbstractText): Range {
126 const reversed = this.inverse(doc);
127 return reversed.mapRange(range);
128 }
129 > textEdit.ts
130 > apply(text: AbstractText): string {
131 let result = '';
132 let lastEditEnd = new Position(1, 1);
149 return result;
150 }
151 > textEdit.ts
152 > applyToString(str: string): string {
153 const strText = new StringText(str);
154 return this.apply(strText);
155 }
156 > textEdit.ts
157 > inverse(doc: AbstractText): TextEdit {
158 const ranges = this.getNewRanges();
159 return new TextEdit(this.replacements.map((e, idx) => new TextReplacement(ranges[idx], doc.getValueOfRange(e.range))));
160 }
161 > textEdit.ts
162 > getNewRanges(): Range[] {
163 const newRanges: Range[] = [];
164 let previousEditEndLineNumber = 0;
179 return newRanges;
180 }
181 > textEdit.ts
182 > toReplacement(text: AbstractText): TextReplacement {
183 if (this.replacements.length === 0) { throw new BugIndicatingError(); }
184 if (this.replacements.length === 1) { return this.replacements[0]; }
201 return new TextReplacement(Range.fromPositions(startPos, endPos), newText);
202 }
203 > textEdit.ts
204 > equals(other: TextEdit): boolean {
205 return equals(this.replacements, other.replacements, (a, b) => a.equals(b));
206 }
207 > textEdit.ts
208 > /**
209 > * Combines two edits into one with the same effect.
210 > * WARNING: This is written by AI, but well tested. I do not understand the implementation myself.
211 > *
212 > * Invariant:
213 > * ```
214 > * other.applyToString(this.applyToString(s0)) = this.compose(other).applyToString(s0)
215 > * ```
216 > */
217 > compose(other: TextEdit): TextEdit {
218 const edits1 = this.normalize();
219 const edits2 = other.normalize();
579 return new TextEdit(resultReplacements).normalize();
580 }
581 > textEdit.ts
582 > toString(text: AbstractText | string | undefined): string {
583 if (text === undefined) {
584 return this.replacements.map(edit => edit.toString()).join('\n');
641 }).join('\n');
642 }
643 > } textEdit.ts
644 >
645 > export class TextReplacement implements IEquatable<TextReplacement> {
646 > public static joinReplacements(replacements: TextReplacement[], initialValue: AbstractText): TextReplacement {
647 > if (replacements.length === 0) { throw new BugIndicatingError(); }
648 > if (replacements.length === 1) { return replacements[0]; }
649
650 const startPos = replacements[0].range.getStartPosition();
664 }
665 return new TextReplacement(Range.fromPositions(startPos, endPos), newText);
666 > } textEdit.ts
667 >
668 > public static fromStringReplacement(replacement: StringReplacement, initialState: AbstractText): TextReplacement {
669 return new TextReplacement(initialState.getTransformer().getRange(replacement.replaceRange), replacement.newText);
670 }
671 > textEdit.ts
672 > public static delete(range: Range): TextReplacement {
673 return new TextReplacement(range, '');
674 }
675 > textEdit.ts
676 > constructor(
677 public readonly range: Range,
678 public readonly text: string,
679 ) {
680 }
681 > textEdit.ts
682 > get isEmpty(): boolean {
683 return this.range.isEmpty() && this.text.length === 0;
684 }
685 > textEdit.ts
686 > static equals(first: TextReplacement, second: TextReplacement) {
687 return first.range.equalsRange(second.range) && first.text === second.text;
688 }
689 > textEdit.ts
690 > public toSingleEditOperation(): ISingleEditOperation {
691 return {
692 range: this.range,
694 };
695 }
696 > textEdit.ts
697 > public toEdit(): TextEdit {
698 return new TextEdit([this]);
699 }
700 > textEdit.ts
701 > public equals(other: TextReplacement): boolean {
702 return TextReplacement.equals(this, other);
703 }
704 > textEdit.ts
705 > public extendToCoverRange(range: Range, initialValue: AbstractText): TextReplacement {
706 if (this.range.containsRange(range)) { return this; }
707
712 return new TextReplacement(newRange, newText);
713 }
714 > textEdit.ts
715 > public extendToFullLine(initialValue: AbstractText): TextReplacement {
716 const newRange = new Range(
717 this.range.startLineNumber,
722 return this.extendToCoverRange(newRange, initialValue);
723 }
724 > textEdit.ts
725 > public removeCommonPrefixAndSuffix(text: AbstractText): TextReplacement {
726 const prefix = this.removeCommonPrefix(text);
727 const suffix = prefix.removeCommonSuffix(text);
728 return suffix;
729 }
730 > textEdit.ts
731 > public removeCommonPrefix(text: AbstractText): TextReplacement {
732 const normalizedOriginalText = text.getValueOfRange(this.range).replaceAll('\r\n', '\n');
733 const normalizedModifiedText = this.text.replaceAll('\r\n', '\n');
741 return new TextReplacement(range, newText);
742 }
743 > textEdit.ts
744 > public removeCommonSuffix(text: AbstractText): TextReplacement {
745 const normalizedOriginalText = text.getValueOfRange(this.range).replaceAll('\r\n', '\n');
746 const normalizedModifiedText = this.text.replaceAll('\r\n', '\n');
754 return new TextReplacement(range, newText);
755 }
756 > textEdit.ts
757 > public isEffectiveDeletion(text: AbstractText): boolean {
758 let newText = this.text.replaceAll('\r\n', '\n');
759 let existingText = text.getValueOfRange(this.range).replaceAll('\r\n', '\n');
767 return newText === '';
768 }
769 > textEdit.ts
770 > public toString(): string {
771 const start = this.range.getStartPosition();
772 const end = this.range.getEndPosition();
773 return `(${start.lineNumber},${start.column} -> ${end.lineNumber},${end.column}): "${this.text}"`;
774 }
775 > } textEdit.ts
776 >
777 function rangeFromPositions(start: Position, end: Position): Range {
778 if (start.lineNumber === end.lineNumber && start.column === Number.MAX_SAFE_INTEGER) {