Files
wursor/packages/diff-engine/src/myers.ts
T
2026-04-25 22:52:30 +01:00

63 lines
1.8 KiB
TypeScript

// LCS-based O(nm) diff algorithm — correct and simple for component-sized inputs.
// Produces a minimal edit script as a sequence of equal/insert/delete operations.
export type EditType = 'equal' | 'insert' | 'delete';
export interface EditOp {
type: EditType;
lines: string[];
}
export function computeEditScript(before: string[], after: string[]): EditOp[] {
const n = before.length;
const m = after.length;
if (n === 0 && m === 0) return [];
if (n === 0) return [{ type: 'insert', lines: [...after] }];
if (m === 0) return [{ type: 'delete', lines: [...before] }];
// Build LCS DP table
const dp: number[][] = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= m; j++) {
dp[i]![j] =
before[i - 1] === after[j - 1]
? (dp[i - 1]![j - 1] ?? 0) + 1
: Math.max(dp[i - 1]![j] ?? 0, dp[i]![j - 1] ?? 0);
}
}
// Backtrack from (n, m) to (0, 0)
type RawOp = { type: EditType; line: string };
const raw: RawOp[] = [];
let i = n, j = m;
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && before[i - 1] === after[j - 1]) {
raw.push({ type: 'equal', line: before[i - 1]! });
i--; j--;
} else if (j > 0 && (i === 0 || (dp[i]![j - 1] ?? 0) >= (dp[i - 1]![j] ?? 0))) {
raw.push({ type: 'insert', line: after[j - 1]! });
j--;
} else {
raw.push({ type: 'delete', line: before[i - 1]! });
i--;
}
}
raw.reverse();
// Merge consecutive ops of the same type
const ops: EditOp[] = [];
for (const op of raw) {
const last = ops[ops.length - 1];
if (last && last.type === op.type) {
last.lines.push(op.line);
} else {
ops.push({ type: op.type, lines: [op.line] });
}
}
return ops;
}