defaultLinesDiffComputer.ts ×11

Frontier kind: Code frontier

unlabeled · c_cca73002f98c

60 tests · 8269 LOC · 52 files · introduces 0 tests · 169 LOC · 7 files

Introduces — evidence that enters the hierarchy at this concept

Code
38 ranges169 lines · 7 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1341 ranges8269 lines · 52 files · Browse complete extent
All tests (intent)
60 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.

7 files ranked by introduced lines: 169 introduced LOC across 38 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/dynamicProgrammingDiffing.ts 73 introduced LOC · 6 ranges

Open complete file

14 export class DynamicProgrammingDiffing implements IDiffAlgorithm {
15 compute(sequence1: ISequence, sequence2: ISequence, timeout: ITimeout = InfiniteTimeout.instance, equalityScore?: (offset1: number, offset2: number) => number): DiffAlgorithmResult {
16 > if (sequence1.length === 0 || sequence2.length === 0) { dynamicProgrammingDiffing.ts
17 return DiffAlgorithmResult.trivial(sequence1, sequence2);
18 }
20 > /**
21 > * lcsLengths.get(i, j): Length of the longest common subsequence of sequence1.substring(0, i + 1) and sequence2.substring(0, j + 1).
22 > */
23 > const lcsLengths = new Array2D<number>(sequence1.length, sequence2.length);
24 > const directions = new Array2D<number>(sequence1.length, sequence2.length);
25 > const lengths = new Array2D<number>(sequence1.length, sequence2.length);
26 >
27 > // ==== Initializing lcsLengths ====
28 > for (let s1 = 0; s1 < sequence1.length; s1++) {
29 > for (let s2 = 0; s2 < sequence2.length; s2++) {
30 > if (!timeout.isValid()) {
31 return DiffAlgorithmResult.trivialTimedOut(sequence1, sequence2);
32 }
34 > const horizontalLen = s1 === 0 ? 0 : lcsLengths.get(s1 - 1, s2);
35 > const verticalLen = s2 === 0 ? 0 : lcsLengths.get(s1, s2 - 1);
36 >
37 > let extendedSeqScore: number;
38 > if (sequence1.getElement(s1) === sequence2.getElement(s2)) {
39 > if (s1 === 0 || s2 === 0) {
40 > extendedSeqScore = 0;
41 > } else {
42 > extendedSeqScore = lcsLengths.get(s1 - 1, s2 - 1);
43 > }
44 > if (s1 > 0 && s2 > 0 && directions.get(s1 - 1, s2 - 1) === 3) {
45 > // Prefer consecutive diagonals
46 > extendedSeqScore += lengths.get(s1 - 1, s2 - 1);
47 > }
48 > extendedSeqScore += (equalityScore ? equalityScore(s1, s2) : 1);
49 > } else {
50 > extendedSeqScore = -1;
51 > }
52 >
53 > const newValue = Math.max(horizontalLen, verticalLen, extendedSeqScore);
54 >
55 > if (newValue === extendedSeqScore) {
56 > // Prefer diagonals
57 > const prevLen = s1 > 0 && s2 > 0 ? lengths.get(s1 - 1, s2 - 1) : 0;
58 > lengths.set(s1, s2, prevLen + 1);
59 > directions.set(s1, s2, 3);
60 > } else if (newValue === horizontalLen) {
61 > lengths.set(s1, s2, 0);
62 > directions.set(s1, s2, 1);
63 > } else if (newValue === verticalLen) {
64 lengths.set(s1, s2, 0);
65 directions.set(s1, s2, 2);
66 }
68 > lcsLengths.set(s1, s2, newValue);
69 > }
70 > }
71 >
72 > // ==== Backtracking ====
73 > const result: SequenceDiff[] = [];
74 > let lastAligningPosS1: number = sequence1.length;
75 > let lastAligningPosS2: number = sequence2.length;
76 >
77 > function reportDecreasingAligningPositions(s1: number, s2: number): void {
78 > if (s1 + 1 !== lastAligningPosS1 || s2 + 1 !== lastAligningPosS2) {
79 result.push(new SequenceDiff(
80 new OffsetRange(s1 + 1, lastAligningPosS1),
82 ));
83 }
84 > lastAligningPosS1 = s1; dynamicProgrammingDiffing.ts
85 > lastAligningPosS2 = s2;
86 > }
87 >
88 > let s1 = sequence1.length - 1;
89 > let s2 = sequence2.length - 1;
90 > while (s1 >= 0 && s2 >= 0) {
91 > if (directions.get(s1, s2) === 3) {
92 > reportDecreasingAligningPositions(s1, s2);
93 > s1--;
94 > s2--;
95 > } else {
96 if (directions.get(s1, s2) === 1) {
97 s1--;
100 }
101 }
103 > reportDecreasingAligningPositions(-1, -1);
104 > result.reverse();
105 > return new DiffAlgorithmResult(result, false);
106 > }
107 }
src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts 41 introduced LOC · 11 ranges

Open complete file

44 ], [], false);
45 }
47 const timeout = options.maxComputationTimeMs === 0 ? InfiniteTimeout.instance : new DateTimeout(options.maxComputationTimeMs);
48 const considerWhitespaceChanges = !options.ignoreTrimWhitespace;
50 const perfectHashes = new Map<string, number>();
51 function getOrCreateHash(text: string): number {
52 > let hash = perfectHashes.get(text); defaultLinesDiffComputer.ts
53 > if (hash === undefined) {
54 > hash = perfectHashes.size;
55 > perfectHashes.set(text, hash);
56 > }
57 > return hash;
58 > }
59
60 const originalLinesHashes = originalLines.map((l) => getOrCreateHash(l.trim()));
65
66 const lineAlignmentResult = (() => {
67 > if (sequence1.length + sequence2.length < 1700) { defaultLinesDiffComputer.ts
68 > // Use the improved algorithm for small files
69 > return this.dynamicProgrammingDiffing.compute(
70 > sequence1,
71 > sequence2,
72 > timeout,
73 > (offset1, offset2) =>
74 originalLines[offset1] === modifiedLines[offset2]
75 ? modifiedLines[offset2].length === 0
77 : 1 + Math.log(1 + modifiedLines[offset2].length)
78 : 0.99
80 > }
81
82 return this.myersDiffingAlgorithm.compute(
95
96 const scanForWhitespaceChanges = (equalLinesCount: number) => {
97 > if (!considerWhitespaceChanges) { defaultLinesDiffComputer.ts
98 return;
99 }
139 }
140 }
142 > scanForWhitespaceChanges(originalLines.length - seq1LastStart);
143 >
144 > const original = new ArrayText(originalLines);
145 > const modified = new ArrayText(modifiedLines);
146 >
147 > const changes = lineRangeMappingFromRangeMappings(alignments, original, modified);
148 >
149 > let moves: MovedText[] = [];
150 > if (options.computeMoves) {
151 moves = this.computeMoves(changes, originalLines, modifiedLines, originalLinesHashes, modifiedLinesHashes, timeout, considerWhitespaceChanges, options);
152 }
154 > // Make sure all ranges are valid
155 > assertFn(() => {
156 > function validatePosition(pos: Position, lines: string[]): boolean {
157 if (pos.lineNumber < 1 || pos.lineNumber > lines.length) { return false; }
158 const line = lines[pos.lineNumber - 1];
160 return true;
161 }
163 > function validateRange(range: LineRange, lines: string[]): boolean {
164 if (range.startLineNumber < 1 || range.startLineNumber > lines.length + 1) { return false; }
165 if (range.endLineNumberExclusive < 1 || range.endLineNumberExclusive > lines.length + 1) { return false; }
166 return true;
167 }
169 > for (const c of changes) {
170 if (!c.innerChanges) { return false; }
171 for (const ic of c.innerChanges) {
180 }
181 }
182 > return true; defaultLinesDiffComputer.ts
183 > });
184 >
185 > return new LinesDiff(changes, moves, hitTimeout);
186 }
187
src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts 22 introduced LOC · 7 ranges

Open complete file

11
12 export function optimizeSequenceDiffs(sequence1: ISequence, sequence2: ISequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] {
13 > let result = sequenceDiffs; heuristicSequenceOptimizations.ts
14 > result = joinSequenceDiffsByShifting(sequence1, sequence2, result);
15 > // Sometimes, calling this function twice improves the result.
16 > // Uncomment the second invocation and run the tests to see the difference.
17 > result = joinSequenceDiffsByShifting(sequence1, sequence2, result);
18 > result = shiftSequenceDiffs(sequence1, sequence2, result);
19 > return result;
20 > }
21
22 /**
32 * Improved diff: [{Add ", Foo" after Bar}]
33 */
34 > function joinSequenceDiffsByShifting(sequence1: ISequence, sequence2: ISequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] { heuristicSequenceOptimizations.ts
35 > if (sequenceDiffs.length === 0) {
36 return sequenceDiffs;
37 }
130 // collectBrackets(level + 1, [levelPerBracket + 1, ]levelPerBracketType);
131
132 > function shiftSequenceDiffs(sequence1: ISequence, sequence2: ISequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] { heuristicSequenceOptimizations.ts
133 > if (!sequence1.getBoundaryScore || !sequence2.getBoundaryScore) {
134 return sequenceDiffs;
135 }
137 > for (let i = 0; i < sequenceDiffs.length; i++) {
138 const prevDiff = (i > 0 ? sequenceDiffs[i - 1] : undefined);
139 const diff = sequenceDiffs[i];
149 }
150 }
152 > return sequenceDiffs;
153 > }
154
155 function shiftDiffToBetterPosition(diff: SequenceDiff, sequence1: ISequence, sequence2: ISequence, seq1ValidRange: OffsetRange, seq2ValidRange: OffsetRange,) {
324
325 export function removeVeryShortMatchingLinesBetweenDiffs(sequence1: LineSequence, _sequence2: LineSequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] {
326 > let diffs = sequenceDiffs; heuristicSequenceOptimizations.ts
327 > if (diffs.length === 0) {
328 return diffs;
329 }
366 diffs = result;
367 } while (counter++ < 10 && shouldRepeat);
369 > return diffs;
370 > }
371
372 export function removeVeryShortMatchingTextBetweenLongDiffs(sequence1: LinesSliceCharSequence, sequence2: LinesSliceCharSequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] {
src/vs/editor/common/diff/rangeMapping.ts 15 introduced LOC · 5 ranges

Open complete file

321
322 export function lineRangeMappingFromRangeMappings(alignments: readonly RangeMapping[], originalLines: AbstractText, modifiedLines: AbstractText, dontAssertStartLine: boolean = false): DetailedLineRangeMapping[] {
323 > const changes: DetailedLineRangeMapping[] = []; rangeMapping.ts
324 > for (const g of groupAdjacentBy(
325 > alignments.map(a => getLineRangeMapping(a, originalLines, modifiedLines)),
326 > (a1, a2) =>
327 a1.original.intersectsOrTouches(a2.original)
328 || a1.modified.intersectsOrTouches(a2.modified)
329 > )) { rangeMapping.ts
330 const first = g[0];
331 const last = g[g.length - 1];
337 ));
338 }
340 > assertFn(() => {
341 > if (!dontAssertStartLine && changes.length > 0) {
342 if (changes[0].modified.startLineNumber !== changes[0].original.startLineNumber) {
343 return false;
348 }
349 }
350 > return checkAdjacentItems(changes, rangeMapping.ts
351 > (m1, m2) => m2.original.startLineNumber - m1.original.endLineNumberExclusive === m2.modified.startLineNumber - m1.modified.endLineNumberExclusive &&
352 // There has to be an unchanged line in between (otherwise both diffs should have been joined)
353 m1.original.endLineNumberExclusive < m2.original.startLineNumber &&
354 m1.modified.endLineNumberExclusive < m2.modified.startLineNumber,
355 > ); rangeMapping.ts
356 > });
357 >
358 > return changes;
359 > }
360
361 export function getLineRangeMapping(rangeMapping: RangeMapping, originalLines: AbstractText, modifiedLines: AbstractText): DetailedLineRangeMapping {
src/vs/editor/common/diff/defaultLinesDiffComputer/lineSequence.ts 7 introduced LOC · 3 ranges

Open complete file

10 export class LineSequence implements ISequence {
11 constructor(
12 > private readonly trimmedHash: number[], lineSequence.ts
13 > private readonly lines: string[]
14 > ) { }
15
16 getElement(offset: number): number {
17 > return this.trimmedHash[offset]; lineSequence.ts
18 > }
19
20 get length(): number {
21 > return this.trimmedHash.length; lineSequence.ts
22 > }
23
24 getBoundaryScore(length: number): number {
src/vs/editor/common/diff/defaultLinesDiffComputer/utils.ts 6 introduced LOC · 3 ranges

Open complete file

12
13 constructor(public readonly width: number, public readonly height: number) {
14 > this.array = new Array<T>(width * height); utils.ts
15 > }
16
17 get(x: number, y: number): T {
18 > return this.array[x + y * this.width]; utils.ts
19 > }
20
21 set(x: number, y: number, value: T): void {
22 > this.array[x + y * this.width] = value; utils.ts
23 > }
24 }
25
src/vs/base/common/arrays.ts 5 introduced LOC · 3 ranges

Open complete file

173 */
174 export function* groupAdjacentBy<T>(items: Iterable<T>, shouldBeGrouped: (item1: T, item2: T) => boolean): Iterable<T[]> {
175 > let currentGroup: T[] | undefined; arrays.ts
176 > let last: T | undefined;
177 > for (const item of items) {
178 if (last !== undefined && shouldBeGrouped(last, item)) {
179 currentGroup!.push(item);
186 last = item;
187 }
188 > if (currentGroup) { arrays.ts
189 yield currentGroup;
190 }
191 > } arrays.ts
192
193 export function forEachAdjacent<T>(arr: T[], f: (item1: T | undefined, item2: T | undefined) => void): void {