import { ExtendedPatch, SimplePatch, toExtendedPatches } from "./extended"; import { Diff } from "./typeboxDiff"; import fastDiff from "fast-diff"; type Op = | { op: "add"; index: number; value: string } | { op: "remove"; index: number; length: number } | { op: "replace"; index: number; value: string; length: number }; export function diffString(original: string, updated: string): SimplePatch[] { const diffResult = fastDiff(original, updated); const ops: Op[] = []; let index = 0; for (const [type, value] of diffResult) { switch (type) { case fastDiff.INSERT: ops.push({ op: "add", index, value }); index += value.length; break; case fastDiff.DELETE: ops.push({ op: "remove", index, length: value.length }); break; case fastDiff.EQUAL: index += value.length; break; } } // Combine adjacent add and remove operations into replace for (let i = 0; i < ops.length - 1; i++) { const current = ops[i]; const next = ops[i + 1]; if ( current.op === "remove" && next.op === "add" && current.index === next.index ) { ops.splice(i, 2, { op: "replace", index: current.index, value: next.value, length: current.length, }); } } return ops.map(({ index, ...op }) => ({ ...op, path: [index], })); } export function diff( original: any, updated: any, { useExtendedPatches = false } = {} ): [ExtendedPatch[], ExtendedPatch[]] { if (typeof original === "string" && typeof updated === "string") { return [diffString(original, updated), diffString(updated, original)]; } const patches = Diff(original, updated); const inversePatches = Diff(updated, original); if (!useExtendedPatches) return [patches, inversePatches]; return [ toExtendedPatches(original, patches), toExtendedPatches(updated, inversePatches), ]; }