90 lines
3.4 KiB
TypeScript
90 lines
3.4 KiB
TypeScript
export type RevisionDiffRow = {
|
|
lineNumber: number | null;
|
|
text: string;
|
|
state: "same" | "changed" | "empty";
|
|
};
|
|
|
|
export const buildRevisionDiffRows = (left: string, right: string) => {
|
|
const leftLines = left.split("\n");
|
|
const rightLines = right.split("\n");
|
|
const leftLength = leftLines.length;
|
|
const rightLength = rightLines.length;
|
|
const matrix: number[][] = Array.from({ length: leftLength + 1 }, () => Array(rightLength + 1).fill(0));
|
|
|
|
for (let leftIndex = 1; leftIndex <= leftLength; leftIndex += 1) {
|
|
for (let rightIndex = 1; rightIndex <= rightLength; rightIndex += 1) {
|
|
matrix[leftIndex][rightIndex] = leftLines[leftIndex - 1] === rightLines[rightIndex - 1]
|
|
? matrix[leftIndex - 1][rightIndex - 1] + 1
|
|
: Math.max(matrix[leftIndex - 1][rightIndex], matrix[leftIndex][rightIndex - 1]);
|
|
}
|
|
}
|
|
|
|
type DiffAction =
|
|
| { type: "same"; left: string; right: string }
|
|
| { type: "removed"; line: string }
|
|
| { type: "added"; line: string };
|
|
const actions: DiffAction[] = [];
|
|
let leftIndex = leftLength;
|
|
let rightIndex = rightLength;
|
|
|
|
while (leftIndex > 0 || rightIndex > 0) {
|
|
if (leftIndex > 0 && rightIndex > 0 && leftLines[leftIndex - 1] === rightLines[rightIndex - 1]) {
|
|
actions.push({ type: "same", left: leftLines[leftIndex - 1], right: rightLines[rightIndex - 1] });
|
|
leftIndex -= 1;
|
|
rightIndex -= 1;
|
|
} else if (rightIndex > 0 && (leftIndex === 0 || matrix[leftIndex][rightIndex - 1] >= matrix[leftIndex - 1][rightIndex])) {
|
|
actions.push({ type: "added", line: rightLines[rightIndex - 1] });
|
|
rightIndex -= 1;
|
|
} else {
|
|
actions.push({ type: "removed", line: leftLines[leftIndex - 1] });
|
|
leftIndex -= 1;
|
|
}
|
|
}
|
|
actions.reverse();
|
|
|
|
const leftRows: RevisionDiffRow[] = [];
|
|
const rightRows: RevisionDiffRow[] = [];
|
|
let leftLineNumber = 1;
|
|
let rightLineNumber = 1;
|
|
let actionIndex = 0;
|
|
|
|
while (actionIndex < actions.length) {
|
|
const action = actions[actionIndex];
|
|
if (action.type === "same") {
|
|
leftRows.push({ lineNumber: leftLineNumber++, text: action.left, state: "same" });
|
|
rightRows.push({ lineNumber: rightLineNumber++, text: action.right, state: "same" });
|
|
actionIndex += 1;
|
|
continue;
|
|
}
|
|
|
|
const removedLines: string[] = [];
|
|
const addedLines: string[] = [];
|
|
while (actionIndex < actions.length && actions[actionIndex].type !== "same") {
|
|
const changedAction = actions[actionIndex];
|
|
if (changedAction.type === "removed") {
|
|
removedLines.push(changedAction.line);
|
|
} else if (changedAction.type === "added") {
|
|
addedLines.push(changedAction.line);
|
|
}
|
|
actionIndex += 1;
|
|
}
|
|
|
|
const changedBlockLength = Math.max(removedLines.length, addedLines.length);
|
|
for (let index = 0; index < changedBlockLength; index += 1) {
|
|
leftRows.push(index < removedLines.length
|
|
? { lineNumber: leftLineNumber++, text: removedLines[index], state: "changed" }
|
|
: { lineNumber: null, text: "", state: "empty" });
|
|
rightRows.push(index < addedLines.length
|
|
? { lineNumber: rightLineNumber++, text: addedLines[index], state: "changed" }
|
|
: { lineNumber: null, text: "", state: "empty" });
|
|
}
|
|
}
|
|
|
|
const changed = leftRows.reduce(
|
|
(total, row, index) => total + (row.state !== "same" || rightRows[index]?.state !== "same" ? 1 : 0),
|
|
0
|
|
);
|
|
|
|
return { changed, leftRows, rightRows };
|
|
};
|