UNPKG

@pierre/diffs

Version:

236 lines (235 loc) 10.9 kB
import { getHunkSideEndBoundary, getHunkSideStartBoundary } from "./getHunkSideBoundaries.js"; import { hasTrailingContextMismatch } from "./virtualDiffLayout.js"; import { cleanLastNewline } from "./cleanLastNewline.js"; import { parseDiffFromFile } from "./parseDiffFromFile.js"; //#region src/utils/updateDiffHunks.ts /** Rebuilds all hunk metadata from the current deletion/addition line arrays. */ function recomputeDiffHunks(diff, parseDiffOptions) { const recomputed = parseDiffFromFile({ name: diff.prevName ?? diff.name, contents: diff.deletionLines.join("") }, { name: diff.name, contents: diff.additionLines.join(""), lang: diff.lang }, parseDiffOptions); return { hunks: recomputed.hunks, splitLineCount: recomputed.splitLineCount, unifiedLineCount: recomputed.unifiedLineCount, additionLines: recomputed.additionLines, deletionLines: recomputed.deletionLines, type: recomputed.type }; } function buildTopAlignedAdditionSentinel(lineCount, deletionContents) { const count = Math.max(lineCount, 1); let sentinel = Array.from({ length: count }, (_, index) => `${" ".repeat(index + 1)}\n`).join(""); if (sentinel === deletionContents) sentinel = Array.from({ length: count }, (_, index) => `\u0000${" ".repeat(index)}\n`).join(""); return sentinel; } function hasOnlyBlankAdditionContents(additionLines) { for (const line of additionLines) if (line.trim().length > 0) return false; return true; } function shouldTopAlignAdditionRecompute(diff, additionLines) { return additionLines.length > 0 && additionLines.length < diff.deletionLines.length && hasOnlyBlankAdditionContents(additionLines); } function recomputeTopAlignedAdditionDiff(diff, additionLines, parseDiffOptions) { const deletionContents = diff.deletionLines.join(""); const additionSentinel = buildTopAlignedAdditionSentinel(additionLines.length, deletionContents); const recomputed = parseDiffFromFile({ name: diff.prevName ?? diff.name, contents: deletionContents }, { name: diff.name, contents: additionSentinel, lang: diff.lang }, parseDiffOptions); return { hunks: recomputed.hunks, splitLineCount: recomputed.splitLineCount, unifiedLineCount: recomputed.unifiedLineCount, additionLines, deletionLines: recomputed.deletionLines, type: recomputed.type }; } function recomputeEmptyDocumentDiff(diff, parseDiffOptions) { return recomputeTopAlignedAdditionDiff(diff, [""], parseDiffOptions); } /** Rebuilds diff hunks after an edit, top-aligning sparse addition sides when needed. */ function recomputeDiffHunksForEdit(diff, parseDiffOptions) { if (diff.additionLines.length === 0) return recomputeEmptyDocumentDiff(diff, parseDiffOptions); if (shouldTopAlignAdditionRecompute(diff, diff.additionLines)) return recomputeTopAlignedAdditionDiff(diff, diff.additionLines, parseDiffOptions); const additionLines = diff.additionLines; const recomputed = recomputeDiffHunks(diff, parseDiffOptions); preserveTrailingEditorBlankLine(recomputed, additionLines); return recomputed; } function hasTrailingEditorBlankLine(additionLines) { return additionLines.length > 1 && additionLines.at(-1) === ""; } function preserveTrailingEditorBlankLine(recomputed, additionLines) { if (!hasTrailingEditorBlankLine(additionLines)) return; const extraLineCount = additionLines.length - recomputed.additionLines.length; if (extraLineCount <= 0) return; const extraAdditionLineIndex = recomputed.additionLines.length; const lastHunk = recomputed.hunks.at(-1); if (lastHunk == null) return; if (getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount) !== extraAdditionLineIndex) return; for (const content of lastHunk.hunkContent) if (content.type === "change" && content.additions < content.deletions && content.additionLineIndex + content.additions === extraAdditionLineIndex) { recomputed.additionLines = additionLines; content.additions += extraLineCount; lastHunk.additionCount += extraLineCount; lastHunk.additionLines += extraLineCount; recomputeDiffRenderLineCounts(recomputed); return; } } /** Updates hunk metadata after addition lines change; re-parses affected hunks only. */ function updateDiffHunks(diff, changedAdditionLineIndexes, parseDiffOptions) { if (diff.isPartial) return applyHunkUpdateResult(diff, recomputeDiffHunks(diff, parseDiffOptions)); if (diff.deletionLines.length !== diff.additionLines.length) return applyHunkUpdateResult(diff, recomputeDiffHunks(diff, parseDiffOptions)); const changedLines = Array.from(changedAdditionLineIndexes); if (changedLines.length === 0) return applyHunkUpdateResult(diff, { hunks: diff.hunks, splitLineCount: diff.splitLineCount, unifiedLineCount: diff.unifiedLineCount, type: diff.type }); for (const line of changedLines) { const additionLine = diff.additionLines[line]; const deletionLine = diff.deletionLines[line]; if (additionLine == null || deletionLine == null) return applyHunkUpdateResult(diff, recomputeDiffHunks(diff, parseDiffOptions)); if (cleanLastNewline(additionLine) === cleanLastNewline(deletionLine)) return applyHunkUpdateResult(diff, recomputeDiffHunks(diff, parseDiffOptions)); } const affectedHunkIndexes = getAffectedHunkIndexes(diff, changedLines); if (affectedHunkIndexes.size === 0) return applyHunkUpdateResult(diff, recomputeDiffHunks(diff, parseDiffOptions)); for (const hunkIndex of affectedHunkIndexes) if (!reparseHunkRegion(diff, hunkIndex, parseDiffOptions)) return applyHunkUpdateResult(diff, recomputeDiffHunks(diff, parseDiffOptions)); recomputeDiffRenderLineCounts(diff); if (hasTrailingContextMismatch(diff)) return applyHunkUpdateResult(diff, recomputeDiffHunks(diff, parseDiffOptions)); return applyHunkUpdateResult(diff, { hunks: diff.hunks, splitLineCount: diff.splitLineCount, unifiedLineCount: diff.unifiedLineCount, type: diff.type }); } function applyHunkUpdateResult(diff, result) { Object.assign(diff, result); return result; } function getAffectedHunkIndexes(diff, changedAdditionLineIndexes) { const indexes = /* @__PURE__ */ new Set(); for (const line of changedAdditionLineIndexes) { const hunkIndex = findHunkIndexForAdditionLine(diff, line); if (hunkIndex == null) return /* @__PURE__ */ new Set(); indexes.add(hunkIndex); } return indexes; } function findHunkIndexForAdditionLine(diff, line) { for (const [hunkIndex, hunk] of diff.hunks.entries()) { const end = hunk.additionLineIndex + hunk.additionCount; if (line >= hunk.additionLineIndex && line < end) return hunkIndex; } } function reparseHunkRegion(diff, hunkIndex, parseDiffOptions) { const hunk = diff.hunks[hunkIndex]; if (hunk == null) return false; const deletionSlice = diff.deletionLines.slice(hunk.deletionLineIndex, hunk.deletionLineIndex + hunk.deletionCount); const additionSlice = diff.additionLines.slice(hunk.additionLineIndex, hunk.additionLineIndex + hunk.additionCount); const reparsed = parseDiffFromFile({ name: diff.prevName ?? diff.name, contents: deletionSlice.join("") }, { name: diff.name, contents: additionSlice.join(""), lang: diff.lang }, { ...parseDiffOptions, context: 0 }); const reparsedHunk = reparsed.hunks[0]; if (reparsedHunk == null || reparsed.hunks.length !== 1) return false; applyReparsedHunk(hunk, reparsedHunk); syncHunkNoEOFCRFromFullFile(diff, hunkIndex); return true; } function syncHunkNoEOFCRFromFullFile(diff, hunkIndex) { const hunk = diff.hunks[hunkIndex]; if (hunk == null) return; if (!(hunkIndex === diff.hunks.length - 1)) { hunk.noEOFCRAdditions = false; hunk.noEOFCRDeletions = false; return; } const lastAdditionLine = diff.additionLines.at(-1); const lastDeletionLine = diff.deletionLines.at(-1); hunk.noEOFCRAdditions = lastAdditionLine != null && lastAdditionLine !== "" && !lastAdditionLine.endsWith("\n"); hunk.noEOFCRDeletions = lastDeletionLine != null && lastDeletionLine !== "" && !lastDeletionLine.endsWith("\n"); } function applyReparsedHunk(target, parsed) { const additionOffset = target.additionLineIndex; const deletionOffset = target.deletionLineIndex; target.hunkContent = parsed.hunkContent.map((content) => offsetHunkContent(content, additionOffset, deletionOffset)); target.additionLineIndex = additionOffset + parsed.additionLineIndex; target.additionStart = target.additionStart + parsed.additionLineIndex; target.additionCount = parsed.additionCount; target.additionLines = parsed.additionLines; if (parsed.deletionLineIndex >= 0) { target.deletionLineIndex = deletionOffset + parsed.deletionLineIndex; target.deletionStart = target.deletionStart + parsed.deletionLineIndex; } target.deletionCount = parsed.deletionCount; target.deletionLines = parsed.deletionLines; target.noEOFCRAdditions = parsed.noEOFCRAdditions; target.noEOFCRDeletions = parsed.noEOFCRDeletions; recomputeHunkRenderLineCounts(target); } function offsetHunkContent(content, additionOffset, deletionOffset) { return { ...content, additionLineIndex: content.additionLineIndex + additionOffset, deletionLineIndex: content.deletionLineIndex + deletionOffset }; } function recomputeHunkRenderLineCounts(hunk) { let splitLineCount = 0; let unifiedLineCount = 0; for (const content of hunk.hunkContent) if (content.type === "context") { splitLineCount += content.lines; unifiedLineCount += content.lines; } else { splitLineCount += Math.max(content.additions, content.deletions); unifiedLineCount += content.additions + content.deletions; } hunk.splitLineCount = splitLineCount; hunk.unifiedLineCount = unifiedLineCount; } function recomputeDiffRenderLineCounts(diff) { let splitTotal = 0; let unifiedTotal = 0; let lastHunkAdditionEnd = 0; for (const hunk of diff.hunks) { hunk.collapsedBefore = Math.max(getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount) - lastHunkAdditionEnd, 0); hunk.splitLineStart = splitTotal + hunk.collapsedBefore; hunk.unifiedLineStart = unifiedTotal + hunk.collapsedBefore; recomputeHunkRenderLineCounts(hunk); splitTotal += hunk.collapsedBefore + hunk.splitLineCount; unifiedTotal += hunk.collapsedBefore + hunk.unifiedLineCount; lastHunkAdditionEnd = getHunkSideEndBoundary(hunk.additionStart, hunk.additionCount); } if (diff.hunks.length > 0) { const lastHunk = diff.hunks[diff.hunks.length - 1]; const collapsedAfter = Math.max(diff.additionLines.length - getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount), 0); splitTotal += collapsedAfter; unifiedTotal += collapsedAfter; } diff.splitLineCount = splitTotal; diff.unifiedLineCount = unifiedTotal; } //#endregion export { offsetHunkContent, preserveTrailingEditorBlankLine, recomputeDiffHunks, recomputeDiffHunksForEdit, recomputeDiffRenderLineCounts, recomputeEmptyDocumentDiff, recomputeHunkRenderLineCounts, recomputeTopAlignedAdditionDiff, shouldTopAlignAdditionRecompute, syncHunkNoEOFCRFromFullFile, updateDiffHunks }; //# sourceMappingURL=updateDiffHunks.js.map