Joins consecutive moves that are close together

This commit is contained in:
Henning Dieterichs
2023-08-22 17:34:59 +02:00
committed by Henning Dieterichs
parent 026f38a82c
commit d5f5ad201d
4 changed files with 122 additions and 23 deletions
+8
View File
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { BugIndicatingError } from 'vs/base/common/errors';
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { Range } from 'vs/editor/common/core/range';
/**
@@ -239,6 +240,13 @@ export class LineRange {
public includes(lineNumber: number): boolean {
return this.startLineNumber <= lineNumber && lineNumber < this.endLineNumberExclusive;
}
/**
* Converts this 1-based line range to a 0-based offset range (subtracts 1!).
*/
public toOffsetRange(): OffsetRange {
return new OffsetRange(this.startLineNumber - 1, this.endLineNumberExclusive - 1);
}
}
export type ISerializedLineRange = [startLineNumber: number, endLineNumberExclusive: number];
+12
View File
@@ -48,6 +48,14 @@ export class OffsetRange {
return new OffsetRange(this.start + offset, this.endExclusive + offset);
}
public deltaStart(offset: number): OffsetRange {
return new OffsetRange(this.start + offset, this.endExclusive);
}
public deltaEnd(offset: number): OffsetRange {
return new OffsetRange(this.start, this.endExclusive + offset);
}
public get length(): number {
return this.endExclusive - this.start;
}
@@ -90,6 +98,10 @@ export class OffsetRange {
}
return undefined;
}
public slice<T>(arr: T[]): T[] {
return arr.slice(this.start, this.endExclusive);
}
}
export class OffsetRangeSet {
@@ -155,6 +155,13 @@ export class SimpleLineRangeMapping {
public flip(): SimpleLineRangeMapping {
return new SimpleLineRangeMapping(this.modified, this.original);
}
public join(other: SimpleLineRangeMapping): SimpleLineRangeMapping {
return new SimpleLineRangeMapping(
this.original.join(other.original),
this.modified.join(other.modified),
);
}
}
export class MovedText {
@@ -177,7 +177,7 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
}
private computeMoves(changes: LineRangeMapping[], originalLines: string[], modifiedLines: string[], hashedOriginalLines: number[], hashedModifiedLines: number[], timeout: ITimeout, considerWhitespaceChanges: boolean): MovedText[] {
const moves: MovedText[] = [];
const moves: SimpleLineRangeMapping[] = [];
const deletions = changes
.filter(c => c.modifiedRange.isEmpty && c.originalRange.length >= 3)
.map(d => new LineRangeFragment(d.originalRange, originalLines, d));
@@ -199,17 +199,15 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
}
if (highestSimilarity > 0.90 && best) {
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
new OffsetRange(deletion.range.startLineNumber - 1, deletion.range.endLineNumberExclusive - 1),
new OffsetRange(best.range.startLineNumber - 1, best.range.endLineNumberExclusive - 1)
), timeout, considerWhitespaceChanges);
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines, true);
insertions.delete(best);
moves.push(new MovedText(new SimpleLineRangeMapping(deletion.range, best.range), mappings));
moves.push(new SimpleLineRangeMapping(deletion.range, best.range));
excludedChanges.add(deletion.source);
excludedChanges.add(best.source);
}
if (!timeout.isValid()) {
return [];
}
}
const original3LineHashes = new SetMap<string, { range: LineRange }>();
@@ -242,9 +240,6 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
let lastMappings: PossibleMapping[] = [];
for (let i = change.modifiedRange.startLineNumber; i < change.modifiedRange.endLineNumberExclusive - 2; i++) {
const key = `${hashedModifiedLines[i - 1]}:${hashedModifiedLines[i + 1 - 1]}:${hashedModifiedLines[i + 2 - 1]}`;
//const isWeakKey = (originalLines[i].trim().length + originalLines[i + 1].trim().length + originalLines[i + 2].trim().length < 20);
const currentModifiedRange = new LineRange(i, i + 3);
const nextMappings: PossibleMapping[] = [];
@@ -269,6 +264,10 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
});
lastMappings = nextMappings;
}
if (!timeout.isValid()) {
return [];
}
}
possibleMappings.sort(reverseOrder(compareBy(m => m.modifiedLineRange.length, numberComparator)));
@@ -291,19 +290,49 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
const modifiedLineRange = s;
const originalLineRange = s.delta(-diffOrigToMod);
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
new OffsetRange(originalLineRange.startLineNumber - 1, originalLineRange.endLineNumberExclusive - 1),
new OffsetRange(modifiedLineRange.startLineNumber - 1, modifiedLineRange.endLineNumberExclusive - 1)
), timeout, considerWhitespaceChanges);
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines, true);
moves.push(new MovedText(new SimpleLineRangeMapping(originalLineRange, modifiedLineRange), mappings));
moves.push(new SimpleLineRangeMapping(originalLineRange, modifiedLineRange));
modifiedSet.addRange(modifiedLineRange);
originalSet.addRange(originalLineRange);
}
}
return moves;
// join moves
moves.sort(compareBy(m => m.original.startLineNumber, numberComparator));
if (moves.length === 0) {
return [];
}
const joinedMoves = [moves[0]];
for (let i = 1; i < moves.length; i++) {
const last = joinedMoves[joinedMoves.length - 1];
const current = moves[i];
const originalDist = current.original.startLineNumber - last.original.endLineNumberExclusive;
const modifiedDist = current.modified.startLineNumber - last.modified.endLineNumberExclusive;
const currentMoveAfterLast = originalDist >= 0 && modifiedDist >= 0;
if (currentMoveAfterLast && originalDist <= 1 && modifiedDist <= 1) {
joinedMoves[joinedMoves.length - 1] = last.join(current);
continue;
}
const originalText = current.original.toOffsetRange().slice(originalLines).map(l => l.trim()).join('\n');
if (originalText.length <= 10) {
// Ignore small moves
continue;
}
joinedMoves.push(current);
}
const fullMoves = joinedMoves.map(m => {
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
m.original.toOffsetRange(),
m.modified.toOffsetRange(),
), timeout, considerWhitespaceChanges);
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines, true);
return new MovedText(m, mappings);
});
return fullMoves;
}
private refineDiff(originalLines: string[], modifiedLines: string[], diff: SequenceDiff, timeout: ITimeout, considerWhitespaceChanges: boolean): { mappings: RangeMapping[]; hitTimeout: boolean } {
@@ -659,7 +688,7 @@ export class LinesSliceCharSequence implements ISequence {
private readonly firstCharOffsetByLineMinusOne: number[] = [];
public readonly lineRange: OffsetRange;
// To account for trimming
private readonly offsetByLine: number[] = [];
private readonly additionalOffsetByLine: number[] = [];
constructor(public readonly lines: string[], lineRange: OffsetRange, public readonly considerWhitespaceChanges: boolean) {
// This slice has to have lineRange.length many \n! (otherwise diffing against an empty slice will be problematic)
@@ -687,7 +716,7 @@ export class LinesSliceCharSequence implements ISequence {
line = trimmedStartLine.trimEnd();
}
this.offsetByLine.push(offset);
this.additionalOffsetByLine.push(offset);
for (let i = 0; i < line.length; i++) {
this.elements.push(line.charCodeAt(i));
@@ -700,7 +729,7 @@ export class LinesSliceCharSequence implements ISequence {
}
}
// To account for the last line
this.offsetByLine.push(0);
this.additionalOffsetByLine.push(0);
}
toString() {
@@ -767,7 +796,7 @@ export class LinesSliceCharSequence implements ISequence {
}
const offsetOfFirstCharInLine = i === 0 ? 0 : this.firstCharOffsetByLineMinusOne[i - 1];
return new Position(this.lineRange.start + i + 1, offset - offsetOfFirstCharInLine + 1 + this.offsetByLine[i]);
return new Position(this.lineRange.start + i + 1, offset - offsetOfFirstCharInLine + 1 + this.additionalOffsetByLine[i]);
}
public translateRange(range: OffsetRange): Range {
@@ -805,9 +834,52 @@ export class LinesSliceCharSequence implements ISequence {
return this.translateOffset(range.endExclusive).lineNumber - this.translateOffset(range.start).lineNumber;
}
isStronglyEqual(offset1: number, offset2: number): boolean {
public isStronglyEqual(offset1: number, offset2: number): boolean {
return this.elements[offset1] === this.elements[offset2];
}
public extendToFullLines(range: OffsetRange): OffsetRange {
const firstIdx = findLastIdxMonotonous(this.firstCharOffsetByLineMinusOne, x => x <= range.start);
const lastIdx = findFirstIdxMonotonous(this.firstCharOffsetByLineMinusOne, x => range.endExclusive <= x);
const start = firstIdx === -1 ? 0 : this.firstCharOffsetByLineMinusOne[firstIdx];
const end = lastIdx === this.firstCharOffsetByLineMinusOne.length ? this.elements.length : this.firstCharOffsetByLineMinusOne[lastIdx];
return new OffsetRange(start, end);
}
}
/**
* @returns -1 if predicate is false for all items
*/
function findLastIdxMonotonous<T>(arr: T[], predicate: (item: T) => boolean): number {
let i = 0;
let j = arr.length;
while (i < j) {
const k = Math.floor((i + j) / 2);
if (predicate(arr[k])) {
i = k + 1;
} else {
j = k;
}
}
return i - 1;
}
/**
* @returns arr.length if predicate is false for all items
*/
function findFirstIdxMonotonous<T>(arr: T[], predicate: (item: T) => boolean): number {
let i = 0;
let j = arr.length;
while (i < j) {
const k = Math.floor((i + j) / 2);
if (predicate(arr[k])) {
j = k;
} else {
i = k + 1;
}
}
return i;
}
function isWordChar(charCode: number): boolean {