@pierre/diffs 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CodeView.d.ts +2 -4
- package/dist/components/CodeView.d.ts.map +1 -1
- package/dist/components/CodeView.js +23 -12
- package/dist/components/CodeView.js.map +1 -1
- package/dist/components/File.d.ts +3 -0
- package/dist/components/File.d.ts.map +1 -1
- package/dist/components/File.js +10 -0
- package/dist/components/File.js.map +1 -1
- package/dist/components/FileDiff.d.ts +19 -1
- package/dist/components/FileDiff.d.ts.map +1 -1
- package/dist/components/FileDiff.js +51 -15
- package/dist/components/FileDiff.js.map +1 -1
- package/dist/components/VirtualizedFile.d.ts.map +1 -1
- package/dist/components/VirtualizedFile.js +18 -19
- package/dist/components/VirtualizedFile.js.map +1 -1
- package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
- package/dist/components/VirtualizedFileDiff.js +25 -22
- package/dist/components/VirtualizedFileDiff.js.map +1 -1
- package/dist/constants.d.ts +1 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -3
- package/dist/constants.js.map +1 -1
- package/dist/editor/editor.d.ts.map +1 -1
- package/dist/editor/editor.js +21 -10
- package/dist/editor/editor.js.map +1 -1
- package/dist/editor/textMeasure.d.ts.map +1 -1
- package/dist/editor/textMeasure.js +27 -3
- package/dist/editor/textMeasure.js.map +1 -1
- package/dist/highlighter/languages/attachResolvedLanguages.js +8 -1
- package/dist/highlighter/languages/attachResolvedLanguages.js.map +1 -1
- package/dist/highlighter/languages/registerCustomLanguage.d.ts +3 -1
- package/dist/highlighter/languages/registerCustomLanguage.d.ts.map +1 -1
- package/dist/highlighter/languages/registerCustomLanguage.js +3 -1
- package/dist/highlighter/languages/registerCustomLanguage.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
- package/dist/react/utils/useFileDiffInstance.js +47 -11
- package/dist/react/utils/useFileDiffInstance.js.map +1 -1
- package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
- package/dist/renderers/DiffHunksRenderer.js +20 -13
- package/dist/renderers/DiffHunksRenderer.js.map +1 -1
- package/dist/renderers/FileRenderer.d.ts.map +1 -1
- package/dist/renderers/FileRenderer.js +30 -11
- package/dist/renderers/FileRenderer.js.map +1 -1
- package/dist/sprite.d.ts +2 -2
- package/dist/sprite.d.ts.map +1 -1
- package/dist/sprite.js +57 -0
- package/dist/sprite.js.map +1 -1
- package/dist/utils/editSessionHunks.d.ts +5 -4
- package/dist/utils/editSessionHunks.d.ts.map +1 -1
- package/dist/utils/editSessionHunks.js +32 -9
- package/dist/utils/editSessionHunks.js.map +1 -1
- package/dist/utils/parsePatchFiles.d.ts.map +1 -1
- package/dist/utils/parsePatchFiles.js +21 -13
- package/dist/utils/parsePatchFiles.js.map +1 -1
- package/dist/utils/parseQuotedDiffFileName.d.ts +8 -0
- package/dist/utils/parseQuotedDiffFileName.d.ts.map +1 -0
- package/dist/utils/parseQuotedDiffFileName.js +54 -0
- package/dist/utils/parseQuotedDiffFileName.js.map +1 -0
- package/dist/utils/realignChangeContent.d.ts +13 -10
- package/dist/utils/realignChangeContent.d.ts.map +1 -1
- package/dist/utils/realignChangeContent.js +23 -19
- package/dist/utils/realignChangeContent.js.map +1 -1
- package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
- package/dist/worker/WorkerPoolManager.js +40 -38
- package/dist/worker/WorkerPoolManager.js.map +1 -1
- package/dist/worker/worker-portable.js +8 -1
- package/dist/worker/worker-portable.js.map +1 -1
- package/dist/worker/worker.js +8 -1
- package/dist/worker/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -26,11 +26,12 @@ declare function normalizeEditorLines(lines: string[]): string[];
|
|
|
26
26
|
*/
|
|
27
27
|
declare function findDivergenceCore(deletionLines: string[], additionLines: string[]): DivergenceCore | undefined;
|
|
28
28
|
/**
|
|
29
|
-
* Rebuild
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Rebuild session regions from old/current lines and the previous change blocks.
|
|
30
|
+
* A full parse supplies changes, preserving existing blank positions while
|
|
31
|
+
* sliding new or edited blank changes up for the live editor. Previous line text
|
|
32
|
+
* distinguishes edits to an insertion from untouched blocks of the same size.
|
|
32
33
|
*/
|
|
33
|
-
declare function rebuildSessionHunks(diff: FileDiffMetadata, parseDiffOptions?: CreatePatchOptionsNonabortable): SessionRegionChange | undefined;
|
|
34
|
+
declare function rebuildSessionHunks(diff: FileDiffMetadata, parseDiffOptions?: CreatePatchOptionsNonabortable, getPreviousAdditionLine?: (index: number) => string | undefined): SessionRegionChange | undefined;
|
|
34
35
|
/**
|
|
35
36
|
* Keep a cheap content-only path when a same-line-count pass cannot alter the
|
|
36
37
|
* canonical blocks. Gap, ambiguous, or multi-region edits rebuild statelessly.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editSessionHunks.d.ts","names":[],"sources":["../../src/utils/editSessionHunks.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"editSessionHunks.d.ts","names":[],"sources":["../../src/utils/editSessionHunks.ts"],"mappings":";;;UA+BiB;EACf;EACA;EACA;;UAGQ;EACR;EACA;;;UAIe;EACf,SAAS,MAAM;;;;;;;iBA2BD,qBAAqB;;;;;iBAWrB,mBACd,yBACA,0BACC;;;;;;;iBA4Ba,oBACd,MAAM,kBACN,mBAAmB,gCACnB,2BAA2B,uCAC1B;;;;;iBAuCa,yBACd,MAAM,kBACN,4BAA4B,kBAC5B,mBAAmB,gCACnB,wBAAwB,8BACvB;;iBAiIa,kCACd,eAAe,YAAY,sBAC3B,QAAQ,sBACP,YAAY;;;;;;;KA6BH,wBAAwB,eAAe;;iBAGnC,wBACd,MAAM,kBACN,eAAe,YAAY,sBAC3B,oCACC;;;;;;;iBAoDa,4BACd,MAAM,kBACN,SAAS,yBACR,YAAY;;;;;;;;iBAgDC,yBACd,MAAM,kBACN,mBAAmB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getHunkSideStartBoundary } from "./getHunkSideBoundaries.js";
|
|
2
2
|
import { getExpandedRegion, getTrailingExpandedRegion } from "./virtualDiffLayout.js";
|
|
3
|
+
import { slideBlankBoundaryBlocksUp } from "./realignChangeContent.js";
|
|
3
4
|
import { parseDiffFromFile } from "./parseDiffFromFile.js";
|
|
4
5
|
import { offsetHunkContent, preserveTrailingEditorBlankLine, recomputeDiffHunks, recomputeDiffHunksForEdit, recomputeDiffRenderLineCounts, recomputeHunkRenderLineCounts, syncHunkNoEOFCRFromFullFile } from "./updateDiffHunks.js";
|
|
5
6
|
//#region src/utils/editSessionHunks.ts
|
|
@@ -35,11 +36,12 @@ function findDivergenceCore(deletionLines, additionLines) {
|
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
|
-
* Rebuild
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* Rebuild session regions from old/current lines and the previous change blocks.
|
|
40
|
+
* A full parse supplies changes, preserving existing blank positions while
|
|
41
|
+
* sliding new or edited blank changes up for the live editor. Previous line text
|
|
42
|
+
* distinguishes edits to an insertion from untouched blocks of the same size.
|
|
41
43
|
*/
|
|
42
|
-
function rebuildSessionHunks(diff, parseDiffOptions) {
|
|
44
|
+
function rebuildSessionHunks(diff, parseDiffOptions, getPreviousAdditionLine) {
|
|
43
45
|
const previousHunks = diff.hunks;
|
|
44
46
|
const editorAdditionLines = diff.additionLines;
|
|
45
47
|
const canonicalAdditionLines = normalizeEditorLines(editorAdditionLines);
|
|
@@ -47,7 +49,7 @@ function rebuildSessionHunks(diff, parseDiffOptions) {
|
|
|
47
49
|
...diff,
|
|
48
50
|
additionLines: canonicalAdditionLines
|
|
49
51
|
};
|
|
50
|
-
const plans = buildRegionPlans(previousHunks,
|
|
52
|
+
const plans = buildRegionPlans(previousHunks, parseSessionChangeBlocks(canonicalDiff, parseDiffOptions, getPreviousAdditionLine), diff.deletionLines.length);
|
|
51
53
|
const nextHunks = buildRegionHunks(canonicalDiff, plans);
|
|
52
54
|
diff.additionLines = canonicalAdditionLines;
|
|
53
55
|
diff.hunks = nextHunks;
|
|
@@ -62,8 +64,10 @@ function rebuildSessionHunks(diff, parseDiffOptions) {
|
|
|
62
64
|
* canonical blocks. Gap, ambiguous, or multi-region edits rebuild statelessly.
|
|
63
65
|
*/
|
|
64
66
|
function applySessionChangedLines(diff, changedAdditionLineIndexes, parseDiffOptions, previousAdditionLines) {
|
|
65
|
-
const
|
|
67
|
+
const changedLines = new Set(changedAdditionLineIndexes);
|
|
68
|
+
const lines = Array.from(changedLines).filter((line) => line >= 0 && line < diff.additionLines.length).sort((a, b) => a - b);
|
|
66
69
|
if (lines.length === 0) return;
|
|
70
|
+
const getPreviousAdditionLine = (index) => changedLines.has(index) ? previousAdditionLines?.get(index) : diff.additionLines[index];
|
|
67
71
|
const { hunks } = diff;
|
|
68
72
|
let regionIndex;
|
|
69
73
|
let hunkIndex = 0;
|
|
@@ -75,7 +79,7 @@ function applySessionChangedLines(diff, changedAdditionLineIndexes, parseDiffOpt
|
|
|
75
79
|
}
|
|
76
80
|
const hunk = hunks[hunkIndex];
|
|
77
81
|
const start = hunk == null ? void 0 : getHunkAdditionStart(hunk);
|
|
78
|
-
if (start == null || line < start || regionIndex != null && regionIndex !== hunkIndex) return rebuildSessionHunks(diff, parseDiffOptions);
|
|
82
|
+
if (start == null || line < start || regionIndex != null && regionIndex !== hunkIndex) return rebuildSessionHunks(diff, parseDiffOptions, getPreviousAdditionLine);
|
|
79
83
|
regionIndex = hunkIndex;
|
|
80
84
|
}
|
|
81
85
|
if (regionIndex == null) return;
|
|
@@ -83,7 +87,7 @@ function applySessionChangedLines(diff, changedAdditionLineIndexes, parseDiffOpt
|
|
|
83
87
|
diff.editSessionDirty = true;
|
|
84
88
|
return;
|
|
85
89
|
}
|
|
86
|
-
return rebuildSessionHunks(diff, parseDiffOptions);
|
|
90
|
+
return rebuildSessionHunks(diff, parseDiffOptions, getPreviousAdditionLine);
|
|
87
91
|
}
|
|
88
92
|
function canRetainCanonicalBlocks(diff, changedLines, regionIndex, previousAdditionLines, parseDiffOptions) {
|
|
89
93
|
if (previousAdditionLines == null || parseDiffOptions?.ignoreWhitespace === true || parseDiffOptions?.stripTrailingCr === true) return false;
|
|
@@ -210,7 +214,7 @@ function finishEditSessionForDiff(diff, parseDiffOptions) {
|
|
|
210
214
|
Object.assign(diff, diff.additionLines.length <= 1 && diff.additionLines.join("") === "" ? recomputeDiffHunks(diff, parseDiffOptions) : recomputeDiffHunksForEdit(diff, parseDiffOptions));
|
|
211
215
|
return true;
|
|
212
216
|
}
|
|
213
|
-
function
|
|
217
|
+
function parseSessionChangeBlocks(diff, parseDiffOptions, getPreviousAdditionLine) {
|
|
214
218
|
if (findDivergenceCore(diff.deletionLines, diff.additionLines) == null) return [];
|
|
215
219
|
const parsed = parseDiffFromFile({
|
|
216
220
|
name: diff.prevName ?? diff.name,
|
|
@@ -220,10 +224,24 @@ function parseCanonicalChangeBlocks(diff, parseDiffOptions) {
|
|
|
220
224
|
contents: diff.additionLines.join(""),
|
|
221
225
|
lang: diff.lang
|
|
222
226
|
}, parseDiffOptions);
|
|
227
|
+
let previousBlocks;
|
|
228
|
+
const resolveSlide = (block, maxSlide) => {
|
|
229
|
+
previousBlocks ??= collectPureChangeBlocks(diff.hunks);
|
|
230
|
+
for (let offset = 0; offset <= maxSlide; offset++) {
|
|
231
|
+
const previous = previousBlocks.get(block.deletionLineIndex - offset);
|
|
232
|
+
if (previous == null || previous.additions !== block.additions || previous.deletions !== block.deletions) continue;
|
|
233
|
+
if (block.additions > 0 && getPreviousAdditionLine != null) {
|
|
234
|
+
for (let line = 0; line < block.additions; line++) if (getPreviousAdditionLine(previous.additionLineIndex + line) !== parsed.additionLines[block.additionLineIndex + line]) return maxSlide;
|
|
235
|
+
}
|
|
236
|
+
return offset;
|
|
237
|
+
}
|
|
238
|
+
return maxSlide;
|
|
239
|
+
};
|
|
223
240
|
const blocks = [];
|
|
224
241
|
let coveredAdditions = 0;
|
|
225
242
|
let coveredDeletions = 0;
|
|
226
243
|
for (const hunk of parsed.hunks) {
|
|
244
|
+
slideBlankBoundaryBlocksUp(hunk, parsed, resolveSlide);
|
|
227
245
|
const contextLines = hunk.additionCount > 0 ? hunk.additionLineIndex - coveredAdditions : hunk.deletionLineIndex - coveredDeletions;
|
|
228
246
|
coveredAdditions += contextLines;
|
|
229
247
|
coveredDeletions += contextLines;
|
|
@@ -243,6 +261,11 @@ function parseCanonicalChangeBlocks(diff, parseDiffOptions) {
|
|
|
243
261
|
}
|
|
244
262
|
return blocks;
|
|
245
263
|
}
|
|
264
|
+
function collectPureChangeBlocks(hunks) {
|
|
265
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
266
|
+
for (const hunk of hunks) for (const content of hunk.hunkContent) if (content.type === "change" && (content.additions === 0 || content.deletions === 0)) blocks.set(content.deletionLineIndex, content);
|
|
267
|
+
return blocks;
|
|
268
|
+
}
|
|
246
269
|
function buildRegionPlans(previousHunks, blocks, deletionLineCount) {
|
|
247
270
|
const previousPlans = previousHunks.map((hunk, index) => {
|
|
248
271
|
const deletionStart = getHunkDeletionStart(hunk);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editSessionHunks.js","names":[],"sources":["../../src/utils/editSessionHunks.ts"],"sourcesContent":["import type { CreatePatchOptionsNonabortable } from 'diff';\n\nimport type {\n ChangeContent,\n ContextContent,\n FileDiffMetadata,\n Hunk,\n HunkExpansionRegion,\n} from '../types';\nimport { getHunkSideStartBoundary } from './getHunkSideBoundaries';\nimport { parseDiffFromFile } from './parseDiffFromFile';\nimport {\n offsetHunkContent,\n preserveTrailingEditorBlankLine,\n recomputeDiffHunks,\n recomputeDiffHunksForEdit,\n recomputeDiffRenderLineCounts,\n recomputeHunkRenderLineCounts,\n syncHunkNoEOFCRFromFullFile,\n} from './updateDiffHunks';\nimport {\n getExpandedRegion,\n getTrailingExpandedRegion,\n} from './virtualDiffLayout';\n\n// While an editor is attached to a FileDiff, each hunk is a persistent region\n// identified by its old-side range. Structural passes rebuild those regions\n// from one canonical old/current diff; a reverted region remains as context so\n// its rows keep rendering until the session-exit recompute.\n\nexport interface DivergenceCore {\n start: number;\n deletionEnd: number;\n additionEnd: number;\n}\n\ninterface PreviousRegionSpan {\n firstIndex: number;\n lastIndex: number;\n}\n\n/** Maps rebuilt regions back to the previous skeleton for expansion remapping. */\nexport interface SessionRegionChange {\n regions: Array<PreviousRegionSpan | undefined>;\n}\n\ninterface RegionBounds {\n additionStart: number;\n additionEnd: number;\n deletionStart: number;\n deletionEnd: number;\n}\n\ninterface RegionPlan {\n deletionStart: number;\n deletionEnd: number;\n blocks: ChangeContent[];\n previousSpan: PreviousRegionSpan | undefined;\n}\n\nconst deletionLineSetCache = new WeakMap<\n FileDiffMetadata,\n { lines: string[]; set: Set<string> }\n>();\n\n/**\n * Drops the editor document's phantom trailing empty line (a document ending\n * in a newline exposes one extra empty line the parsed diff never contains)\n * so session line arrays compare like parse-derived ones.\n */\nexport function normalizeEditorLines(lines: string[]): string[] {\n if (lines.length > 1 && lines[lines.length - 1] === '') {\n return lines.slice(0, -1);\n }\n return lines;\n}\n\n/**\n * Find the complete old/current divergence core. The old side is immutable\n * during an edit session, so this result needs no prior-pass snapshot.\n */\nexport function findDivergenceCore(\n deletionLines: string[],\n additionLines: string[]\n): DivergenceCore | undefined {\n const maxStart = Math.min(deletionLines.length, additionLines.length);\n let start = 0;\n while (start < maxStart && deletionLines[start] === additionLines[start]) {\n start++;\n }\n let deletionEnd = deletionLines.length;\n let additionEnd = additionLines.length;\n while (\n deletionEnd > start &&\n additionEnd > start &&\n deletionLines[deletionEnd - 1] === additionLines[additionEnd - 1]\n ) {\n deletionEnd--;\n additionEnd--;\n }\n if (start === deletionEnd && start === additionEnd) {\n return undefined;\n }\n return { start, deletionEnd, additionEnd };\n}\n\n/**\n * Rebuild the session skeleton as a pure function of the immutable old lines,\n * current new lines, and old-side ranges of the previous regions. One\n * canonical parse supplies the same change blocks that session exit will use.\n */\nexport function rebuildSessionHunks(\n diff: FileDiffMetadata,\n parseDiffOptions?: CreatePatchOptionsNonabortable\n): SessionRegionChange | undefined {\n const previousHunks = diff.hunks;\n const editorAdditionLines = diff.additionLines;\n const canonicalAdditionLines = normalizeEditorLines(editorAdditionLines);\n const canonicalDiff =\n canonicalAdditionLines === editorAdditionLines\n ? diff\n : { ...diff, additionLines: canonicalAdditionLines };\n const blocks = parseCanonicalChangeBlocks(canonicalDiff, parseDiffOptions);\n const plans = buildRegionPlans(\n previousHunks,\n blocks,\n diff.deletionLines.length\n );\n\n const nextHunks = buildRegionHunks(canonicalDiff, plans);\n diff.additionLines = canonicalAdditionLines;\n diff.hunks = nextHunks;\n diff.editSessionDirty = true;\n finalizeSessionHunks(diff);\n preserveTrailingEditorBlankLine(diff, editorAdditionLines);\n const layoutChanged = hasRegionOrSplitLayoutChanged(\n previousHunks,\n diff.hunks\n );\n if (!layoutChanged) {\n return undefined;\n }\n return { regions: plans.map((plan) => plan.previousSpan) };\n}\n\n/**\n * Keep a cheap content-only path when a same-line-count pass cannot alter the\n * canonical blocks. Gap, ambiguous, or multi-region edits rebuild statelessly.\n */\nexport function applySessionChangedLines(\n diff: FileDiffMetadata,\n changedAdditionLineIndexes: Iterable<number>,\n parseDiffOptions?: CreatePatchOptionsNonabortable,\n previousAdditionLines?: ReadonlyMap<number, string>\n): SessionRegionChange | undefined {\n const lines = Array.from(new Set(changedAdditionLineIndexes))\n .filter((line) => line >= 0 && line < diff.additionLines.length)\n .sort((a, b) => a - b);\n if (lines.length === 0) {\n return undefined;\n }\n\n const { hunks } = diff;\n let regionIndex: number | undefined;\n let hunkIndex = 0;\n for (const line of lines) {\n while (hunkIndex < hunks.length) {\n const hunk = hunks[hunkIndex];\n const start = getHunkAdditionStart(hunk);\n if (line < start + hunk.additionCount) break;\n hunkIndex++;\n }\n const hunk = hunks[hunkIndex];\n const start = hunk == null ? undefined : getHunkAdditionStart(hunk);\n if (\n start == null ||\n line < start ||\n (regionIndex != null && regionIndex !== hunkIndex)\n ) {\n return rebuildSessionHunks(diff, parseDiffOptions);\n }\n regionIndex = hunkIndex;\n }\n\n if (regionIndex == null) {\n return undefined;\n }\n if (\n canRetainCanonicalBlocks(\n diff,\n lines,\n regionIndex,\n previousAdditionLines,\n parseDiffOptions\n )\n ) {\n diff.editSessionDirty = true;\n return undefined;\n }\n return rebuildSessionHunks(diff, parseDiffOptions);\n}\n\n// Existing balanced change blocks remain canonical when every edited line was\n// unmatched before and stays unmatched now: the old/new equality matrix is\n// unchanged, and similarity realignment never reorders a balanced block.\nfunction canRetainCanonicalBlocks(\n diff: FileDiffMetadata,\n changedLines: number[],\n regionIndex: number,\n previousAdditionLines: ReadonlyMap<number, string> | undefined,\n parseDiffOptions: CreatePatchOptionsNonabortable | undefined\n): boolean {\n if (\n previousAdditionLines == null ||\n parseDiffOptions?.ignoreWhitespace === true ||\n parseDiffOptions?.stripTrailingCr === true\n ) {\n return false;\n }\n const deletionLineSet = getDeletionLineSet(diff);\n const hunk = diff.hunks[regionIndex];\n if (hunk.hunkContent.some(isPureChange)) {\n return false;\n }\n for (const line of changedLines) {\n const previousLine = previousAdditionLines.get(line);\n const additionLine = diff.additionLines[line];\n if (\n previousLine == null ||\n additionLine == null ||\n deletionLineSet.has(previousLine) ||\n deletionLineSet.has(additionLine)\n ) {\n return false;\n }\n let insideBalancedChange = false;\n for (const content of hunk.hunkContent) {\n if (\n content.type === 'change' &&\n content.additions === content.deletions &&\n line >= content.additionLineIndex &&\n line < content.additionLineIndex + content.additions\n ) {\n insideBalancedChange = true;\n break;\n }\n }\n if (!insideBalancedChange) {\n return false;\n }\n }\n return true;\n}\n\nfunction getDeletionLineSet(diff: FileDiffMetadata): Set<string> {\n const cached = deletionLineSetCache.get(diff);\n if (cached?.lines === diff.deletionLines) {\n return cached.set;\n }\n const set = new Set(diff.deletionLines);\n deletionLineSetCache.set(diff, { lines: diff.deletionLines, set });\n return set;\n}\n\nfunction isPureChange(\n content: ContextContent | ChangeContent | undefined\n): boolean {\n return (\n content?.type === 'change' &&\n (content.additions === 0 || content.deletions === 0)\n );\n}\n\n/** Preserve expansion at the surviving outer edges of rebuilt old-side gaps. */\nexport function remapExpandedHunksForRegionChange(\n expandedHunks: Map<number, HunkExpansionRegion>,\n change: SessionRegionChange\n): Map<number, HunkExpansionRegion> {\n const remapped = new Map<number, HunkExpansionRegion>();\n const { regions } = change;\n for (let key = 0; key <= regions.length; key++) {\n const previous = regions[key - 1];\n const next = regions[key];\n const fromStartSource =\n key === 0\n ? expandedHunks.get(0)\n : previous == null\n ? undefined\n : expandedHunks.get(previous.lastIndex + 1);\n const fromEndSource =\n next == null ? undefined : expandedHunks.get(next.firstIndex);\n const fromStart = fromStartSource?.fromStart ?? 0;\n const fromEnd = fromEndSource?.fromEnd ?? 0;\n if (fromStart > 0 || fromEnd > 0) {\n remapped.set(key, { fromStart, fromEnd });\n }\n }\n return remapped;\n}\n\n/**\n * An expanded gap-edge slice in old-side (deletion-line) coordinates as a\n * `[start, end)` range. Old-side coordinates survive the exit recompute\n * unchanged — edits only touch the new side — so these anchor best-effort\n * expansion preservation across the recompute.\n */\nexport type ExpansionAnchorRange = [start: number, end: number];\n\n/** Snapshot the expanded gap-edge slices before the exit recompute. */\nexport function captureExpansionAnchors(\n diff: FileDiffMetadata,\n expandedHunks: Map<number, HunkExpansionRegion>,\n collapsedContextThreshold: number\n): ExpansionAnchorRange[] {\n const anchors: ExpansionAnchorRange[] = [];\n if (diff.isPartial) {\n return anchors;\n }\n for (const [hunkIndex, hunk] of diff.hunks.entries()) {\n const region = getExpandedRegion({\n isPartial: diff.isPartial,\n rangeSize: hunk.collapsedBefore,\n expandedHunks,\n hunkIndex,\n collapsedContextThreshold,\n });\n // Gaps at or below the threshold render on their own; only explicit\n // expansion state needs preserving.\n if (region.rangeSize <= collapsedContextThreshold) {\n continue;\n }\n const gapEnd = getHunkDeletionStart(hunk);\n const gapStart = gapEnd - region.rangeSize;\n if (region.fromStart > 0) {\n anchors.push([gapStart, gapStart + region.fromStart]);\n }\n if (region.fromEnd > 0) {\n anchors.push([gapEnd - region.fromEnd, gapEnd]);\n }\n }\n const trailingRegion = getTrailingExpandedRegion({\n fileDiff: diff,\n hunkIndex: diff.hunks.length - 1,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix: 'captureExpansionAnchors',\n });\n if (\n trailingRegion != null &&\n trailingRegion.fromStart > 0 &&\n trailingRegion.rangeSize > collapsedContextThreshold\n ) {\n const lastHunk = diff.hunks[diff.hunks.length - 1];\n const gapStart = getHunkDeletionStart(lastHunk) + lastHunk.deletionCount;\n anchors.push([gapStart, gapStart + trailingRegion.fromStart]);\n }\n return anchors;\n}\n\n/**\n * Rebuild gap expansion state against the recomputed hunks: for each new\n * gap, an anchor touching the gap's start edge restores `fromStart`, one\n * touching its end edge restores `fromEnd`, and anchors for gaps that no\n * longer exist drop.\n */\nexport function rebuildExpansionFromAnchors(\n diff: FileDiffMetadata,\n anchors: ExpansionAnchorRange[]\n): Map<number, HunkExpansionRegion> {\n const rebuilt = new Map<number, HunkExpansionRegion>();\n if (anchors.length === 0) {\n return rebuilt;\n }\n const applyGap = (key: number, gapStart: number, gapEnd: number) => {\n if (gapEnd <= gapStart) {\n return;\n }\n let fromStart = 0;\n let fromEnd = 0;\n for (const [start, end] of anchors) {\n if (end <= gapStart || start >= gapEnd) {\n continue;\n }\n if (start <= gapStart) {\n fromStart = Math.max(fromStart, Math.min(end, gapEnd) - gapStart);\n }\n if (end >= gapEnd) {\n fromEnd = Math.max(fromEnd, gapEnd - Math.max(start, gapStart));\n }\n }\n if (fromStart > 0 || fromEnd > 0) {\n rebuilt.set(key, { fromStart, fromEnd });\n }\n };\n for (const [hunkIndex, hunk] of diff.hunks.entries()) {\n const gapEnd = getHunkDeletionStart(hunk);\n applyGap(hunkIndex, gapEnd - Math.max(hunk.collapsedBefore, 0), gapEnd);\n }\n const lastHunk = diff.hunks[diff.hunks.length - 1];\n if (lastHunk != null && !diff.isPartial && diff.deletionLines.length > 0) {\n applyGap(\n diff.hunks.length,\n getHunkDeletionStart(lastHunk) + lastHunk.deletionCount,\n diff.deletionLines.length\n );\n }\n return rebuilt;\n}\n\n/**\n * While editing, hunk updates keep a lightweight session-specific shape and\n * mark the diff with `editSessionDirty`. Called at session end, this\n * recomputes the hunks in full from the diff's current lines — the same\n * hunks a non-session edit would have produced — and clears the flag.\n * Returns true when a recompute ran.\n */\nexport function finishEditSessionForDiff(\n diff: FileDiffMetadata,\n parseDiffOptions?: CreatePatchOptionsNonabortable\n): boolean {\n if (diff.editSessionDirty !== true) {\n return false;\n }\n delete diff.editSessionDirty;\n // The empty editor row only hosts a caret; it is not file content after exit.\n Object.assign(\n diff,\n diff.additionLines.length <= 1 && diff.additionLines.join('') === ''\n ? recomputeDiffHunks(diff, parseDiffOptions)\n : recomputeDiffHunksForEdit(diff, parseDiffOptions)\n );\n return true;\n}\n\n// Parse the complete old/current files once with the same context policy as\n// session exit. Equal boundary lines affect Myers tie-breaking, while parsed\n// context is required for the blank-run alignment post-pass; only canonical\n// change blocks are retained. Running cursors normalize unified N,0 indexes.\nfunction parseCanonicalChangeBlocks(\n diff: FileDiffMetadata,\n parseDiffOptions?: CreatePatchOptionsNonabortable\n): ChangeContent[] {\n if (findDivergenceCore(diff.deletionLines, diff.additionLines) == null) {\n return [];\n }\n const parsed = parseDiffFromFile(\n {\n name: diff.prevName ?? diff.name,\n contents: diff.deletionLines.join(''),\n },\n {\n name: diff.name,\n contents: diff.additionLines.join(''),\n lang: diff.lang,\n },\n parseDiffOptions\n );\n\n const blocks: ChangeContent[] = [];\n let coveredAdditions = 0;\n let coveredDeletions = 0;\n for (const hunk of parsed.hunks) {\n const contextLines =\n hunk.additionCount > 0\n ? hunk.additionLineIndex - coveredAdditions\n : hunk.deletionLineIndex - coveredDeletions;\n coveredAdditions += contextLines;\n coveredDeletions += contextLines;\n for (const content of hunk.hunkContent) {\n if (content.type === 'context') {\n coveredAdditions += content.lines;\n coveredDeletions += content.lines;\n continue;\n }\n const block = offsetHunkContent(content, 0, 0) as ChangeContent;\n if (block.additions === 0) {\n block.additionLineIndex = coveredAdditions;\n }\n if (block.deletions === 0) {\n block.deletionLineIndex = coveredDeletions;\n }\n blocks.push(block);\n coveredAdditions += block.additions;\n coveredDeletions += block.deletions;\n }\n }\n return blocks;\n}\n\n// Co-walk canonical blocks and previous old-side regions. A block touching a\n// region grows it; touching several merges them; a block wholly in a gap gets\n// its own region. Pure insertions/deletions absorb one adjacent context line\n// when available so the region stays renderable on both sides and after undo.\nfunction buildRegionPlans(\n previousHunks: Hunk[],\n blocks: ChangeContent[],\n deletionLineCount: number\n): RegionPlan[] {\n const previousPlans = previousHunks.map((hunk, index): RegionPlan => {\n const deletionStart = getHunkDeletionStart(hunk);\n return {\n deletionStart,\n deletionEnd: deletionStart + hunk.deletionCount,\n blocks: [],\n previousSpan: { firstIndex: index, lastIndex: index },\n };\n });\n const plans: RegionPlan[] = [];\n let previousIndex = 0;\n\n for (const block of blocks) {\n const blockStart = block.deletionLineIndex;\n const blockEnd = blockStart + block.deletions;\n while (\n previousIndex < previousPlans.length &&\n previousPlans[previousIndex].deletionEnd < blockStart\n ) {\n plans.push(previousPlans[previousIndex]);\n previousIndex++;\n }\n\n let plan =\n plans.length > 0 &&\n blockTouchesRegion(blockStart, blockEnd, plans[plans.length - 1])\n ? plans.pop()\n : undefined;\n while (\n previousIndex < previousPlans.length &&\n previousPlans[previousIndex].deletionStart <= blockEnd\n ) {\n plan = mergeRegionPlans(plan, previousPlans[previousIndex]);\n previousIndex++;\n }\n\n if (plan == null) {\n let deletionStart = blockStart;\n let deletionEnd = blockEnd;\n if (\n (block.deletions === 0 || block.additions === 0) &&\n deletionLineCount > block.deletions\n ) {\n const previousEnd = plans[plans.length - 1]?.deletionEnd ?? 0;\n const nextStart =\n previousPlans[previousIndex]?.deletionStart ?? deletionLineCount;\n if (blockStart > previousEnd) {\n deletionStart--;\n } else if (blockEnd < nextStart) {\n deletionEnd++;\n }\n }\n plan = {\n deletionStart,\n deletionEnd,\n blocks: [],\n previousSpan: undefined,\n };\n }\n plan.deletionStart = Math.min(plan.deletionStart, blockStart);\n plan.deletionEnd = Math.max(plan.deletionEnd, blockEnd);\n plan.blocks.push(block);\n plans.push(plan);\n }\n\n while (previousIndex < previousPlans.length) {\n plans.push(previousPlans[previousIndex]);\n previousIndex++;\n }\n return plans;\n}\n\nfunction blockTouchesRegion(\n blockStart: number,\n blockEnd: number,\n region: RegionPlan\n): boolean {\n return blockStart <= region.deletionEnd && blockEnd >= region.deletionStart;\n}\n\nfunction mergeRegionPlans(\n target: RegionPlan | undefined,\n source: RegionPlan\n): RegionPlan {\n if (target == null) {\n return source;\n }\n target.deletionStart = Math.min(target.deletionStart, source.deletionStart);\n target.deletionEnd = Math.max(target.deletionEnd, source.deletionEnd);\n target.blocks.push(...source.blocks);\n if (source.previousSpan != null) {\n target.previousSpan ??= { ...source.previousSpan };\n target.previousSpan.firstIndex = Math.min(\n target.previousSpan.firstIndex,\n source.previousSpan.firstIndex\n );\n target.previousSpan.lastIndex = Math.max(\n target.previousSpan.lastIndex,\n source.previousSpan.lastIndex\n );\n }\n return target;\n}\n\n// One paired-context walk constructs every region's new-side range. There is\n// no downstream coordinate shifting: each boundary is derived from the\n// canonical blocks that precede it.\nfunction buildRegionHunks(diff: FileDiffMetadata, plans: RegionPlan[]): Hunk[] {\n const hunks: Hunk[] = [];\n let deletionCursor = 0;\n let additionCursor = 0;\n for (const plan of plans) {\n const contextBefore = plan.deletionStart - deletionCursor;\n if (contextBefore < 0) {\n throw new Error('buildRegionHunks: overlapping old-side regions');\n }\n deletionCursor += contextBefore;\n additionCursor += contextBefore;\n const additionStart = additionCursor;\n const hunkContent: Array<ContextContent | ChangeContent> = [];\n\n for (const canonicalBlock of plan.blocks) {\n const deletionContext = canonicalBlock.deletionLineIndex - deletionCursor;\n const additionContext = canonicalBlock.additionLineIndex - additionCursor;\n if (deletionContext < 0 || deletionContext !== additionContext) {\n throw new Error('buildRegionHunks: canonical block context mismatch');\n }\n pushContext(hunkContent, deletionContext, additionCursor, deletionCursor);\n deletionCursor += deletionContext;\n additionCursor += additionContext;\n hunkContent.push({ ...canonicalBlock });\n deletionCursor += canonicalBlock.deletions;\n additionCursor += canonicalBlock.additions;\n }\n\n const trailingContext = plan.deletionEnd - deletionCursor;\n if (trailingContext < 0) {\n throw new Error('buildRegionHunks: block exceeds its old-side region');\n }\n pushContext(hunkContent, trailingContext, additionCursor, deletionCursor);\n deletionCursor += trailingContext;\n additionCursor += trailingContext;\n hunks.push(\n createRegionHunk(\n diff,\n {\n additionStart,\n additionEnd: additionCursor,\n deletionStart: plan.deletionStart,\n deletionEnd: plan.deletionEnd,\n },\n hunkContent\n )\n );\n }\n\n if (\n diff.deletionLines.length - deletionCursor !==\n diff.additionLines.length - additionCursor\n ) {\n throw new Error('buildRegionHunks: trailing context mismatch');\n }\n return hunks;\n}\n\nfunction createRegionHunk(\n diff: FileDiffMetadata,\n bounds: RegionBounds,\n hunkContent: Array<ContextContent | ChangeContent>\n): Hunk {\n const additionCount = bounds.additionEnd - bounds.additionStart;\n const deletionCount = bounds.deletionEnd - bounds.deletionStart;\n let additionLines = 0;\n let deletionLines = 0;\n for (const content of hunkContent) {\n if (content.type === 'change') {\n additionLines += content.additions;\n deletionLines += content.deletions;\n }\n }\n const hunk: Hunk = {\n collapsedBefore: 0,\n additionStart: getUnifiedStart(bounds.additionStart, additionCount),\n additionCount,\n additionLines,\n additionLineIndex: getUnifiedLineIndex(bounds.additionStart, additionCount),\n deletionStart: getUnifiedStart(bounds.deletionStart, deletionCount),\n deletionCount,\n deletionLines,\n deletionLineIndex: getUnifiedLineIndex(bounds.deletionStart, deletionCount),\n hunkContent,\n hunkSpecs: `@@ -${getUnifiedStart(bounds.deletionStart, deletionCount)},${deletionCount} +${getUnifiedStart(bounds.additionStart, additionCount)},${additionCount} @@`,\n splitLineStart: 0,\n splitLineCount: 0,\n unifiedLineStart: 0,\n unifiedLineCount: 0,\n noEOFCRAdditions: false,\n noEOFCRDeletions: false,\n };\n recomputeHunkRenderLineCounts(hunk);\n return hunk;\n}\n\nfunction pushContext(\n hunkContent: Array<ContextContent | ChangeContent>,\n lines: number,\n additionLineIndex: number,\n deletionLineIndex: number\n): void {\n if (lines > 0) {\n hunkContent.push({\n type: 'context',\n lines,\n additionLineIndex,\n deletionLineIndex,\n });\n }\n}\n\nfunction hasRegionOrSplitLayoutChanged(\n previous: Hunk[],\n next: Hunk[]\n): boolean {\n if (previous.length !== next.length) {\n return true;\n }\n for (let index = 0; index < previous.length; index++) {\n const previousHunk = previous[index];\n const nextHunk = next[index];\n if (\n getHunkDeletionStart(previousHunk) !== getHunkDeletionStart(nextHunk) ||\n previousHunk.deletionCount !== nextHunk.deletionCount ||\n getHunkAdditionStart(previousHunk) !== getHunkAdditionStart(nextHunk) ||\n previousHunk.additionCount !== nextHunk.additionCount ||\n previousHunk.splitLineCount !== nextHunk.splitLineCount ||\n !haveSameSplitRowMapping(previousHunk, nextHunk)\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction haveSameSplitRowMapping(previous: Hunk, next: Hunk): boolean {\n const previousRows = iterateSplitRowMapping(previous);\n const nextRows = iterateSplitRowMapping(next);\n while (true) {\n const previousRow = previousRows.next();\n const nextRow = nextRows.next();\n if (previousRow.done === true || nextRow.done === true) {\n return previousRow.done === nextRow.done;\n }\n if (\n previousRow.value[0] !== nextRow.value[0] ||\n previousRow.value[1] !== nextRow.value[1]\n ) {\n return false;\n }\n }\n}\n\nfunction* iterateSplitRowMapping(\n hunk: Hunk\n): Generator<\n [deletionLine: number | undefined, additionLine: number | undefined]\n> {\n for (const content of hunk.hunkContent) {\n if (content.type === 'context') {\n for (let offset = 0; offset < content.lines; offset++) {\n yield [\n content.deletionLineIndex + offset,\n content.additionLineIndex + offset,\n ];\n }\n continue;\n }\n const rowCount = Math.max(content.deletions, content.additions);\n for (let offset = 0; offset < rowCount; offset++) {\n yield [\n offset < content.deletions\n ? content.deletionLineIndex + offset\n : undefined,\n offset < content.additions\n ? content.additionLineIndex + offset\n : undefined,\n ];\n }\n }\n}\n\nfunction getHunkAdditionStart(hunk: Hunk): number {\n return getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount);\n}\n\nfunction getHunkDeletionStart(hunk: Hunk): number {\n return getHunkSideStartBoundary(hunk.deletionStart, hunk.deletionCount);\n}\n\nfunction getUnifiedStart(lineIndex: number, count: number): number {\n return count === 0 ? lineIndex : lineIndex + 1;\n}\n\nfunction getUnifiedLineIndex(lineIndex: number, count: number): number {\n return count === 0 ? lineIndex - 1 : lineIndex;\n}\n\nfunction finalizeSessionHunks(diff: FileDiffMetadata): void {\n recomputeDiffRenderLineCounts(diff);\n for (let index = 0; index < diff.hunks.length; index++) {\n syncHunkNoEOFCRFromFullFile(diff, index);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"editSessionHunks.js","names":[],"sources":["../../src/utils/editSessionHunks.ts"],"sourcesContent":["import type { CreatePatchOptionsNonabortable } from 'diff';\n\nimport type {\n ChangeContent,\n ContextContent,\n FileDiffMetadata,\n Hunk,\n HunkExpansionRegion,\n} from '../types';\nimport { getHunkSideStartBoundary } from './getHunkSideBoundaries';\nimport { parseDiffFromFile } from './parseDiffFromFile';\nimport { slideBlankBoundaryBlocksUp } from './realignChangeContent';\nimport {\n offsetHunkContent,\n preserveTrailingEditorBlankLine,\n recomputeDiffHunks,\n recomputeDiffHunksForEdit,\n recomputeDiffRenderLineCounts,\n recomputeHunkRenderLineCounts,\n syncHunkNoEOFCRFromFullFile,\n} from './updateDiffHunks';\nimport {\n getExpandedRegion,\n getTrailingExpandedRegion,\n} from './virtualDiffLayout';\n\n// While an editor is attached to a FileDiff, each hunk is a persistent region\n// identified by its old-side range. Structural passes rebuild those regions\n// from one canonical old/current diff; a reverted region remains as context so\n// its rows keep rendering until the session-exit recompute.\n\nexport interface DivergenceCore {\n start: number;\n deletionEnd: number;\n additionEnd: number;\n}\n\ninterface PreviousRegionSpan {\n firstIndex: number;\n lastIndex: number;\n}\n\n/** Maps rebuilt regions back to the previous skeleton for expansion remapping. */\nexport interface SessionRegionChange {\n regions: Array<PreviousRegionSpan | undefined>;\n}\n\ninterface RegionBounds {\n additionStart: number;\n additionEnd: number;\n deletionStart: number;\n deletionEnd: number;\n}\n\ninterface RegionPlan {\n deletionStart: number;\n deletionEnd: number;\n blocks: ChangeContent[];\n previousSpan: PreviousRegionSpan | undefined;\n}\n\nconst deletionLineSetCache = new WeakMap<\n FileDiffMetadata,\n { lines: string[]; set: Set<string> }\n>();\n\n/**\n * Drops the editor document's phantom trailing empty line (a document ending\n * in a newline exposes one extra empty line the parsed diff never contains)\n * so session line arrays compare like parse-derived ones.\n */\nexport function normalizeEditorLines(lines: string[]): string[] {\n if (lines.length > 1 && lines[lines.length - 1] === '') {\n return lines.slice(0, -1);\n }\n return lines;\n}\n\n/**\n * Find the complete old/current divergence core. The old side is immutable\n * during an edit session, so this result needs no prior-pass snapshot.\n */\nexport function findDivergenceCore(\n deletionLines: string[],\n additionLines: string[]\n): DivergenceCore | undefined {\n const maxStart = Math.min(deletionLines.length, additionLines.length);\n let start = 0;\n while (start < maxStart && deletionLines[start] === additionLines[start]) {\n start++;\n }\n let deletionEnd = deletionLines.length;\n let additionEnd = additionLines.length;\n while (\n deletionEnd > start &&\n additionEnd > start &&\n deletionLines[deletionEnd - 1] === additionLines[additionEnd - 1]\n ) {\n deletionEnd--;\n additionEnd--;\n }\n if (start === deletionEnd && start === additionEnd) {\n return undefined;\n }\n return { start, deletionEnd, additionEnd };\n}\n\n/**\n * Rebuild session regions from old/current lines and the previous change blocks.\n * A full parse supplies changes, preserving existing blank positions while\n * sliding new or edited blank changes up for the live editor. Previous line text\n * distinguishes edits to an insertion from untouched blocks of the same size.\n */\nexport function rebuildSessionHunks(\n diff: FileDiffMetadata,\n parseDiffOptions?: CreatePatchOptionsNonabortable,\n getPreviousAdditionLine?: (index: number) => string | undefined\n): SessionRegionChange | undefined {\n const previousHunks = diff.hunks;\n const editorAdditionLines = diff.additionLines;\n const canonicalAdditionLines = normalizeEditorLines(editorAdditionLines);\n const canonicalDiff =\n canonicalAdditionLines === editorAdditionLines\n ? diff\n : { ...diff, additionLines: canonicalAdditionLines };\n const blocks = parseSessionChangeBlocks(\n canonicalDiff,\n parseDiffOptions,\n getPreviousAdditionLine\n );\n const plans = buildRegionPlans(\n previousHunks,\n blocks,\n diff.deletionLines.length\n );\n\n const nextHunks = buildRegionHunks(canonicalDiff, plans);\n diff.additionLines = canonicalAdditionLines;\n diff.hunks = nextHunks;\n diff.editSessionDirty = true;\n finalizeSessionHunks(diff);\n preserveTrailingEditorBlankLine(diff, editorAdditionLines);\n const layoutChanged = hasRegionOrSplitLayoutChanged(\n previousHunks,\n diff.hunks\n );\n if (!layoutChanged) {\n return undefined;\n }\n return { regions: plans.map((plan) => plan.previousSpan) };\n}\n\n/**\n * Keep a cheap content-only path when a same-line-count pass cannot alter the\n * canonical blocks. Gap, ambiguous, or multi-region edits rebuild statelessly.\n */\nexport function applySessionChangedLines(\n diff: FileDiffMetadata,\n changedAdditionLineIndexes: Iterable<number>,\n parseDiffOptions?: CreatePatchOptionsNonabortable,\n previousAdditionLines?: ReadonlyMap<number, string>\n): SessionRegionChange | undefined {\n const changedLines = new Set(changedAdditionLineIndexes);\n const lines = Array.from(changedLines)\n .filter((line) => line >= 0 && line < diff.additionLines.length)\n .sort((a, b) => a - b);\n if (lines.length === 0) {\n return undefined;\n }\n const getPreviousAdditionLine = (index: number) =>\n changedLines.has(index)\n ? previousAdditionLines?.get(index)\n : diff.additionLines[index];\n\n const { hunks } = diff;\n let regionIndex: number | undefined;\n let hunkIndex = 0;\n for (const line of lines) {\n while (hunkIndex < hunks.length) {\n const hunk = hunks[hunkIndex];\n const start = getHunkAdditionStart(hunk);\n if (line < start + hunk.additionCount) break;\n hunkIndex++;\n }\n const hunk = hunks[hunkIndex];\n const start = hunk == null ? undefined : getHunkAdditionStart(hunk);\n if (\n start == null ||\n line < start ||\n (regionIndex != null && regionIndex !== hunkIndex)\n ) {\n return rebuildSessionHunks(\n diff,\n parseDiffOptions,\n getPreviousAdditionLine\n );\n }\n regionIndex = hunkIndex;\n }\n\n if (regionIndex == null) {\n return undefined;\n }\n if (\n canRetainCanonicalBlocks(\n diff,\n lines,\n regionIndex,\n previousAdditionLines,\n parseDiffOptions\n )\n ) {\n diff.editSessionDirty = true;\n return undefined;\n }\n return rebuildSessionHunks(diff, parseDiffOptions, getPreviousAdditionLine);\n}\n\n// Existing balanced change blocks remain canonical when every edited line was\n// unmatched before and stays unmatched now: the old/new equality matrix is\n// unchanged, and similarity realignment never reorders a balanced block.\nfunction canRetainCanonicalBlocks(\n diff: FileDiffMetadata,\n changedLines: number[],\n regionIndex: number,\n previousAdditionLines: ReadonlyMap<number, string> | undefined,\n parseDiffOptions: CreatePatchOptionsNonabortable | undefined\n): boolean {\n if (\n previousAdditionLines == null ||\n parseDiffOptions?.ignoreWhitespace === true ||\n parseDiffOptions?.stripTrailingCr === true\n ) {\n return false;\n }\n const deletionLineSet = getDeletionLineSet(diff);\n const hunk = diff.hunks[regionIndex];\n if (hunk.hunkContent.some(isPureChange)) {\n return false;\n }\n for (const line of changedLines) {\n const previousLine = previousAdditionLines.get(line);\n const additionLine = diff.additionLines[line];\n if (\n previousLine == null ||\n additionLine == null ||\n deletionLineSet.has(previousLine) ||\n deletionLineSet.has(additionLine)\n ) {\n return false;\n }\n let insideBalancedChange = false;\n for (const content of hunk.hunkContent) {\n if (\n content.type === 'change' &&\n content.additions === content.deletions &&\n line >= content.additionLineIndex &&\n line < content.additionLineIndex + content.additions\n ) {\n insideBalancedChange = true;\n break;\n }\n }\n if (!insideBalancedChange) {\n return false;\n }\n }\n return true;\n}\n\nfunction getDeletionLineSet(diff: FileDiffMetadata): Set<string> {\n const cached = deletionLineSetCache.get(diff);\n if (cached?.lines === diff.deletionLines) {\n return cached.set;\n }\n const set = new Set(diff.deletionLines);\n deletionLineSetCache.set(diff, { lines: diff.deletionLines, set });\n return set;\n}\n\nfunction isPureChange(\n content: ContextContent | ChangeContent | undefined\n): boolean {\n return (\n content?.type === 'change' &&\n (content.additions === 0 || content.deletions === 0)\n );\n}\n\n/** Preserve expansion at the surviving outer edges of rebuilt old-side gaps. */\nexport function remapExpandedHunksForRegionChange(\n expandedHunks: Map<number, HunkExpansionRegion>,\n change: SessionRegionChange\n): Map<number, HunkExpansionRegion> {\n const remapped = new Map<number, HunkExpansionRegion>();\n const { regions } = change;\n for (let key = 0; key <= regions.length; key++) {\n const previous = regions[key - 1];\n const next = regions[key];\n const fromStartSource =\n key === 0\n ? expandedHunks.get(0)\n : previous == null\n ? undefined\n : expandedHunks.get(previous.lastIndex + 1);\n const fromEndSource =\n next == null ? undefined : expandedHunks.get(next.firstIndex);\n const fromStart = fromStartSource?.fromStart ?? 0;\n const fromEnd = fromEndSource?.fromEnd ?? 0;\n if (fromStart > 0 || fromEnd > 0) {\n remapped.set(key, { fromStart, fromEnd });\n }\n }\n return remapped;\n}\n\n/**\n * An expanded gap-edge slice in old-side (deletion-line) coordinates as a\n * `[start, end)` range. Old-side coordinates survive the exit recompute\n * unchanged — edits only touch the new side — so these anchor best-effort\n * expansion preservation across the recompute.\n */\nexport type ExpansionAnchorRange = [start: number, end: number];\n\n/** Snapshot the expanded gap-edge slices before the exit recompute. */\nexport function captureExpansionAnchors(\n diff: FileDiffMetadata,\n expandedHunks: Map<number, HunkExpansionRegion>,\n collapsedContextThreshold: number\n): ExpansionAnchorRange[] {\n const anchors: ExpansionAnchorRange[] = [];\n if (diff.isPartial) {\n return anchors;\n }\n for (const [hunkIndex, hunk] of diff.hunks.entries()) {\n const region = getExpandedRegion({\n isPartial: diff.isPartial,\n rangeSize: hunk.collapsedBefore,\n expandedHunks,\n hunkIndex,\n collapsedContextThreshold,\n });\n // Gaps at or below the threshold render on their own; only explicit\n // expansion state needs preserving.\n if (region.rangeSize <= collapsedContextThreshold) {\n continue;\n }\n const gapEnd = getHunkDeletionStart(hunk);\n const gapStart = gapEnd - region.rangeSize;\n if (region.fromStart > 0) {\n anchors.push([gapStart, gapStart + region.fromStart]);\n }\n if (region.fromEnd > 0) {\n anchors.push([gapEnd - region.fromEnd, gapEnd]);\n }\n }\n const trailingRegion = getTrailingExpandedRegion({\n fileDiff: diff,\n hunkIndex: diff.hunks.length - 1,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix: 'captureExpansionAnchors',\n });\n if (\n trailingRegion != null &&\n trailingRegion.fromStart > 0 &&\n trailingRegion.rangeSize > collapsedContextThreshold\n ) {\n const lastHunk = diff.hunks[diff.hunks.length - 1];\n const gapStart = getHunkDeletionStart(lastHunk) + lastHunk.deletionCount;\n anchors.push([gapStart, gapStart + trailingRegion.fromStart]);\n }\n return anchors;\n}\n\n/**\n * Rebuild gap expansion state against the recomputed hunks: for each new\n * gap, an anchor touching the gap's start edge restores `fromStart`, one\n * touching its end edge restores `fromEnd`, and anchors for gaps that no\n * longer exist drop.\n */\nexport function rebuildExpansionFromAnchors(\n diff: FileDiffMetadata,\n anchors: ExpansionAnchorRange[]\n): Map<number, HunkExpansionRegion> {\n const rebuilt = new Map<number, HunkExpansionRegion>();\n if (anchors.length === 0) {\n return rebuilt;\n }\n const applyGap = (key: number, gapStart: number, gapEnd: number) => {\n if (gapEnd <= gapStart) {\n return;\n }\n let fromStart = 0;\n let fromEnd = 0;\n for (const [start, end] of anchors) {\n if (end <= gapStart || start >= gapEnd) {\n continue;\n }\n if (start <= gapStart) {\n fromStart = Math.max(fromStart, Math.min(end, gapEnd) - gapStart);\n }\n if (end >= gapEnd) {\n fromEnd = Math.max(fromEnd, gapEnd - Math.max(start, gapStart));\n }\n }\n if (fromStart > 0 || fromEnd > 0) {\n rebuilt.set(key, { fromStart, fromEnd });\n }\n };\n for (const [hunkIndex, hunk] of diff.hunks.entries()) {\n const gapEnd = getHunkDeletionStart(hunk);\n applyGap(hunkIndex, gapEnd - Math.max(hunk.collapsedBefore, 0), gapEnd);\n }\n const lastHunk = diff.hunks[diff.hunks.length - 1];\n if (lastHunk != null && !diff.isPartial && diff.deletionLines.length > 0) {\n applyGap(\n diff.hunks.length,\n getHunkDeletionStart(lastHunk) + lastHunk.deletionCount,\n diff.deletionLines.length\n );\n }\n return rebuilt;\n}\n\n/**\n * While editing, hunk updates keep a lightweight session-specific shape and\n * mark the diff with `editSessionDirty`. Called at session end, this\n * recomputes the hunks in full from the diff's current lines — the same\n * hunks a non-session edit would have produced — and clears the flag.\n * Returns true when a recompute ran.\n */\nexport function finishEditSessionForDiff(\n diff: FileDiffMetadata,\n parseDiffOptions?: CreatePatchOptionsNonabortable\n): boolean {\n if (diff.editSessionDirty !== true) {\n return false;\n }\n delete diff.editSessionDirty;\n // The empty editor row only hosts a caret; it is not file content after exit.\n Object.assign(\n diff,\n diff.additionLines.length <= 1 && diff.additionLines.join('') === ''\n ? recomputeDiffHunks(diff, parseDiffOptions)\n : recomputeDiffHunksForEdit(diff, parseDiffOptions)\n );\n return true;\n}\n\n// Parse the complete old/current files once with the same context policy as\n// session exit, then slide new or edited blank changes before extracting blocks.\n// Use the parsed context window so a larger persistent session region cannot\n// extend the slide. Running cursors normalize unified N,0 indexes.\nfunction parseSessionChangeBlocks(\n diff: FileDiffMetadata,\n parseDiffOptions: CreatePatchOptionsNonabortable | undefined,\n getPreviousAdditionLine: ((index: number) => string | undefined) | undefined\n): ChangeContent[] {\n if (findDivergenceCore(diff.deletionLines, diff.additionLines) == null) {\n return [];\n }\n const parsed = parseDiffFromFile(\n {\n name: diff.prevName ?? diff.name,\n contents: diff.deletionLines.join(''),\n },\n {\n name: diff.name,\n contents: diff.additionLines.join(''),\n lang: diff.lang,\n },\n parseDiffOptions\n );\n\n // Built on the first blank block that qualifies to slide, so passes without\n // one never scan the previous skeleton.\n let previousBlocks: Map<number, ChangeContent> | undefined;\n // The parsed block sits at the bottom of its blank run. A previous block of\n // the same shape anywhere between there and the run's top is the same change\n // and keeps its position: offset 0 is untouched at the parsed position, and\n // `maxSlide` is one already slid to the top. A block with no previous\n // counterpart, or an insertion whose text changed in place, is new to this\n // pass and slides to the top.\n const resolveSlide = (block: ChangeContent, maxSlide: number): number => {\n previousBlocks ??= collectPureChangeBlocks(diff.hunks);\n for (let offset = 0; offset <= maxSlide; offset++) {\n const previous = previousBlocks.get(block.deletionLineIndex - offset);\n if (\n previous == null ||\n previous.additions !== block.additions ||\n previous.deletions !== block.deletions\n ) {\n continue;\n }\n // Old-side text is immutable. For insertions, compare the previous text\n // at its previous indexes because structural edits shift the new side.\n if (block.additions > 0 && getPreviousAdditionLine != null) {\n for (let line = 0; line < block.additions; line++) {\n if (\n getPreviousAdditionLine(previous.additionLineIndex + line) !==\n parsed.additionLines[block.additionLineIndex + line]\n ) {\n return maxSlide;\n }\n }\n }\n return offset;\n }\n return maxSlide;\n };\n\n const blocks: ChangeContent[] = [];\n let coveredAdditions = 0;\n let coveredDeletions = 0;\n for (const hunk of parsed.hunks) {\n slideBlankBoundaryBlocksUp(hunk, parsed, resolveSlide);\n const contextLines =\n hunk.additionCount > 0\n ? hunk.additionLineIndex - coveredAdditions\n : hunk.deletionLineIndex - coveredDeletions;\n coveredAdditions += contextLines;\n coveredDeletions += contextLines;\n for (const content of hunk.hunkContent) {\n if (content.type === 'context') {\n coveredAdditions += content.lines;\n coveredDeletions += content.lines;\n continue;\n }\n const block = offsetHunkContent(content, 0, 0) as ChangeContent;\n if (block.additions === 0) {\n block.additionLineIndex = coveredAdditions;\n }\n if (block.deletions === 0) {\n block.deletionLineIndex = coveredDeletions;\n }\n blocks.push(block);\n coveredAdditions += block.additions;\n coveredDeletions += block.deletions;\n }\n }\n return blocks;\n}\n\n// Pure insert/delete blocks keyed by old-side index. The old side is immutable\n// during a session, so that index identifies a block across passes while\n// new-side rows shift around it. Adjacent insert and delete runs parse as one\n// mixed block, so two pure blocks never share an index.\nfunction collectPureChangeBlocks(hunks: Hunk[]): Map<number, ChangeContent> {\n const blocks = new Map<number, ChangeContent>();\n for (const hunk of hunks) {\n for (const content of hunk.hunkContent) {\n if (\n content.type === 'change' &&\n (content.additions === 0 || content.deletions === 0)\n ) {\n blocks.set(content.deletionLineIndex, content);\n }\n }\n }\n return blocks;\n}\n\n// Co-walk canonical blocks and previous old-side regions. A block touching a\n// region grows it; touching several merges them; a block wholly in a gap gets\n// its own region. Pure insertions/deletions absorb one adjacent context line\n// when available so the region stays renderable on both sides and after undo.\nfunction buildRegionPlans(\n previousHunks: Hunk[],\n blocks: ChangeContent[],\n deletionLineCount: number\n): RegionPlan[] {\n const previousPlans = previousHunks.map((hunk, index): RegionPlan => {\n const deletionStart = getHunkDeletionStart(hunk);\n return {\n deletionStart,\n deletionEnd: deletionStart + hunk.deletionCount,\n blocks: [],\n previousSpan: { firstIndex: index, lastIndex: index },\n };\n });\n const plans: RegionPlan[] = [];\n let previousIndex = 0;\n\n for (const block of blocks) {\n const blockStart = block.deletionLineIndex;\n const blockEnd = blockStart + block.deletions;\n while (\n previousIndex < previousPlans.length &&\n previousPlans[previousIndex].deletionEnd < blockStart\n ) {\n plans.push(previousPlans[previousIndex]);\n previousIndex++;\n }\n\n let plan =\n plans.length > 0 &&\n blockTouchesRegion(blockStart, blockEnd, plans[plans.length - 1])\n ? plans.pop()\n : undefined;\n while (\n previousIndex < previousPlans.length &&\n previousPlans[previousIndex].deletionStart <= blockEnd\n ) {\n plan = mergeRegionPlans(plan, previousPlans[previousIndex]);\n previousIndex++;\n }\n\n if (plan == null) {\n let deletionStart = blockStart;\n let deletionEnd = blockEnd;\n if (\n (block.deletions === 0 || block.additions === 0) &&\n deletionLineCount > block.deletions\n ) {\n const previousEnd = plans[plans.length - 1]?.deletionEnd ?? 0;\n const nextStart =\n previousPlans[previousIndex]?.deletionStart ?? deletionLineCount;\n if (blockStart > previousEnd) {\n deletionStart--;\n } else if (blockEnd < nextStart) {\n deletionEnd++;\n }\n }\n plan = {\n deletionStart,\n deletionEnd,\n blocks: [],\n previousSpan: undefined,\n };\n }\n plan.deletionStart = Math.min(plan.deletionStart, blockStart);\n plan.deletionEnd = Math.max(plan.deletionEnd, blockEnd);\n plan.blocks.push(block);\n plans.push(plan);\n }\n\n while (previousIndex < previousPlans.length) {\n plans.push(previousPlans[previousIndex]);\n previousIndex++;\n }\n return plans;\n}\n\nfunction blockTouchesRegion(\n blockStart: number,\n blockEnd: number,\n region: RegionPlan\n): boolean {\n return blockStart <= region.deletionEnd && blockEnd >= region.deletionStart;\n}\n\nfunction mergeRegionPlans(\n target: RegionPlan | undefined,\n source: RegionPlan\n): RegionPlan {\n if (target == null) {\n return source;\n }\n target.deletionStart = Math.min(target.deletionStart, source.deletionStart);\n target.deletionEnd = Math.max(target.deletionEnd, source.deletionEnd);\n target.blocks.push(...source.blocks);\n if (source.previousSpan != null) {\n target.previousSpan ??= { ...source.previousSpan };\n target.previousSpan.firstIndex = Math.min(\n target.previousSpan.firstIndex,\n source.previousSpan.firstIndex\n );\n target.previousSpan.lastIndex = Math.max(\n target.previousSpan.lastIndex,\n source.previousSpan.lastIndex\n );\n }\n return target;\n}\n\n// One paired-context walk constructs every region's new-side range. There is\n// no downstream coordinate shifting: each boundary is derived from the\n// canonical blocks that precede it.\nfunction buildRegionHunks(diff: FileDiffMetadata, plans: RegionPlan[]): Hunk[] {\n const hunks: Hunk[] = [];\n let deletionCursor = 0;\n let additionCursor = 0;\n for (const plan of plans) {\n const contextBefore = plan.deletionStart - deletionCursor;\n if (contextBefore < 0) {\n throw new Error('buildRegionHunks: overlapping old-side regions');\n }\n deletionCursor += contextBefore;\n additionCursor += contextBefore;\n const additionStart = additionCursor;\n const hunkContent: Array<ContextContent | ChangeContent> = [];\n\n for (const canonicalBlock of plan.blocks) {\n const deletionContext = canonicalBlock.deletionLineIndex - deletionCursor;\n const additionContext = canonicalBlock.additionLineIndex - additionCursor;\n if (deletionContext < 0 || deletionContext !== additionContext) {\n throw new Error('buildRegionHunks: canonical block context mismatch');\n }\n pushContext(hunkContent, deletionContext, additionCursor, deletionCursor);\n deletionCursor += deletionContext;\n additionCursor += additionContext;\n hunkContent.push({ ...canonicalBlock });\n deletionCursor += canonicalBlock.deletions;\n additionCursor += canonicalBlock.additions;\n }\n\n const trailingContext = plan.deletionEnd - deletionCursor;\n if (trailingContext < 0) {\n throw new Error('buildRegionHunks: block exceeds its old-side region');\n }\n pushContext(hunkContent, trailingContext, additionCursor, deletionCursor);\n deletionCursor += trailingContext;\n additionCursor += trailingContext;\n hunks.push(\n createRegionHunk(\n diff,\n {\n additionStart,\n additionEnd: additionCursor,\n deletionStart: plan.deletionStart,\n deletionEnd: plan.deletionEnd,\n },\n hunkContent\n )\n );\n }\n\n if (\n diff.deletionLines.length - deletionCursor !==\n diff.additionLines.length - additionCursor\n ) {\n throw new Error('buildRegionHunks: trailing context mismatch');\n }\n return hunks;\n}\n\nfunction createRegionHunk(\n diff: FileDiffMetadata,\n bounds: RegionBounds,\n hunkContent: Array<ContextContent | ChangeContent>\n): Hunk {\n const additionCount = bounds.additionEnd - bounds.additionStart;\n const deletionCount = bounds.deletionEnd - bounds.deletionStart;\n let additionLines = 0;\n let deletionLines = 0;\n for (const content of hunkContent) {\n if (content.type === 'change') {\n additionLines += content.additions;\n deletionLines += content.deletions;\n }\n }\n const hunk: Hunk = {\n collapsedBefore: 0,\n additionStart: getUnifiedStart(bounds.additionStart, additionCount),\n additionCount,\n additionLines,\n additionLineIndex: getUnifiedLineIndex(bounds.additionStart, additionCount),\n deletionStart: getUnifiedStart(bounds.deletionStart, deletionCount),\n deletionCount,\n deletionLines,\n deletionLineIndex: getUnifiedLineIndex(bounds.deletionStart, deletionCount),\n hunkContent,\n hunkSpecs: `@@ -${getUnifiedStart(bounds.deletionStart, deletionCount)},${deletionCount} +${getUnifiedStart(bounds.additionStart, additionCount)},${additionCount} @@`,\n splitLineStart: 0,\n splitLineCount: 0,\n unifiedLineStart: 0,\n unifiedLineCount: 0,\n noEOFCRAdditions: false,\n noEOFCRDeletions: false,\n };\n recomputeHunkRenderLineCounts(hunk);\n return hunk;\n}\n\nfunction pushContext(\n hunkContent: Array<ContextContent | ChangeContent>,\n lines: number,\n additionLineIndex: number,\n deletionLineIndex: number\n): void {\n if (lines > 0) {\n hunkContent.push({\n type: 'context',\n lines,\n additionLineIndex,\n deletionLineIndex,\n });\n }\n}\n\nfunction hasRegionOrSplitLayoutChanged(\n previous: Hunk[],\n next: Hunk[]\n): boolean {\n if (previous.length !== next.length) {\n return true;\n }\n for (let index = 0; index < previous.length; index++) {\n const previousHunk = previous[index];\n const nextHunk = next[index];\n if (\n getHunkDeletionStart(previousHunk) !== getHunkDeletionStart(nextHunk) ||\n previousHunk.deletionCount !== nextHunk.deletionCount ||\n getHunkAdditionStart(previousHunk) !== getHunkAdditionStart(nextHunk) ||\n previousHunk.additionCount !== nextHunk.additionCount ||\n previousHunk.splitLineCount !== nextHunk.splitLineCount ||\n !haveSameSplitRowMapping(previousHunk, nextHunk)\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction haveSameSplitRowMapping(previous: Hunk, next: Hunk): boolean {\n const previousRows = iterateSplitRowMapping(previous);\n const nextRows = iterateSplitRowMapping(next);\n while (true) {\n const previousRow = previousRows.next();\n const nextRow = nextRows.next();\n if (previousRow.done === true || nextRow.done === true) {\n return previousRow.done === nextRow.done;\n }\n if (\n previousRow.value[0] !== nextRow.value[0] ||\n previousRow.value[1] !== nextRow.value[1]\n ) {\n return false;\n }\n }\n}\n\nfunction* iterateSplitRowMapping(\n hunk: Hunk\n): Generator<\n [deletionLine: number | undefined, additionLine: number | undefined]\n> {\n for (const content of hunk.hunkContent) {\n if (content.type === 'context') {\n for (let offset = 0; offset < content.lines; offset++) {\n yield [\n content.deletionLineIndex + offset,\n content.additionLineIndex + offset,\n ];\n }\n continue;\n }\n const rowCount = Math.max(content.deletions, content.additions);\n for (let offset = 0; offset < rowCount; offset++) {\n yield [\n offset < content.deletions\n ? content.deletionLineIndex + offset\n : undefined,\n offset < content.additions\n ? content.additionLineIndex + offset\n : undefined,\n ];\n }\n }\n}\n\nfunction getHunkAdditionStart(hunk: Hunk): number {\n return getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount);\n}\n\nfunction getHunkDeletionStart(hunk: Hunk): number {\n return getHunkSideStartBoundary(hunk.deletionStart, hunk.deletionCount);\n}\n\nfunction getUnifiedStart(lineIndex: number, count: number): number {\n return count === 0 ? lineIndex : lineIndex + 1;\n}\n\nfunction getUnifiedLineIndex(lineIndex: number, count: number): number {\n return count === 0 ? lineIndex - 1 : lineIndex;\n}\n\nfunction finalizeSessionHunks(diff: FileDiffMetadata): void {\n recomputeDiffRenderLineCounts(diff);\n for (let index = 0; index < diff.hunks.length; index++) {\n syncHunkNoEOFCRFromFullFile(diff, index);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsePatchFiles.d.ts","names":[],"sources":["../../src/utils/parsePatchFiles.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"parsePatchFiles.d.ts","names":[],"sources":["../../src/utils/parsePatchFiles.ts"],"mappings":";;iBAsCgB,aACd,cACA,yBACA,yBACC;UAiFO;EACR;EACA;EACA,UAAU;EACV,UAAU;EACV;;iBAGc,YACd,wBACA,UAAU,qBACT;;;;;;;;;;;;iBA8hBa,gBACd,cACA,yBACA,yBACC"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { COMMIT_METADATA_SPLIT, FILENAME_HEADER_REGEX, GIT_DIFF_FILE_BREAK_REGEX, INDEX_LINE_METADATA } from "../constants.js";
|
|
2
2
|
import { getHunkSideEndBoundary, getHunkSideStartBoundary } from "./getHunkSideBoundaries.js";
|
|
3
3
|
import { composeCacheKey } from "./composeCacheKey.js";
|
|
4
4
|
import { cleanLastNewline } from "./cleanLastNewline.js";
|
|
5
5
|
import { detachString, releaseStringDetachBuffer } from "./detachString.js";
|
|
6
|
+
import { parseQuotedDiffFileName } from "./parseQuotedDiffFileName.js";
|
|
6
7
|
import { realignChangeContentBySimilarity } from "./realignChangeContent.js";
|
|
7
8
|
//#region src/utils/parsePatchFiles.ts
|
|
9
|
+
const GIT_DIFF_HEADER_FILENAMES = /^diff --git ("a\/(?:[^"\\]|\\.)*"|a\/.+?) ("b\/(?:[^"\\]|\\.)*"|b\/.+?)$/;
|
|
8
10
|
function processPatch(data, cacheKeyPrefix, throwOnError) {
|
|
9
11
|
try {
|
|
10
12
|
return _processPatch(data, cacheKeyPrefix, throwOnError);
|
|
@@ -87,26 +89,27 @@ function _processFile(fileDiffString, { cacheKey, isGitDiff = GIT_DIFF_FILE_BREA
|
|
|
87
89
|
if (currentFile.deletionLines.length === 1 && oldFile?.contents === "") currentFile.deletionLines.length = 0;
|
|
88
90
|
for (const line of lines) {
|
|
89
91
|
if (line.startsWith("diff --git")) {
|
|
90
|
-
const filenameMatch = line.trim().match(
|
|
91
|
-
|
|
92
|
-
const name = filenameMatch?.[3] ?? filenameMatch?.[4];
|
|
93
|
-
if (prevName == null || name == null) {
|
|
92
|
+
const filenameMatch = line.trim().match(GIT_DIFF_HEADER_FILENAMES);
|
|
93
|
+
if (filenameMatch == null) {
|
|
94
94
|
if (throwOnError) throw Error("parsePatchContent: invalid git diff header");
|
|
95
95
|
else console.error("parsePatchContent: invalid git diff header", line);
|
|
96
96
|
continue;
|
|
97
97
|
}
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const prevName = decodeDiffFileName(filenameMatch[1], true);
|
|
99
|
+
const name = decodeDiffFileName(filenameMatch[2], true);
|
|
100
|
+
currentFile.name = detachString(name);
|
|
101
|
+
if (prevName !== name) currentFile.prevName = detachString(prevName);
|
|
100
102
|
continue;
|
|
101
103
|
}
|
|
102
|
-
const filenameMatch = line.startsWith("---") || line.startsWith("+++") ? line.match(
|
|
104
|
+
const filenameMatch = line.startsWith("---") || line.startsWith("+++") ? line.match(FILENAME_HEADER_REGEX) : null;
|
|
103
105
|
if (filenameMatch != null) {
|
|
104
|
-
const [, type,
|
|
106
|
+
const [, type, rawFileName] = filenameMatch;
|
|
107
|
+
const fileName = decodeDiffFileName(rawFileName, isGitDiff);
|
|
105
108
|
if (type === "---" && fileName !== "/dev/null") {
|
|
106
|
-
const detachedFileName = detachString(fileName
|
|
109
|
+
const detachedFileName = detachString(fileName);
|
|
107
110
|
currentFile.prevName = detachedFileName;
|
|
108
111
|
currentFile.name = detachedFileName;
|
|
109
|
-
} else if (type === "+++" && fileName !== "/dev/null") currentFile.name = detachString(fileName
|
|
112
|
+
} else if (type === "+++" && fileName !== "/dev/null") currentFile.name = detachString(fileName);
|
|
110
113
|
} else if (isGitDiff) {
|
|
111
114
|
if (line.startsWith("new mode ")) currentFile.mode = detachString(line.slice(8).trim());
|
|
112
115
|
if (line.startsWith("old mode ")) currentFile.prevMode = detachString(line.slice(8).trim());
|
|
@@ -128,8 +131,8 @@ function _processFile(fileDiffString, { cacheKey, isGitDiff = GIT_DIFF_FILE_BREA
|
|
|
128
131
|
if (newObjectId != null) currentFile.newObjectId = detachString(newObjectId);
|
|
129
132
|
if (mode != null) currentFile.mode = detachString(mode);
|
|
130
133
|
}
|
|
131
|
-
if (line.startsWith("rename from ")) currentFile.prevName = detachString(line.slice(
|
|
132
|
-
if (line.startsWith("rename to ")) currentFile.name = detachString(line.slice(
|
|
134
|
+
if (line.startsWith("rename from ") || line.startsWith("copy from ")) currentFile.prevName = detachString(decodeDiffFileName(line.slice(line.startsWith("rename") ? 12 : 10)));
|
|
135
|
+
if (line.startsWith("rename to ") || line.startsWith("copy to ")) currentFile.name = detachString(decodeDiffFileName(line.slice(line.startsWith("rename") ? 10 : 8)));
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
138
|
continue;
|
|
@@ -317,6 +320,11 @@ function parsePatchFiles(data, cacheKeyPrefix, throwOnError = false) {
|
|
|
317
320
|
}
|
|
318
321
|
return patches;
|
|
319
322
|
}
|
|
323
|
+
function decodeDiffFileName(value, stripGitPrefix = false) {
|
|
324
|
+
const rawName = value.trim();
|
|
325
|
+
const name = rawName.startsWith("\"") ? parseQuotedDiffFileName(rawName)?.fileName ?? rawName : rawName;
|
|
326
|
+
return stripGitPrefix && (name.startsWith("a/") || name.startsWith("b/")) ? name.slice(2) : name;
|
|
327
|
+
}
|
|
320
328
|
function hasCommitMetadataBoundary(data) {
|
|
321
329
|
return data.startsWith("From ") || data.includes("\nFrom ");
|
|
322
330
|
}
|