@pierre/diffs 1.3.0-beta.8 → 1.3.0-rc.1

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.
Files changed (229) hide show
  1. package/dist/components/CodeView.d.ts +12 -12
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +51 -43
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +7 -2
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +36 -8
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +34 -6
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +198 -17
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/FileStream.d.ts.map +1 -1
  14. package/dist/components/FileStream.js +2 -1
  15. package/dist/components/FileStream.js.map +1 -1
  16. package/dist/components/VirtualizedFile.d.ts +2 -1
  17. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  18. package/dist/components/VirtualizedFile.js +9 -5
  19. package/dist/components/VirtualizedFile.js.map +1 -1
  20. package/dist/components/VirtualizedFileDiff.d.ts +14 -0
  21. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  22. package/dist/components/VirtualizedFileDiff.js +61 -3
  23. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  24. package/dist/components/Virtualizer.d.ts +14 -2
  25. package/dist/components/Virtualizer.d.ts.map +1 -1
  26. package/dist/components/Virtualizer.js +29 -3
  27. package/dist/components/Virtualizer.js.map +1 -1
  28. package/dist/edit/index.d.ts +5 -0
  29. package/dist/edit/index.js +3 -0
  30. package/dist/editor/command.d.ts +1 -1
  31. package/dist/editor/command.d.ts.map +1 -1
  32. package/dist/editor/command.js +11 -0
  33. package/dist/editor/command.js.map +1 -1
  34. package/dist/editor/editStack.d.ts +6 -2
  35. package/dist/editor/editStack.d.ts.map +1 -1
  36. package/dist/editor/editStack.js +54 -13
  37. package/dist/editor/editStack.js.map +1 -1
  38. package/dist/editor/editor.d.ts +51 -23
  39. package/dist/editor/editor.d.ts.map +1 -1
  40. package/dist/editor/editor.js +995 -242
  41. package/dist/editor/editor.js.map +1 -1
  42. package/dist/editor/editor2.js +1 -1
  43. package/dist/editor/languages.d.ts +26 -0
  44. package/dist/editor/languages.d.ts.map +1 -0
  45. package/dist/editor/languages.js +363 -0
  46. package/dist/editor/languages.js.map +1 -0
  47. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  48. package/dist/editor/lineAnnotations.js +25 -26
  49. package/dist/editor/lineAnnotations.js.map +1 -1
  50. package/dist/editor/marker.d.ts +5 -3
  51. package/dist/editor/marker.d.ts.map +1 -1
  52. package/dist/editor/marker.js +111 -91
  53. package/dist/editor/marker.js.map +1 -1
  54. package/dist/editor/matchBrackets.d.ts +3 -2
  55. package/dist/editor/matchBrackets.d.ts.map +1 -1
  56. package/dist/editor/matchBrackets.js +2 -10
  57. package/dist/editor/matchBrackets.js.map +1 -1
  58. package/dist/editor/pieceTable.d.ts +2 -1
  59. package/dist/editor/pieceTable.d.ts.map +1 -1
  60. package/dist/editor/pieceTable.js +103 -39
  61. package/dist/editor/pieceTable.js.map +1 -1
  62. package/dist/editor/platform.js +2 -0
  63. package/dist/editor/platform.js.map +1 -1
  64. package/dist/editor/popover.d.ts +25 -5
  65. package/dist/editor/popover.d.ts.map +1 -1
  66. package/dist/editor/popover.js +60 -14
  67. package/dist/editor/popover.js.map +1 -1
  68. package/dist/editor/searchPanel.d.ts +1 -0
  69. package/dist/editor/searchPanel.d.ts.map +1 -1
  70. package/dist/editor/searchPanel.js +15 -8
  71. package/dist/editor/searchPanel.js.map +1 -1
  72. package/dist/editor/selection.d.ts +31 -11
  73. package/dist/editor/selection.d.ts.map +1 -1
  74. package/dist/editor/selection.js +198 -70
  75. package/dist/editor/selection.js.map +1 -1
  76. package/dist/editor/selectionAction.d.ts +4 -3
  77. package/dist/editor/selectionAction.d.ts.map +1 -1
  78. package/dist/editor/selectionAction.js +9 -5
  79. package/dist/editor/selectionAction.js.map +1 -1
  80. package/dist/editor/stateStorage.d.ts +15 -0
  81. package/dist/editor/stateStorage.d.ts.map +1 -0
  82. package/dist/editor/stateStorage.js +65 -0
  83. package/dist/editor/stateStorage.js.map +1 -0
  84. package/dist/editor/textDocument.d.ts +12 -77
  85. package/dist/editor/textDocument.d.ts.map +1 -1
  86. package/dist/editor/textDocument.js +76 -34
  87. package/dist/editor/textDocument.js.map +1 -1
  88. package/dist/editor/textMeasure.d.ts +7 -12
  89. package/dist/editor/textMeasure.d.ts.map +1 -1
  90. package/dist/editor/textMeasure.js +40 -32
  91. package/dist/editor/textMeasure.js.map +1 -1
  92. package/dist/editor/{tokenzier.d.ts → tokenizer.d.ts} +3 -3
  93. package/dist/editor/tokenizer.d.ts.map +1 -0
  94. package/dist/editor/{tokenzier.js → tokenizer.js} +91 -26
  95. package/dist/editor/tokenizer.js.map +1 -0
  96. package/dist/editor/utils.js +1 -1
  97. package/dist/editor/utils.js.map +1 -1
  98. package/dist/index.d.ts +5 -4
  99. package/dist/index.js +7 -6
  100. package/dist/managers/InteractionManager.d.ts +18 -7
  101. package/dist/managers/InteractionManager.d.ts.map +1 -1
  102. package/dist/managers/InteractionManager.js +154 -99
  103. package/dist/managers/InteractionManager.js.map +1 -1
  104. package/dist/managers/UniversalRenderingManager.d.ts +2 -1
  105. package/dist/managers/UniversalRenderingManager.d.ts.map +1 -1
  106. package/dist/managers/UniversalRenderingManager.js +7 -3
  107. package/dist/managers/UniversalRenderingManager.js.map +1 -1
  108. package/dist/react/CodeView.d.ts +13 -13
  109. package/dist/react/CodeView.d.ts.map +1 -1
  110. package/dist/react/CodeView.js +38 -29
  111. package/dist/react/CodeView.js.map +1 -1
  112. package/dist/react/EditContext.d.ts +20 -0
  113. package/dist/react/EditContext.d.ts.map +1 -0
  114. package/dist/react/EditContext.js +20 -0
  115. package/dist/react/EditContext.js.map +1 -0
  116. package/dist/react/File.d.ts +2 -1
  117. package/dist/react/File.d.ts.map +1 -1
  118. package/dist/react/File.js +3 -2
  119. package/dist/react/File.js.map +1 -1
  120. package/dist/react/FileDiff.d.ts +2 -2
  121. package/dist/react/FileDiff.d.ts.map +1 -1
  122. package/dist/react/FileDiff.js +3 -2
  123. package/dist/react/FileDiff.js.map +1 -1
  124. package/dist/react/MultiFileDiff.d.ts +2 -2
  125. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  126. package/dist/react/MultiFileDiff.js +3 -2
  127. package/dist/react/MultiFileDiff.js.map +1 -1
  128. package/dist/react/PatchDiff.d.ts +2 -2
  129. package/dist/react/PatchDiff.d.ts.map +1 -1
  130. package/dist/react/PatchDiff.js +3 -2
  131. package/dist/react/PatchDiff.js.map +1 -1
  132. package/dist/react/UnresolvedFile.d.ts +1 -1
  133. package/dist/react/UnresolvedFile.js.map +1 -1
  134. package/dist/react/index.d.ts +4 -4
  135. package/dist/react/index.js +3 -3
  136. package/dist/react/types.d.ts +9 -1
  137. package/dist/react/types.d.ts.map +1 -1
  138. package/dist/react/utils/useFileDiffInstance.d.ts +5 -3
  139. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  140. package/dist/react/utils/useFileDiffInstance.js +28 -27
  141. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  142. package/dist/react/utils/useFileInstance.d.ts +5 -3
  143. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  144. package/dist/react/utils/useFileInstance.js +29 -25
  145. package/dist/react/utils/useFileInstance.js.map +1 -1
  146. package/dist/react/utils/useUnresolvedFileInstance.js +3 -3
  147. package/dist/react/utils/useUnresolvedFileInstance.js.map +1 -1
  148. package/dist/renderers/DiffHunksRenderer.d.ts +33 -1
  149. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  150. package/dist/renderers/DiffHunksRenderer.js +134 -21
  151. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  152. package/dist/renderers/FileRenderer.d.ts +2 -0
  153. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  154. package/dist/renderers/FileRenderer.js +33 -11
  155. package/dist/renderers/FileRenderer.js.map +1 -1
  156. package/dist/ssr/FileDiffReact.js +1 -1
  157. package/dist/ssr/index.d.ts +2 -2
  158. package/dist/ssr/preloadDiffs.js +1 -1
  159. package/dist/style.js +1 -1
  160. package/dist/types.d.ts +185 -42
  161. package/dist/types.d.ts.map +1 -1
  162. package/dist/utils/annotationHelpers.d.ts +20 -0
  163. package/dist/utils/annotationHelpers.d.ts.map +1 -0
  164. package/dist/utils/annotationHelpers.js +29 -0
  165. package/dist/utils/annotationHelpers.js.map +1 -0
  166. package/dist/utils/computeFileOffsets.d.ts.map +1 -1
  167. package/dist/utils/computeFileOffsets.js +34 -5
  168. package/dist/utils/computeFileOffsets.js.map +1 -1
  169. package/dist/utils/editSessionHunks.d.ts +66 -0
  170. package/dist/utils/editSessionHunks.d.ts.map +1 -0
  171. package/dist/utils/editSessionHunks.js +437 -0
  172. package/dist/utils/editSessionHunks.js.map +1 -0
  173. package/dist/utils/getHunkSideBoundaries.d.ts +7 -0
  174. package/dist/utils/getHunkSideBoundaries.d.ts.map +1 -0
  175. package/dist/utils/getHunkSideBoundaries.js +12 -0
  176. package/dist/utils/getHunkSideBoundaries.js.map +1 -0
  177. package/dist/utils/getTotalLineCountFromHunks.d.ts.map +1 -1
  178. package/dist/utils/getTotalLineCountFromHunks.js +2 -1
  179. package/dist/utils/getTotalLineCountFromHunks.js.map +1 -1
  180. package/dist/utils/hydratePartialDiff.d.ts.map +1 -1
  181. package/dist/utils/hydratePartialDiff.js +4 -3
  182. package/dist/utils/hydratePartialDiff.js.map +1 -1
  183. package/dist/utils/isGutterUtilityPath.d.ts +5 -0
  184. package/dist/utils/isGutterUtilityPath.d.ts.map +1 -0
  185. package/dist/utils/isGutterUtilityPath.js +12 -0
  186. package/dist/utils/isGutterUtilityPath.js.map +1 -0
  187. package/dist/utils/iterateOverDiff.d.ts.map +1 -1
  188. package/dist/utils/iterateOverDiff.js +17 -12
  189. package/dist/utils/iterateOverDiff.js.map +1 -1
  190. package/dist/utils/parseMergeConflictDiffFromFile.d.ts.map +1 -1
  191. package/dist/utils/parseMergeConflictDiffFromFile.js +9 -6
  192. package/dist/utils/parseMergeConflictDiffFromFile.js.map +1 -1
  193. package/dist/utils/parsePatchFiles.d.ts.map +1 -1
  194. package/dist/utils/parsePatchFiles.js +6 -3
  195. package/dist/utils/parsePatchFiles.js.map +1 -1
  196. package/dist/utils/realignChangeContent.d.ts +33 -0
  197. package/dist/utils/realignChangeContent.d.ts.map +1 -0
  198. package/dist/utils/realignChangeContent.js +148 -0
  199. package/dist/utils/realignChangeContent.js.map +1 -0
  200. package/dist/utils/renderDiffWithHighlighter.js +1 -1
  201. package/dist/utils/resolveRegion.d.ts.map +1 -1
  202. package/dist/utils/resolveRegion.js +18 -2
  203. package/dist/utils/resolveRegion.js.map +1 -1
  204. package/dist/utils/updateDiffHunks.d.ts +8 -2
  205. package/dist/utils/updateDiffHunks.d.ts.map +1 -1
  206. package/dist/utils/updateDiffHunks.js +9 -7
  207. package/dist/utils/updateDiffHunks.js.map +1 -1
  208. package/dist/utils/virtualDiffLayout.d.ts +42 -2
  209. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  210. package/dist/utils/virtualDiffLayout.js +101 -7
  211. package/dist/utils/virtualDiffLayout.js.map +1 -1
  212. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  213. package/dist/worker/WorkerPoolManager.js +4 -2
  214. package/dist/worker/WorkerPoolManager.js.map +1 -1
  215. package/dist/worker/types.d.ts +4 -0
  216. package/dist/worker/types.d.ts.map +1 -1
  217. package/dist/worker/worker-portable.js +60 -20
  218. package/dist/worker/worker-portable.js.map +1 -1
  219. package/dist/worker/worker.js +61 -19
  220. package/dist/worker/worker.js.map +1 -1
  221. package/package.json +13 -8
  222. package/dist/editor/index.d.ts +0 -3
  223. package/dist/editor/index.js +0 -3
  224. package/dist/editor/tokenzier.d.ts.map +0 -1
  225. package/dist/editor/tokenzier.js.map +0 -1
  226. package/dist/react/EditorContext.d.ts +0 -15
  227. package/dist/react/EditorContext.d.ts.map +0 -1
  228. package/dist/react/EditorContext.js +0 -23
  229. package/dist/react/EditorContext.js.map +0 -1
@@ -0,0 +1,437 @@
1
+ import { getHunkSideStartBoundary } from "./getHunkSideBoundaries.js";
2
+ import { parseDiffFromFile } from "./parseDiffFromFile.js";
3
+ import { getExpandedRegion, getTrailingExpandedRegion } from "./virtualDiffLayout.js";
4
+ import { offsetHunkContent, preserveTrailingEditorBlankLine, recomputeDiffHunksForEdit, recomputeDiffRenderLineCounts, recomputeHunkRenderLineCounts, syncHunkNoEOFCRFromFullFile } from "./updateDiffHunks.js";
5
+ //#region src/utils/editSessionHunks.ts
6
+ const deletionLineSetCache = /* @__PURE__ */ new WeakMap();
7
+ /**
8
+ * Drops the editor document's phantom trailing empty line (a document ending
9
+ * in a newline exposes one extra empty line the parsed diff never contains)
10
+ * so session line arrays compare like parse-derived ones.
11
+ */
12
+ function normalizeEditorLines(lines) {
13
+ if (lines.length > 1 && lines[lines.length - 1] === "") return lines.slice(0, -1);
14
+ return lines;
15
+ }
16
+ /**
17
+ * Find the complete old/current divergence core. The old side is immutable
18
+ * during an edit session, so this result needs no prior-pass snapshot.
19
+ */
20
+ function findDivergenceCore(deletionLines, additionLines) {
21
+ const maxStart = Math.min(deletionLines.length, additionLines.length);
22
+ let start = 0;
23
+ while (start < maxStart && deletionLines[start] === additionLines[start]) start++;
24
+ let deletionEnd = deletionLines.length;
25
+ let additionEnd = additionLines.length;
26
+ while (deletionEnd > start && additionEnd > start && deletionLines[deletionEnd - 1] === additionLines[additionEnd - 1]) {
27
+ deletionEnd--;
28
+ additionEnd--;
29
+ }
30
+ if (start === deletionEnd && start === additionEnd) return;
31
+ return {
32
+ start,
33
+ deletionEnd,
34
+ additionEnd
35
+ };
36
+ }
37
+ /**
38
+ * Rebuild the session skeleton as a pure function of the immutable old lines,
39
+ * current new lines, and old-side ranges of the previous regions. One
40
+ * canonical parse supplies the same change blocks that session exit will use.
41
+ */
42
+ function rebuildSessionHunks(diff, parseDiffOptions) {
43
+ const previousHunks = diff.hunks;
44
+ const editorAdditionLines = diff.additionLines;
45
+ const canonicalAdditionLines = normalizeEditorLines(editorAdditionLines);
46
+ const canonicalDiff = canonicalAdditionLines === editorAdditionLines ? diff : {
47
+ ...diff,
48
+ additionLines: canonicalAdditionLines
49
+ };
50
+ const plans = buildRegionPlans(previousHunks, parseCanonicalChangeBlocks(canonicalDiff, parseDiffOptions), diff.deletionLines.length);
51
+ const nextHunks = buildRegionHunks(canonicalDiff, plans);
52
+ diff.additionLines = canonicalAdditionLines;
53
+ diff.hunks = nextHunks;
54
+ diff.editSessionDirty = true;
55
+ finalizeSessionHunks(diff);
56
+ preserveTrailingEditorBlankLine(diff, editorAdditionLines);
57
+ if (!hasRegionOrSplitLayoutChanged(previousHunks, diff.hunks)) return;
58
+ return { regions: plans.map((plan) => plan.previousSpan) };
59
+ }
60
+ /**
61
+ * Keep a cheap content-only path when a same-line-count pass cannot alter the
62
+ * canonical blocks. Gap, ambiguous, or multi-region edits rebuild statelessly.
63
+ */
64
+ function applySessionChangedLines(diff, changedAdditionLineIndexes, parseDiffOptions, previousAdditionLines) {
65
+ const lines = Array.from(new Set(changedAdditionLineIndexes)).filter((line) => line >= 0 && line < diff.additionLines.length).sort((a, b) => a - b);
66
+ if (lines.length === 0) return;
67
+ const { hunks } = diff;
68
+ let regionIndex;
69
+ let hunkIndex = 0;
70
+ for (const line of lines) {
71
+ while (hunkIndex < hunks.length) {
72
+ const hunk = hunks[hunkIndex];
73
+ if (line < getHunkAdditionStart(hunk) + hunk.additionCount) break;
74
+ hunkIndex++;
75
+ }
76
+ const hunk = hunks[hunkIndex];
77
+ const start = hunk == null ? void 0 : getHunkAdditionStart(hunk);
78
+ if (start == null || line < start || regionIndex != null && regionIndex !== hunkIndex) return rebuildSessionHunks(diff, parseDiffOptions);
79
+ regionIndex = hunkIndex;
80
+ }
81
+ if (regionIndex == null) return;
82
+ if (canRetainCanonicalBlocks(diff, lines, regionIndex, previousAdditionLines, parseDiffOptions)) {
83
+ diff.editSessionDirty = true;
84
+ return;
85
+ }
86
+ return rebuildSessionHunks(diff, parseDiffOptions);
87
+ }
88
+ function canRetainCanonicalBlocks(diff, changedLines, regionIndex, previousAdditionLines, parseDiffOptions) {
89
+ if (previousAdditionLines == null || parseDiffOptions?.ignoreWhitespace === true || parseDiffOptions?.stripTrailingCr === true) return false;
90
+ const deletionLineSet = getDeletionLineSet(diff);
91
+ const hunk = diff.hunks[regionIndex];
92
+ if (hunk.hunkContent.some(isPureChange)) return false;
93
+ for (const line of changedLines) {
94
+ const previousLine = previousAdditionLines.get(line);
95
+ const additionLine = diff.additionLines[line];
96
+ if (previousLine == null || additionLine == null || deletionLineSet.has(previousLine) || deletionLineSet.has(additionLine)) return false;
97
+ let insideBalancedChange = false;
98
+ for (const content of hunk.hunkContent) if (content.type === "change" && content.additions === content.deletions && line >= content.additionLineIndex && line < content.additionLineIndex + content.additions) {
99
+ insideBalancedChange = true;
100
+ break;
101
+ }
102
+ if (!insideBalancedChange) return false;
103
+ }
104
+ return true;
105
+ }
106
+ function getDeletionLineSet(diff) {
107
+ const cached = deletionLineSetCache.get(diff);
108
+ if (cached?.lines === diff.deletionLines) return cached.set;
109
+ const set = new Set(diff.deletionLines);
110
+ deletionLineSetCache.set(diff, {
111
+ lines: diff.deletionLines,
112
+ set
113
+ });
114
+ return set;
115
+ }
116
+ function isPureChange(content) {
117
+ return content?.type === "change" && (content.additions === 0 || content.deletions === 0);
118
+ }
119
+ /** Preserve expansion at the surviving outer edges of rebuilt old-side gaps. */
120
+ function remapExpandedHunksForRegionChange(expandedHunks, change) {
121
+ const remapped = /* @__PURE__ */ new Map();
122
+ const { regions } = change;
123
+ for (let key = 0; key <= regions.length; key++) {
124
+ const previous = regions[key - 1];
125
+ const next = regions[key];
126
+ const fromStartSource = key === 0 ? expandedHunks.get(0) : previous == null ? void 0 : expandedHunks.get(previous.lastIndex + 1);
127
+ const fromEndSource = next == null ? void 0 : expandedHunks.get(next.firstIndex);
128
+ const fromStart = fromStartSource?.fromStart ?? 0;
129
+ const fromEnd = fromEndSource?.fromEnd ?? 0;
130
+ if (fromStart > 0 || fromEnd > 0) remapped.set(key, {
131
+ fromStart,
132
+ fromEnd
133
+ });
134
+ }
135
+ return remapped;
136
+ }
137
+ /** Snapshot the expanded gap-edge slices before the exit recompute. */
138
+ function captureExpansionAnchors(diff, expandedHunks, collapsedContextThreshold) {
139
+ const anchors = [];
140
+ if (diff.isPartial) return anchors;
141
+ for (const [hunkIndex, hunk] of diff.hunks.entries()) {
142
+ const region = getExpandedRegion({
143
+ isPartial: diff.isPartial,
144
+ rangeSize: hunk.collapsedBefore,
145
+ expandedHunks,
146
+ hunkIndex,
147
+ collapsedContextThreshold
148
+ });
149
+ if (region.rangeSize <= collapsedContextThreshold) continue;
150
+ const gapEnd = getHunkDeletionStart(hunk);
151
+ const gapStart = gapEnd - region.rangeSize;
152
+ if (region.fromStart > 0) anchors.push([gapStart, gapStart + region.fromStart]);
153
+ if (region.fromEnd > 0) anchors.push([gapEnd - region.fromEnd, gapEnd]);
154
+ }
155
+ const trailingRegion = getTrailingExpandedRegion({
156
+ fileDiff: diff,
157
+ hunkIndex: diff.hunks.length - 1,
158
+ expandedHunks,
159
+ collapsedContextThreshold,
160
+ errorPrefix: "captureExpansionAnchors"
161
+ });
162
+ if (trailingRegion != null && trailingRegion.fromStart > 0 && trailingRegion.rangeSize > collapsedContextThreshold) {
163
+ const lastHunk = diff.hunks[diff.hunks.length - 1];
164
+ const gapStart = getHunkDeletionStart(lastHunk) + lastHunk.deletionCount;
165
+ anchors.push([gapStart, gapStart + trailingRegion.fromStart]);
166
+ }
167
+ return anchors;
168
+ }
169
+ /**
170
+ * Rebuild gap expansion state against the recomputed hunks: for each new
171
+ * gap, an anchor touching the gap's start edge restores `fromStart`, one
172
+ * touching its end edge restores `fromEnd`, and anchors for gaps that no
173
+ * longer exist drop.
174
+ */
175
+ function rebuildExpansionFromAnchors(diff, anchors) {
176
+ const rebuilt = /* @__PURE__ */ new Map();
177
+ if (anchors.length === 0) return rebuilt;
178
+ const applyGap = (key, gapStart, gapEnd) => {
179
+ if (gapEnd <= gapStart) return;
180
+ let fromStart = 0;
181
+ let fromEnd = 0;
182
+ for (const [start, end] of anchors) {
183
+ if (end <= gapStart || start >= gapEnd) continue;
184
+ if (start <= gapStart) fromStart = Math.max(fromStart, Math.min(end, gapEnd) - gapStart);
185
+ if (end >= gapEnd) fromEnd = Math.max(fromEnd, gapEnd - Math.max(start, gapStart));
186
+ }
187
+ if (fromStart > 0 || fromEnd > 0) rebuilt.set(key, {
188
+ fromStart,
189
+ fromEnd
190
+ });
191
+ };
192
+ for (const [hunkIndex, hunk] of diff.hunks.entries()) {
193
+ const gapEnd = getHunkDeletionStart(hunk);
194
+ applyGap(hunkIndex, gapEnd - Math.max(hunk.collapsedBefore, 0), gapEnd);
195
+ }
196
+ const lastHunk = diff.hunks[diff.hunks.length - 1];
197
+ if (lastHunk != null && !diff.isPartial && diff.deletionLines.length > 0) applyGap(diff.hunks.length, getHunkDeletionStart(lastHunk) + lastHunk.deletionCount, diff.deletionLines.length);
198
+ return rebuilt;
199
+ }
200
+ /**
201
+ * Genuine session exit: when session passes reshaped the hunks, run the real
202
+ * full recompute so exit state matches a non-session edit pipeline, and clear
203
+ * the marker. Returns true when a recompute ran.
204
+ */
205
+ function finishEditSessionForDiff(diff, parseDiffOptions) {
206
+ if (diff.editSessionDirty !== true) return false;
207
+ diff.editSessionDirty = void 0;
208
+ Object.assign(diff, recomputeDiffHunksForEdit(diff, parseDiffOptions));
209
+ return true;
210
+ }
211
+ function parseCanonicalChangeBlocks(diff, parseDiffOptions) {
212
+ if (findDivergenceCore(diff.deletionLines, diff.additionLines) == null) return [];
213
+ const parsed = parseDiffFromFile({
214
+ name: diff.prevName ?? diff.name,
215
+ contents: diff.deletionLines.join("")
216
+ }, {
217
+ name: diff.name,
218
+ contents: diff.additionLines.join(""),
219
+ lang: diff.lang
220
+ }, parseDiffOptions);
221
+ const blocks = [];
222
+ let coveredAdditions = 0;
223
+ let coveredDeletions = 0;
224
+ for (const hunk of parsed.hunks) {
225
+ const contextLines = hunk.additionCount > 0 ? hunk.additionLineIndex - coveredAdditions : hunk.deletionLineIndex - coveredDeletions;
226
+ coveredAdditions += contextLines;
227
+ coveredDeletions += contextLines;
228
+ for (const content of hunk.hunkContent) {
229
+ if (content.type === "context") {
230
+ coveredAdditions += content.lines;
231
+ coveredDeletions += content.lines;
232
+ continue;
233
+ }
234
+ const block = offsetHunkContent(content, 0, 0);
235
+ if (block.additions === 0) block.additionLineIndex = coveredAdditions;
236
+ if (block.deletions === 0) block.deletionLineIndex = coveredDeletions;
237
+ blocks.push(block);
238
+ coveredAdditions += block.additions;
239
+ coveredDeletions += block.deletions;
240
+ }
241
+ }
242
+ return blocks;
243
+ }
244
+ function buildRegionPlans(previousHunks, blocks, deletionLineCount) {
245
+ const previousPlans = previousHunks.map((hunk, index) => {
246
+ const deletionStart = getHunkDeletionStart(hunk);
247
+ return {
248
+ deletionStart,
249
+ deletionEnd: deletionStart + hunk.deletionCount,
250
+ blocks: [],
251
+ previousSpan: {
252
+ firstIndex: index,
253
+ lastIndex: index
254
+ }
255
+ };
256
+ });
257
+ const plans = [];
258
+ let previousIndex = 0;
259
+ for (const block of blocks) {
260
+ const blockStart = block.deletionLineIndex;
261
+ const blockEnd = blockStart + block.deletions;
262
+ while (previousIndex < previousPlans.length && previousPlans[previousIndex].deletionEnd < blockStart) {
263
+ plans.push(previousPlans[previousIndex]);
264
+ previousIndex++;
265
+ }
266
+ let plan = plans.length > 0 && blockTouchesRegion(blockStart, blockEnd, plans[plans.length - 1]) ? plans.pop() : void 0;
267
+ while (previousIndex < previousPlans.length && previousPlans[previousIndex].deletionStart <= blockEnd) {
268
+ plan = mergeRegionPlans(plan, previousPlans[previousIndex]);
269
+ previousIndex++;
270
+ }
271
+ if (plan == null) {
272
+ let deletionStart = blockStart;
273
+ let deletionEnd = blockEnd;
274
+ if ((block.deletions === 0 || block.additions === 0) && deletionLineCount > block.deletions) {
275
+ const previousEnd = plans[plans.length - 1]?.deletionEnd ?? 0;
276
+ const nextStart = previousPlans[previousIndex]?.deletionStart ?? deletionLineCount;
277
+ if (blockStart > previousEnd) deletionStart--;
278
+ else if (blockEnd < nextStart) deletionEnd++;
279
+ }
280
+ plan = {
281
+ deletionStart,
282
+ deletionEnd,
283
+ blocks: [],
284
+ previousSpan: void 0
285
+ };
286
+ }
287
+ plan.deletionStart = Math.min(plan.deletionStart, blockStart);
288
+ plan.deletionEnd = Math.max(plan.deletionEnd, blockEnd);
289
+ plan.blocks.push(block);
290
+ plans.push(plan);
291
+ }
292
+ while (previousIndex < previousPlans.length) {
293
+ plans.push(previousPlans[previousIndex]);
294
+ previousIndex++;
295
+ }
296
+ return plans;
297
+ }
298
+ function blockTouchesRegion(blockStart, blockEnd, region) {
299
+ return blockStart <= region.deletionEnd && blockEnd >= region.deletionStart;
300
+ }
301
+ function mergeRegionPlans(target, source) {
302
+ if (target == null) return source;
303
+ target.deletionStart = Math.min(target.deletionStart, source.deletionStart);
304
+ target.deletionEnd = Math.max(target.deletionEnd, source.deletionEnd);
305
+ target.blocks.push(...source.blocks);
306
+ if (source.previousSpan != null) {
307
+ target.previousSpan ??= { ...source.previousSpan };
308
+ target.previousSpan.firstIndex = Math.min(target.previousSpan.firstIndex, source.previousSpan.firstIndex);
309
+ target.previousSpan.lastIndex = Math.max(target.previousSpan.lastIndex, source.previousSpan.lastIndex);
310
+ }
311
+ return target;
312
+ }
313
+ function buildRegionHunks(diff, plans) {
314
+ const hunks = [];
315
+ let deletionCursor = 0;
316
+ let additionCursor = 0;
317
+ for (const plan of plans) {
318
+ const contextBefore = plan.deletionStart - deletionCursor;
319
+ if (contextBefore < 0) throw new Error("buildRegionHunks: overlapping old-side regions");
320
+ deletionCursor += contextBefore;
321
+ additionCursor += contextBefore;
322
+ const additionStart = additionCursor;
323
+ const hunkContent = [];
324
+ for (const canonicalBlock of plan.blocks) {
325
+ const deletionContext = canonicalBlock.deletionLineIndex - deletionCursor;
326
+ const additionContext = canonicalBlock.additionLineIndex - additionCursor;
327
+ if (deletionContext < 0 || deletionContext !== additionContext) throw new Error("buildRegionHunks: canonical block context mismatch");
328
+ pushContext(hunkContent, deletionContext, additionCursor, deletionCursor);
329
+ deletionCursor += deletionContext;
330
+ additionCursor += additionContext;
331
+ hunkContent.push({ ...canonicalBlock });
332
+ deletionCursor += canonicalBlock.deletions;
333
+ additionCursor += canonicalBlock.additions;
334
+ }
335
+ const trailingContext = plan.deletionEnd - deletionCursor;
336
+ if (trailingContext < 0) throw new Error("buildRegionHunks: block exceeds its old-side region");
337
+ pushContext(hunkContent, trailingContext, additionCursor, deletionCursor);
338
+ deletionCursor += trailingContext;
339
+ additionCursor += trailingContext;
340
+ hunks.push(createRegionHunk(diff, {
341
+ additionStart,
342
+ additionEnd: additionCursor,
343
+ deletionStart: plan.deletionStart,
344
+ deletionEnd: plan.deletionEnd
345
+ }, hunkContent));
346
+ }
347
+ if (diff.deletionLines.length - deletionCursor !== diff.additionLines.length - additionCursor) throw new Error("buildRegionHunks: trailing context mismatch");
348
+ return hunks;
349
+ }
350
+ function createRegionHunk(diff, bounds, hunkContent) {
351
+ const additionCount = bounds.additionEnd - bounds.additionStart;
352
+ const deletionCount = bounds.deletionEnd - bounds.deletionStart;
353
+ let additionLines = 0;
354
+ let deletionLines = 0;
355
+ for (const content of hunkContent) if (content.type === "change") {
356
+ additionLines += content.additions;
357
+ deletionLines += content.deletions;
358
+ }
359
+ const hunk = {
360
+ collapsedBefore: 0,
361
+ additionStart: getUnifiedStart(bounds.additionStart, additionCount),
362
+ additionCount,
363
+ additionLines,
364
+ additionLineIndex: getUnifiedLineIndex(bounds.additionStart, additionCount),
365
+ deletionStart: getUnifiedStart(bounds.deletionStart, deletionCount),
366
+ deletionCount,
367
+ deletionLines,
368
+ deletionLineIndex: getUnifiedLineIndex(bounds.deletionStart, deletionCount),
369
+ hunkContent,
370
+ hunkSpecs: `@@ -${getUnifiedStart(bounds.deletionStart, deletionCount)},${deletionCount} +${getUnifiedStart(bounds.additionStart, additionCount)},${additionCount} @@`,
371
+ splitLineStart: 0,
372
+ splitLineCount: 0,
373
+ unifiedLineStart: 0,
374
+ unifiedLineCount: 0,
375
+ noEOFCRAdditions: false,
376
+ noEOFCRDeletions: false
377
+ };
378
+ recomputeHunkRenderLineCounts(hunk);
379
+ return hunk;
380
+ }
381
+ function pushContext(hunkContent, lines, additionLineIndex, deletionLineIndex) {
382
+ if (lines > 0) hunkContent.push({
383
+ type: "context",
384
+ lines,
385
+ additionLineIndex,
386
+ deletionLineIndex
387
+ });
388
+ }
389
+ function hasRegionOrSplitLayoutChanged(previous, next) {
390
+ if (previous.length !== next.length) return true;
391
+ for (let index = 0; index < previous.length; index++) {
392
+ const previousHunk = previous[index];
393
+ const nextHunk = next[index];
394
+ if (getHunkDeletionStart(previousHunk) !== getHunkDeletionStart(nextHunk) || previousHunk.deletionCount !== nextHunk.deletionCount || getHunkAdditionStart(previousHunk) !== getHunkAdditionStart(nextHunk) || previousHunk.additionCount !== nextHunk.additionCount || previousHunk.splitLineCount !== nextHunk.splitLineCount || !haveSameSplitRowMapping(previousHunk, nextHunk)) return true;
395
+ }
396
+ return false;
397
+ }
398
+ function haveSameSplitRowMapping(previous, next) {
399
+ const previousRows = iterateSplitRowMapping(previous);
400
+ const nextRows = iterateSplitRowMapping(next);
401
+ while (true) {
402
+ const previousRow = previousRows.next();
403
+ const nextRow = nextRows.next();
404
+ if (previousRow.done === true || nextRow.done === true) return previousRow.done === nextRow.done;
405
+ if (previousRow.value[0] !== nextRow.value[0] || previousRow.value[1] !== nextRow.value[1]) return false;
406
+ }
407
+ }
408
+ function* iterateSplitRowMapping(hunk) {
409
+ for (const content of hunk.hunkContent) {
410
+ if (content.type === "context") {
411
+ for (let offset = 0; offset < content.lines; offset++) yield [content.deletionLineIndex + offset, content.additionLineIndex + offset];
412
+ continue;
413
+ }
414
+ const rowCount = Math.max(content.deletions, content.additions);
415
+ for (let offset = 0; offset < rowCount; offset++) yield [offset < content.deletions ? content.deletionLineIndex + offset : void 0, offset < content.additions ? content.additionLineIndex + offset : void 0];
416
+ }
417
+ }
418
+ function getHunkAdditionStart(hunk) {
419
+ return getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount);
420
+ }
421
+ function getHunkDeletionStart(hunk) {
422
+ return getHunkSideStartBoundary(hunk.deletionStart, hunk.deletionCount);
423
+ }
424
+ function getUnifiedStart(lineIndex, count) {
425
+ return count === 0 ? lineIndex : lineIndex + 1;
426
+ }
427
+ function getUnifiedLineIndex(lineIndex, count) {
428
+ return count === 0 ? lineIndex - 1 : lineIndex;
429
+ }
430
+ function finalizeSessionHunks(diff) {
431
+ recomputeDiffRenderLineCounts(diff);
432
+ for (let index = 0; index < diff.hunks.length; index++) syncHunkNoEOFCRFromFullFile(diff, index);
433
+ }
434
+ //#endregion
435
+ export { applySessionChangedLines, captureExpansionAnchors, findDivergenceCore, finishEditSessionForDiff, normalizeEditorLines, rebuildExpansionFromAnchors, rebuildSessionHunks, remapExpandedHunksForRegionChange };
436
+
437
+ //# sourceMappingURL=editSessionHunks.js.map
@@ -0,0 +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 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 genuine 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 * Genuine session exit: when session passes reshaped the hunks, run the real\n * full recompute so exit state matches a non-session edit pipeline, and clear\n * the marker. 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 diff.editSessionDirty = undefined;\n Object.assign(diff, recomputeDiffHunksForEdit(diff, parseDiffOptions));\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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"}
@@ -0,0 +1,7 @@
1
+ //#region src/utils/getHunkSideBoundaries.d.ts
2
+ /** Converts a unified hunk side's start/count into its consumed-file range. */
3
+ declare function getHunkSideStartBoundary(start: number, count: number): number;
4
+ declare function getHunkSideEndBoundary(start: number, count: number): number;
5
+ //#endregion
6
+ export { getHunkSideEndBoundary, getHunkSideStartBoundary };
7
+ //# sourceMappingURL=getHunkSideBoundaries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getHunkSideBoundaries.d.ts","names":[],"sources":["../../src/utils/getHunkSideBoundaries.ts"],"mappings":";;iBACgB,wBAAA,CAAyB,KAAA,UAAe,KAAa;AAAA,iBAIrD,sBAAA,CAAuB,KAAA,UAAe,KAAa"}
@@ -0,0 +1,12 @@
1
+ //#region src/utils/getHunkSideBoundaries.ts
2
+ /** Converts a unified hunk side's start/count into its consumed-file range. */
3
+ function getHunkSideStartBoundary(start, count) {
4
+ return start - (count === 0 ? 0 : 1);
5
+ }
6
+ function getHunkSideEndBoundary(start, count) {
7
+ return getHunkSideStartBoundary(start, count) + count;
8
+ }
9
+ //#endregion
10
+ export { getHunkSideEndBoundary, getHunkSideStartBoundary };
11
+
12
+ //# sourceMappingURL=getHunkSideBoundaries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getHunkSideBoundaries.js","names":[],"sources":["../../src/utils/getHunkSideBoundaries.ts"],"sourcesContent":["/** Converts a unified hunk side's start/count into its consumed-file range. */\nexport function getHunkSideStartBoundary(start: number, count: number): number {\n return start - (count === 0 ? 0 : 1);\n}\n\nexport function getHunkSideEndBoundary(start: number, count: number): number {\n return getHunkSideStartBoundary(start, count) + count;\n}\n"],"mappings":";;AACA,SAAgB,yBAAyB,OAAe,OAAuB;CAC7E,OAAO,SAAS,UAAU,IAAI,IAAI;AACpC;AAEA,SAAgB,uBAAuB,OAAe,OAAuB;CAC3E,OAAO,yBAAyB,OAAO,KAAK,IAAI;AAClD"}
@@ -1 +1 @@
1
- {"version":3,"file":"getTotalLineCountFromHunks.d.ts","names":[],"sources":["../../src/utils/getTotalLineCountFromHunks.ts"],"mappings":";;;iBAEgB,0BAAA,CAA2B,KAAa,EAAN,IAAI"}
1
+ {"version":3,"file":"getTotalLineCountFromHunks.d.ts","names":[],"sources":["../../src/utils/getTotalLineCountFromHunks.ts"],"mappings":";;;iBAGgB,0BAAA,CAA2B,KAAa,EAAN,IAAI"}
@@ -1,8 +1,9 @@
1
+ import { getHunkSideEndBoundary } from "./getHunkSideBoundaries.js";
1
2
  //#region src/utils/getTotalLineCountFromHunks.ts
2
3
  function getTotalLineCountFromHunks(hunks) {
3
4
  const lastHunk = hunks.at(-1);
4
5
  if (lastHunk == null) return 0;
5
- return Math.max(lastHunk.additionStart + lastHunk.additionCount, lastHunk.deletionStart + lastHunk.deletionCount);
6
+ return Math.max(getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount), getHunkSideEndBoundary(lastHunk.deletionStart, lastHunk.deletionCount));
6
7
  }
7
8
  //#endregion
8
9
  export { getTotalLineCountFromHunks };
@@ -1 +1 @@
1
- {"version":3,"file":"getTotalLineCountFromHunks.js","names":[],"sources":["../../src/utils/getTotalLineCountFromHunks.ts"],"sourcesContent":["import type { Hunk } from '../types';\n\nexport function getTotalLineCountFromHunks(hunks: Hunk[]): number {\n const lastHunk = hunks.at(-1);\n if (lastHunk == null) {\n return 0;\n }\n return Math.max(\n lastHunk.additionStart + lastHunk.additionCount,\n lastHunk.deletionStart + lastHunk.deletionCount\n );\n}\n"],"mappings":";AAEA,SAAgB,2BAA2B,OAAuB;CAChE,MAAM,WAAW,MAAM,GAAG,EAAE;CAC5B,IAAI,YAAY,MACd,OAAO;CAET,OAAO,KAAK,IACV,SAAS,gBAAgB,SAAS,eAClC,SAAS,gBAAgB,SAAS,aACpC;AACF"}
1
+ {"version":3,"file":"getTotalLineCountFromHunks.js","names":[],"sources":["../../src/utils/getTotalLineCountFromHunks.ts"],"sourcesContent":["import type { Hunk } from '../types';\nimport { getHunkSideEndBoundary } from './getHunkSideBoundaries';\n\nexport function getTotalLineCountFromHunks(hunks: Hunk[]): number {\n const lastHunk = hunks.at(-1);\n if (lastHunk == null) {\n return 0;\n }\n return Math.max(\n getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount),\n getHunkSideEndBoundary(lastHunk.deletionStart, lastHunk.deletionCount)\n );\n}\n"],"mappings":";;AAGA,SAAgB,2BAA2B,OAAuB;CAChE,MAAM,WAAW,MAAM,GAAG,EAAE;CAC5B,IAAI,YAAY,MACd,OAAO;CAET,OAAO,KAAK,IACV,uBAAuB,SAAS,eAAe,SAAS,aAAa,GACrE,uBAAuB,SAAS,eAAe,SAAS,aAAa,CACvE;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"hydratePartialDiff.d.ts","names":[],"sources":["../../src/utils/hydratePartialDiff.ts"],"mappings":";;;;;AAkBA;iBAAgB,kBAAA,CACd,IAAA,qBACA,QAAA,EAAU,gBAAA,EACV,KAAA,EAAO,mBAAA,GACN,gBAAA"}
1
+ {"version":3,"file":"hydratePartialDiff.d.ts","names":[],"sources":["../../src/utils/hydratePartialDiff.ts"],"mappings":";;;;;AAsBA;iBAAgB,kBAAA,CACd,IAAA,qBACA,QAAA,EAAU,gBAAA,EACV,KAAA,EAAO,mBAAA,GACN,gBAAA"}
@@ -1,3 +1,4 @@
1
+ import { getHunkSideEndBoundary, getHunkSideStartBoundary } from "./getHunkSideBoundaries.js";
1
2
  import { cloneFileDiffMetadata } from "./cloneFileDiffMetadata.js";
2
3
  import { splitFileContents } from "./splitFileContents.js";
3
4
  //#region src/utils/hydratePartialDiff.ts
@@ -76,7 +77,7 @@ function hydrateHunks(hunks, totalAdditionLines) {
76
77
  hunkSplitLineCount += Math.max(content.additions, content.deletions);
77
78
  hunkUnifiedLineCount += content.additions + content.deletions;
78
79
  }
79
- const collapsedBefore = Math.max(hunk.additionStart - 1 - lastHunkAdditionEnd, 0);
80
+ const collapsedBefore = Math.max(getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount) - lastHunkAdditionEnd, 0);
80
81
  hydratedHunks.push({
81
82
  ...hunk,
82
83
  collapsedBefore,
@@ -92,11 +93,11 @@ function hydrateHunks(hunks, totalAdditionLines) {
92
93
  });
93
94
  splitLineCount += collapsedBefore + hunkSplitLineCount;
94
95
  unifiedLineCount += collapsedBefore + hunkUnifiedLineCount;
95
- lastHunkAdditionEnd = hunk.additionStart + hunk.additionCount - 1;
96
+ lastHunkAdditionEnd = getHunkSideEndBoundary(hunk.additionStart, hunk.additionCount);
96
97
  }
97
98
  if (hydratedHunks.length > 0) {
98
99
  const lastHunk = hydratedHunks[hydratedHunks.length - 1];
99
- const lastHunkEnd = Math.max(lastHunk.additionStart + lastHunk.additionCount - 1, 0);
100
+ const lastHunkEnd = getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount);
100
101
  const collapsedAfter = Math.max(totalAdditionLines - lastHunkEnd, 0);
101
102
  splitLineCount += collapsedAfter;
102
103
  unifiedLineCount += collapsedAfter;