Files
vscode/src/vs/editor/common/diff/linesDiffComputer.ts
T
2023-06-13 13:16:39 -05:00

181 lines
5.1 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { LineRange } from 'vs/editor/common/core/lineRange';
import { Range } from 'vs/editor/common/core/range';
export interface ILinesDiffComputer {
computeDiff(originalLines: string[], modifiedLines: string[], options: ILinesDiffComputerOptions): LinesDiff;
}
export interface ILinesDiffComputerOptions {
readonly ignoreTrimWhitespace: boolean;
readonly maxComputationTimeMs: number;
readonly computeMoves: boolean;
}
export class LinesDiff {
constructor(
readonly changes: readonly LineRangeMapping[],
/**
* Sorted by original line ranges.
* The original line ranges and the modified line ranges must be disjoint (but can be touching).
*/
readonly moves: readonly MovedText[],
/**
* Indicates if the time out was reached.
* In that case, the diffs might be an approximation and the user should be asked to rerun the diff with more time.
*/
readonly hitTimeout: boolean,
) {
}
}
/**
* Maps a line range in the original text model to a line range in the modified text model.
*/
export class LineRangeMapping {
public static inverse(mapping: readonly LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): LineRangeMapping[] {
const result: LineRangeMapping[] = [];
let lastOriginalEndLineNumber = 1;
let lastModifiedEndLineNumber = 1;
for (const m of mapping) {
const r = new LineRangeMapping(
new LineRange(lastOriginalEndLineNumber, m.originalRange.startLineNumber),
new LineRange(lastModifiedEndLineNumber, m.modifiedRange.startLineNumber),
undefined
);
if (!r.modifiedRange.isEmpty) {
result.push(r);
}
lastOriginalEndLineNumber = m.originalRange.endLineNumberExclusive;
lastModifiedEndLineNumber = m.modifiedRange.endLineNumberExclusive;
}
const r = new LineRangeMapping(
new LineRange(lastOriginalEndLineNumber, originalLineCount + 1),
new LineRange(lastModifiedEndLineNumber, modifiedLineCount + 1),
undefined
);
if (!r.modifiedRange.isEmpty) {
result.push(r);
}
return result;
}
/**
* The line range in the original text model.
*/
public readonly originalRange: LineRange;
/**
* The line range in the modified text model.
*/
public readonly modifiedRange: LineRange;
/**
* If inner changes have not been computed, this is set to undefined.
* Otherwise, it represents the character-level diff in this line range.
* The original range of each range mapping should be contained in the original line range (same for modified), exceptions are new-lines.
* Must not be an empty array.
*/
public readonly innerChanges: RangeMapping[] | undefined;
constructor(
originalRange: LineRange,
modifiedRange: LineRange,
innerChanges: RangeMapping[] | undefined,
) {
this.originalRange = originalRange;
this.modifiedRange = modifiedRange;
this.innerChanges = innerChanges;
}
public toString(): string {
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
}
public get changedLineCount() {
return Math.max(this.originalRange.length, this.modifiedRange.length);
}
public flip(): LineRangeMapping {
return new LineRangeMapping(this.modifiedRange, this.originalRange, this.innerChanges?.map(c => c.flip()));
}
}
/**
* Maps a range in the original text model to a range in the modified text model.
*/
export class RangeMapping {
/**
* The original range.
*/
readonly originalRange: Range;
/**
* The modified range.
*/
readonly modifiedRange: Range;
constructor(
originalRange: Range,
modifiedRange: Range,
) {
this.originalRange = originalRange;
this.modifiedRange = modifiedRange;
}
public toString(): string {
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
}
public flip(): RangeMapping {
return new RangeMapping(this.modifiedRange, this.originalRange);
}
}
export class SimpleLineRangeMapping {
constructor(
public readonly originalRange: LineRange,
public readonly modifiedRange: LineRange,
) {
}
public toString(): string {
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
}
public flip(): SimpleLineRangeMapping {
return new SimpleLineRangeMapping(this.modifiedRange, this.originalRange);
}
}
export class MovedText {
public readonly lineRangeMapping: SimpleLineRangeMapping;
/**
* The diff from the original text to the moved text.
* Must be contained in the original/modified line range.
* Can be empty if the text didn't change (only moved).
*/
public readonly changes: readonly LineRangeMapping[];
constructor(
lineRangeMapping: SimpleLineRangeMapping,
changes: readonly LineRangeMapping[],
) {
this.lineRangeMapping = lineRangeMapping;
this.changes = changes;
}
public flip(): MovedText {
return new MovedText(this.lineRangeMapping.flip(), this.changes.map(c => c.flip()));
}
}