import { DiffLine, DiffType } from "../index.js"; import { LineStream, matchLine } from "./util.js"; /** * https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/ * Invariants: * - new + same = newLines.length * - old + same = oldLinesCopy.length * ^ (above two guarantee that all lines get represented) * - Lines are always output in order, at least among old and new separately * - Old lines in a hunk are always output before the new lines */ export async function* streamDiff( oldLines: string[], newLines: LineStream, ): AsyncGenerator { const oldLinesCopy = [...oldLines]; // If one indentation mistake is made, others are likely. So we are more permissive about matching let seenIndentationMistake = false; let newLineResult = await newLines.next(); while (oldLinesCopy.length > 0 && !newLineResult.done) { const { matchIndex, isPerfectMatch, newLine } = matchLine( newLineResult.value, oldLinesCopy, seenIndentationMistake, ); if (!seenIndentationMistake && newLineResult.value !== newLine) { seenIndentationMistake = true; } let type: DiffType; const isNewLine = matchIndex === -1; if (isNewLine) { type = "new"; } else { // Insert all deleted lines before match for (let i = 0; i < matchIndex; i++) { yield { type: "old", line: oldLinesCopy.shift()! }; } type = isPerfectMatch ? "same" : "old"; } switch (type) { case "new": yield { type, line: newLine }; break; case "same": yield { type, line: oldLinesCopy.shift()! }; break; case "old": yield { type, line: oldLinesCopy.shift()! }; yield { type: "new", line: newLine }; break; default: console.error(`Error streaming diff, unrecognized diff type: ${type}`); } newLineResult = await newLines.next(); } // Once at the edge, only one choice if (newLineResult.done && oldLinesCopy.length > 0) { for (const oldLine of oldLinesCopy) { yield { type: "old", line: oldLine }; } } if (!newLineResult.done && oldLinesCopy.length === 0) { yield { type: "new", line: newLineResult.value }; for await (const newLine of newLines) { yield { type: "new", line: newLine }; } } }