mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-13 15:35:20 +01:00
Joins consecutive moves that are close together
This commit is contained in:
committed by
Henning Dieterichs
parent
026f38a82c
commit
d5f5ad201d
@@ -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];
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user