@pierre/diffs 1.3.0-beta.9 → 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 (219) 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 +49 -42
  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/VirtualizedFile.d.ts +2 -1
  14. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  15. package/dist/components/VirtualizedFile.js +9 -5
  16. package/dist/components/VirtualizedFile.js.map +1 -1
  17. package/dist/components/VirtualizedFileDiff.d.ts +14 -0
  18. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  19. package/dist/components/VirtualizedFileDiff.js +61 -3
  20. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  21. package/dist/components/Virtualizer.d.ts +14 -2
  22. package/dist/components/Virtualizer.d.ts.map +1 -1
  23. package/dist/components/Virtualizer.js +27 -2
  24. package/dist/components/Virtualizer.js.map +1 -1
  25. package/dist/edit/index.d.ts +5 -0
  26. package/dist/edit/index.js +3 -0
  27. package/dist/editor/command.d.ts +1 -1
  28. package/dist/editor/command.d.ts.map +1 -1
  29. package/dist/editor/command.js +11 -0
  30. package/dist/editor/command.js.map +1 -1
  31. package/dist/editor/editStack.d.ts +6 -2
  32. package/dist/editor/editStack.d.ts.map +1 -1
  33. package/dist/editor/editStack.js +53 -12
  34. package/dist/editor/editStack.js.map +1 -1
  35. package/dist/editor/editor.d.ts +51 -23
  36. package/dist/editor/editor.d.ts.map +1 -1
  37. package/dist/editor/editor.js +978 -238
  38. package/dist/editor/editor.js.map +1 -1
  39. package/dist/editor/editor2.js +1 -1
  40. package/dist/editor/languages.d.ts +26 -0
  41. package/dist/editor/languages.d.ts.map +1 -0
  42. package/dist/editor/languages.js +363 -0
  43. package/dist/editor/languages.js.map +1 -0
  44. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  45. package/dist/editor/lineAnnotations.js +25 -26
  46. package/dist/editor/lineAnnotations.js.map +1 -1
  47. package/dist/editor/marker.d.ts +5 -3
  48. package/dist/editor/marker.d.ts.map +1 -1
  49. package/dist/editor/marker.js +111 -91
  50. package/dist/editor/marker.js.map +1 -1
  51. package/dist/editor/matchBrackets.d.ts +3 -2
  52. package/dist/editor/matchBrackets.d.ts.map +1 -1
  53. package/dist/editor/matchBrackets.js +2 -10
  54. package/dist/editor/matchBrackets.js.map +1 -1
  55. package/dist/editor/pieceTable.d.ts +2 -1
  56. package/dist/editor/pieceTable.d.ts.map +1 -1
  57. package/dist/editor/pieceTable.js +103 -39
  58. package/dist/editor/pieceTable.js.map +1 -1
  59. package/dist/editor/platform.js +2 -0
  60. package/dist/editor/platform.js.map +1 -1
  61. package/dist/editor/popover.d.ts +25 -5
  62. package/dist/editor/popover.d.ts.map +1 -1
  63. package/dist/editor/popover.js +60 -14
  64. package/dist/editor/popover.js.map +1 -1
  65. package/dist/editor/searchPanel.d.ts.map +1 -1
  66. package/dist/editor/searchPanel.js +9 -2
  67. package/dist/editor/searchPanel.js.map +1 -1
  68. package/dist/editor/selection.d.ts +31 -11
  69. package/dist/editor/selection.d.ts.map +1 -1
  70. package/dist/editor/selection.js +198 -70
  71. package/dist/editor/selection.js.map +1 -1
  72. package/dist/editor/selectionAction.d.ts +4 -3
  73. package/dist/editor/selectionAction.d.ts.map +1 -1
  74. package/dist/editor/selectionAction.js +9 -5
  75. package/dist/editor/selectionAction.js.map +1 -1
  76. package/dist/editor/stateStorage.d.ts +15 -0
  77. package/dist/editor/stateStorage.d.ts.map +1 -0
  78. package/dist/editor/stateStorage.js +65 -0
  79. package/dist/editor/stateStorage.js.map +1 -0
  80. package/dist/editor/textDocument.d.ts +12 -77
  81. package/dist/editor/textDocument.d.ts.map +1 -1
  82. package/dist/editor/textDocument.js +76 -34
  83. package/dist/editor/textDocument.js.map +1 -1
  84. package/dist/editor/textMeasure.d.ts +7 -12
  85. package/dist/editor/textMeasure.d.ts.map +1 -1
  86. package/dist/editor/textMeasure.js +40 -32
  87. package/dist/editor/textMeasure.js.map +1 -1
  88. package/dist/editor/{tokenzier.d.ts → tokenizer.d.ts} +3 -3
  89. package/dist/editor/tokenizer.d.ts.map +1 -0
  90. package/dist/editor/{tokenzier.js → tokenizer.js} +91 -26
  91. package/dist/editor/tokenizer.js.map +1 -0
  92. package/dist/editor/utils.js +1 -1
  93. package/dist/editor/utils.js.map +1 -1
  94. package/dist/index.d.ts +4 -3
  95. package/dist/index.js +6 -5
  96. package/dist/managers/InteractionManager.d.ts +18 -7
  97. package/dist/managers/InteractionManager.d.ts.map +1 -1
  98. package/dist/managers/InteractionManager.js +154 -99
  99. package/dist/managers/InteractionManager.js.map +1 -1
  100. package/dist/managers/UniversalRenderingManager.d.ts.map +1 -1
  101. package/dist/managers/UniversalRenderingManager.js +1 -2
  102. package/dist/managers/UniversalRenderingManager.js.map +1 -1
  103. package/dist/react/CodeView.d.ts +13 -13
  104. package/dist/react/CodeView.d.ts.map +1 -1
  105. package/dist/react/CodeView.js +38 -29
  106. package/dist/react/CodeView.js.map +1 -1
  107. package/dist/react/EditContext.d.ts +20 -0
  108. package/dist/react/EditContext.d.ts.map +1 -0
  109. package/dist/react/EditContext.js +20 -0
  110. package/dist/react/EditContext.js.map +1 -0
  111. package/dist/react/File.d.ts +2 -1
  112. package/dist/react/File.d.ts.map +1 -1
  113. package/dist/react/File.js +3 -2
  114. package/dist/react/File.js.map +1 -1
  115. package/dist/react/FileDiff.d.ts +2 -2
  116. package/dist/react/FileDiff.d.ts.map +1 -1
  117. package/dist/react/FileDiff.js +3 -2
  118. package/dist/react/FileDiff.js.map +1 -1
  119. package/dist/react/MultiFileDiff.d.ts +2 -2
  120. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  121. package/dist/react/MultiFileDiff.js +3 -2
  122. package/dist/react/MultiFileDiff.js.map +1 -1
  123. package/dist/react/PatchDiff.d.ts +2 -2
  124. package/dist/react/PatchDiff.d.ts.map +1 -1
  125. package/dist/react/PatchDiff.js +3 -2
  126. package/dist/react/PatchDiff.js.map +1 -1
  127. package/dist/react/UnresolvedFile.d.ts +1 -1
  128. package/dist/react/UnresolvedFile.js.map +1 -1
  129. package/dist/react/index.d.ts +4 -4
  130. package/dist/react/index.js +3 -3
  131. package/dist/react/types.d.ts +9 -1
  132. package/dist/react/types.d.ts.map +1 -1
  133. package/dist/react/utils/useFileDiffInstance.d.ts +5 -3
  134. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  135. package/dist/react/utils/useFileDiffInstance.js +28 -27
  136. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  137. package/dist/react/utils/useFileInstance.d.ts +5 -3
  138. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  139. package/dist/react/utils/useFileInstance.js +29 -25
  140. package/dist/react/utils/useFileInstance.js.map +1 -1
  141. package/dist/react/utils/useUnresolvedFileInstance.js +3 -3
  142. package/dist/react/utils/useUnresolvedFileInstance.js.map +1 -1
  143. package/dist/renderers/DiffHunksRenderer.d.ts +33 -1
  144. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  145. package/dist/renderers/DiffHunksRenderer.js +134 -21
  146. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  147. package/dist/renderers/FileRenderer.d.ts +2 -0
  148. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  149. package/dist/renderers/FileRenderer.js +33 -11
  150. package/dist/renderers/FileRenderer.js.map +1 -1
  151. package/dist/ssr/FileDiffReact.js +1 -1
  152. package/dist/ssr/index.d.ts +2 -2
  153. package/dist/ssr/preloadDiffs.js +1 -1
  154. package/dist/style.js +1 -1
  155. package/dist/types.d.ts +185 -42
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/utils/annotationHelpers.d.ts +20 -0
  158. package/dist/utils/annotationHelpers.d.ts.map +1 -0
  159. package/dist/utils/annotationHelpers.js +29 -0
  160. package/dist/utils/annotationHelpers.js.map +1 -0
  161. package/dist/utils/computeFileOffsets.d.ts.map +1 -1
  162. package/dist/utils/computeFileOffsets.js +34 -5
  163. package/dist/utils/computeFileOffsets.js.map +1 -1
  164. package/dist/utils/editSessionHunks.d.ts +66 -0
  165. package/dist/utils/editSessionHunks.d.ts.map +1 -0
  166. package/dist/utils/editSessionHunks.js +437 -0
  167. package/dist/utils/editSessionHunks.js.map +1 -0
  168. package/dist/utils/getHunkSideBoundaries.d.ts +7 -0
  169. package/dist/utils/getHunkSideBoundaries.d.ts.map +1 -0
  170. package/dist/utils/getHunkSideBoundaries.js +12 -0
  171. package/dist/utils/getHunkSideBoundaries.js.map +1 -0
  172. package/dist/utils/getTotalLineCountFromHunks.d.ts.map +1 -1
  173. package/dist/utils/getTotalLineCountFromHunks.js +2 -1
  174. package/dist/utils/getTotalLineCountFromHunks.js.map +1 -1
  175. package/dist/utils/hydratePartialDiff.d.ts.map +1 -1
  176. package/dist/utils/hydratePartialDiff.js +4 -3
  177. package/dist/utils/hydratePartialDiff.js.map +1 -1
  178. package/dist/utils/isGutterUtilityPath.d.ts +5 -0
  179. package/dist/utils/isGutterUtilityPath.d.ts.map +1 -0
  180. package/dist/utils/isGutterUtilityPath.js +12 -0
  181. package/dist/utils/isGutterUtilityPath.js.map +1 -0
  182. package/dist/utils/iterateOverDiff.d.ts.map +1 -1
  183. package/dist/utils/iterateOverDiff.js +17 -12
  184. package/dist/utils/iterateOverDiff.js.map +1 -1
  185. package/dist/utils/parseMergeConflictDiffFromFile.d.ts.map +1 -1
  186. package/dist/utils/parseMergeConflictDiffFromFile.js +9 -6
  187. package/dist/utils/parseMergeConflictDiffFromFile.js.map +1 -1
  188. package/dist/utils/parsePatchFiles.d.ts.map +1 -1
  189. package/dist/utils/parsePatchFiles.js +6 -3
  190. package/dist/utils/parsePatchFiles.js.map +1 -1
  191. package/dist/utils/realignChangeContent.d.ts +33 -0
  192. package/dist/utils/realignChangeContent.d.ts.map +1 -0
  193. package/dist/utils/realignChangeContent.js +148 -0
  194. package/dist/utils/realignChangeContent.js.map +1 -0
  195. package/dist/utils/renderDiffWithHighlighter.js +1 -1
  196. package/dist/utils/resolveRegion.d.ts.map +1 -1
  197. package/dist/utils/resolveRegion.js +18 -2
  198. package/dist/utils/resolveRegion.js.map +1 -1
  199. package/dist/utils/updateDiffHunks.d.ts +8 -2
  200. package/dist/utils/updateDiffHunks.d.ts.map +1 -1
  201. package/dist/utils/updateDiffHunks.js +9 -7
  202. package/dist/utils/updateDiffHunks.js.map +1 -1
  203. package/dist/utils/virtualDiffLayout.d.ts +42 -2
  204. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  205. package/dist/utils/virtualDiffLayout.js +101 -7
  206. package/dist/utils/virtualDiffLayout.js.map +1 -1
  207. package/dist/worker/worker-portable.js +60 -20
  208. package/dist/worker/worker-portable.js.map +1 -1
  209. package/dist/worker/worker.js +61 -19
  210. package/dist/worker/worker.js.map +1 -1
  211. package/package.json +10 -7
  212. package/dist/editor/index.d.ts +0 -3
  213. package/dist/editor/index.js +0 -3
  214. package/dist/editor/tokenzier.d.ts.map +0 -1
  215. package/dist/editor/tokenzier.js.map +0 -1
  216. package/dist/react/EditorContext.d.ts +0 -15
  217. package/dist/react/EditorContext.d.ts.map +0 -1
  218. package/dist/react/EditorContext.js +0 -23
  219. package/dist/react/EditorContext.js.map +0 -1
@@ -1,5 +1,10 @@
1
+ import { getHunkSideEndBoundary, getHunkSideStartBoundary } from "./getHunkSideBoundaries.js";
1
2
  import { getDefaultHunkSeparatorHeight } from "./computeVirtualFileMetrics.js";
2
3
  //#region src/utils/virtualDiffLayout.ts
4
+ /** One-based new-file line range covered by a hunk, as `[start, end)`. */
5
+ function getHunkAdditionLineRange(hunk) {
6
+ return [getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount) + 1, getHunkSideEndBoundary(hunk.additionStart, hunk.additionCount) + 1];
7
+ }
3
8
  function getExpandedRegion({ isPartial, rangeSize, expandedHunks, hunkIndex, collapsedContextThreshold }) {
4
9
  const normalizedRangeSize = Math.max(rangeSize, 0);
5
10
  if (normalizedRangeSize === 0 || isPartial) return {
@@ -32,23 +37,23 @@ function getExpandedRegion({ isPartial, rangeSize, expandedHunks, hunkIndex, col
32
37
  function hasTrailingContext(fileDiff) {
33
38
  const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];
34
39
  if (lastHunk == null || fileDiff.isPartial || fileDiff.additionLines.length === 0 || fileDiff.deletionLines.length === 0) return false;
35
- const additionRemaining = fileDiff.additionLines.length - (lastHunk.additionLineIndex + lastHunk.additionCount);
36
- const deletionRemaining = fileDiff.deletionLines.length - (lastHunk.deletionLineIndex + lastHunk.deletionCount);
40
+ const additionRemaining = fileDiff.additionLines.length - getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount);
41
+ const deletionRemaining = fileDiff.deletionLines.length - getHunkSideEndBoundary(lastHunk.deletionStart, lastHunk.deletionCount);
37
42
  return additionRemaining > 0 || deletionRemaining > 0;
38
43
  }
39
44
  function hasTrailingContextMismatch(fileDiff) {
40
45
  const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];
41
46
  if (lastHunk == null || fileDiff.isPartial || fileDiff.additionLines.length === 0 || fileDiff.deletionLines.length === 0) return false;
42
- const additionRemaining = fileDiff.additionLines.length - (lastHunk.additionLineIndex + lastHunk.additionCount);
43
- const deletionRemaining = fileDiff.deletionLines.length - (lastHunk.deletionLineIndex + lastHunk.deletionCount);
47
+ const additionRemaining = fileDiff.additionLines.length - getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount);
48
+ const deletionRemaining = fileDiff.deletionLines.length - getHunkSideEndBoundary(lastHunk.deletionStart, lastHunk.deletionCount);
44
49
  if (additionRemaining <= 0 && deletionRemaining <= 0) return false;
45
50
  return additionRemaining !== deletionRemaining;
46
51
  }
47
52
  function getTrailingContextRangeSize({ fileDiff, errorPrefix }) {
48
53
  const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];
49
54
  if (lastHunk == null || fileDiff.isPartial || fileDiff.additionLines.length === 0 || fileDiff.deletionLines.length === 0) return 0;
50
- const additionRemaining = fileDiff.additionLines.length - (lastHunk.additionLineIndex + lastHunk.additionCount);
51
- const deletionRemaining = fileDiff.deletionLines.length - (lastHunk.deletionLineIndex + lastHunk.deletionCount);
55
+ const additionRemaining = fileDiff.additionLines.length - getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount);
56
+ const deletionRemaining = fileDiff.deletionLines.length - getHunkSideEndBoundary(lastHunk.deletionStart, lastHunk.deletionCount);
52
57
  if (additionRemaining <= 0 && deletionRemaining <= 0) return 0;
53
58
  if (additionRemaining !== deletionRemaining) throw new Error(`${errorPrefix}: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${fileDiff.name}`);
54
59
  return Math.min(additionRemaining, deletionRemaining);
@@ -77,6 +82,95 @@ function getTrailingExpandedRegion({ fileDiff, hunkIndex, expandedHunks, collaps
77
82
  renderAll: fromStart >= trailingRangeSize
78
83
  };
79
84
  }
85
+ /**
86
+ * Whether a one-based new-file line currently has (or will have on scroll) a
87
+ * rendered row under the given expansion state — the editor-facing
88
+ * visibility oracle. False only for lines hidden inside a collapsed
89
+ * unchanged region; lines outside the diff's modeled range report true so
90
+ * callers keep their existing missing-row handling. Computed from the same
91
+ * inputs as `iterateOverDiff` so layout math and the oracle cannot diverge.
92
+ */
93
+ function isAdditionLineRenderable({ fileDiff, lineNumber, expandedHunks, collapsedContextThreshold }) {
94
+ if (expandedHunks === true || fileDiff.isPartial) return true;
95
+ for (const [hunkIndex, hunk] of fileDiff.hunks.entries()) {
96
+ const [hunkStart, hunkEnd] = getHunkAdditionLineRange(hunk);
97
+ if (lineNumber < hunkStart) {
98
+ const region = getExpandedRegion({
99
+ isPartial: fileDiff.isPartial,
100
+ rangeSize: hunk.collapsedBefore,
101
+ expandedHunks,
102
+ hunkIndex,
103
+ collapsedContextThreshold
104
+ });
105
+ const gapStart = hunkStart - region.rangeSize;
106
+ return region.renderAll || lineNumber < gapStart + region.fromStart || lineNumber >= hunkStart - region.fromEnd;
107
+ }
108
+ if (lineNumber < hunkEnd) return true;
109
+ }
110
+ const trailingRegion = getTrailingExpandedRegion({
111
+ fileDiff,
112
+ hunkIndex: fileDiff.hunks.length - 1,
113
+ expandedHunks,
114
+ collapsedContextThreshold,
115
+ errorPrefix: "isAdditionLineRenderable"
116
+ });
117
+ if (trailingRegion == null || trailingRegion.renderAll) return true;
118
+ const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];
119
+ const [, trailingStart] = getHunkAdditionLineRange(lastHunk);
120
+ return lineNumber < trailingStart + trailingRegion.fromStart || lineNumber >= trailingStart + trailingRegion.rangeSize;
121
+ }
122
+ /**
123
+ * The nearest renderable new-file line at or beyond `lineNumber` in the
124
+ * given direction (one-based), or undefined when every line that way is
125
+ * hidden inside collapsed regions. Sequential caret motion uses this to skip
126
+ * over collapsed regions like code folds; it walks the hunk metadata once
127
+ * instead of probing line by line across a gap.
128
+ */
129
+ function getNearestRenderableAdditionLine({ fileDiff, lineNumber, direction, expandedHunks, collapsedContextThreshold }) {
130
+ if (expandedHunks === true || fileDiff.isPartial) return lineNumber;
131
+ const ranges = [];
132
+ let modeledEnd = 1;
133
+ for (const [hunkIndex, hunk] of fileDiff.hunks.entries()) {
134
+ const [hunkStart, hunkEnd] = getHunkAdditionLineRange(hunk);
135
+ const region = getExpandedRegion({
136
+ isPartial: fileDiff.isPartial,
137
+ rangeSize: hunk.collapsedBefore,
138
+ expandedHunks,
139
+ hunkIndex,
140
+ collapsedContextThreshold
141
+ });
142
+ const gapStart = hunkStart - region.rangeSize;
143
+ if (region.renderAll) ranges.push([gapStart, hunkStart]);
144
+ else {
145
+ if (region.fromStart > 0) ranges.push([gapStart, gapStart + region.fromStart]);
146
+ if (region.fromEnd > 0) ranges.push([hunkStart - region.fromEnd, hunkStart]);
147
+ }
148
+ ranges.push([hunkStart, hunkEnd]);
149
+ modeledEnd = hunkEnd;
150
+ }
151
+ const trailingRegion = getTrailingExpandedRegion({
152
+ fileDiff,
153
+ hunkIndex: fileDiff.hunks.length - 1,
154
+ expandedHunks,
155
+ collapsedContextThreshold,
156
+ errorPrefix: "getNearestRenderableAdditionLine"
157
+ });
158
+ if (trailingRegion != null) {
159
+ const trailingStart = modeledEnd;
160
+ modeledEnd = trailingStart + trailingRegion.rangeSize;
161
+ if (trailingRegion.renderAll) ranges.push([trailingStart, modeledEnd]);
162
+ else if (trailingRegion.fromStart > 0) ranges.push([trailingStart, trailingStart + trailingRegion.fromStart]);
163
+ }
164
+ if (lineNumber >= modeledEnd) return lineNumber;
165
+ if (direction === "down") {
166
+ for (const [start, end] of ranges) if (end > lineNumber) return Math.max(start, lineNumber);
167
+ return;
168
+ }
169
+ for (let index = ranges.length - 1; index >= 0; index--) {
170
+ const [start, end] = ranges[index];
171
+ if (start <= lineNumber) return Math.min(end - 1, lineNumber);
172
+ }
173
+ }
80
174
  function getHunkSeparatorHeight({ type, metrics }) {
81
175
  return metrics.hunkSeparatorHeight ?? getDefaultHunkSeparatorHeight(type);
82
176
  }
@@ -136,6 +230,6 @@ function getTrailingHunkSeparatorLayout({ type, metrics }) {
136
230
  };
137
231
  }
138
232
  //#endregion
139
- export { getExpandedRegion, getHunkSeparatorGap, getHunkSeparatorHeight, getLeadingHunkSeparatorLayout, getTrailingContextRangeSize, getTrailingExpandedRegion, getTrailingHunkSeparatorLayout, hasLeadingHunkSeparator, hasTrailingContext, hasTrailingContextMismatch, hasTrailingHunkSeparator };
233
+ export { getExpandedRegion, getHunkAdditionLineRange, getHunkSeparatorGap, getHunkSeparatorHeight, getLeadingHunkSeparatorLayout, getNearestRenderableAdditionLine, getTrailingContextRangeSize, getTrailingExpandedRegion, getTrailingHunkSeparatorLayout, hasLeadingHunkSeparator, hasTrailingContext, hasTrailingContextMismatch, hasTrailingHunkSeparator, isAdditionLineRenderable };
140
234
 
141
235
  //# sourceMappingURL=virtualDiffLayout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtualDiffLayout.js","names":[],"sources":["../../src/utils/virtualDiffLayout.ts"],"sourcesContent":["import type {\n FileDiffMetadata,\n HunkExpansionRegion,\n HunkSeparators,\n VirtualFileMetrics,\n} from '../types';\nimport { getDefaultHunkSeparatorHeight } from './computeVirtualFileMetrics';\n\nexport interface ExpandedRegionResult {\n fromStart: number;\n fromEnd: number;\n rangeSize: number;\n collapsedLines: number;\n renderAll: boolean;\n}\n\nexport interface GetExpandedRegionProps {\n isPartial: boolean;\n rangeSize: number;\n expandedHunks: Map<number, HunkExpansionRegion> | true | undefined;\n hunkIndex: number;\n collapsedContextThreshold: number;\n}\n\nexport interface GetTrailingContextRangeSizeProps {\n fileDiff: FileDiffMetadata;\n errorPrefix: string;\n}\n\nexport interface GetTrailingExpandedRegionProps extends GetTrailingContextRangeSizeProps {\n hunkIndex: number;\n expandedHunks: GetExpandedRegionProps['expandedHunks'];\n collapsedContextThreshold: number;\n}\n\nexport interface HunkSeparatorLayout {\n height: number;\n gapBefore: number;\n gapAfter: number;\n totalHeight: number;\n}\n\ninterface HunkSeparatorBaseProps {\n type: HunkSeparators;\n metrics: VirtualFileMetrics;\n}\n\ninterface LeadingHunkSeparatorLayoutProps extends HunkSeparatorBaseProps {\n hunkIndex: number;\n hunkSpecs: string | undefined;\n}\n\n// Converts a collapsed unchanged range into the slices that should render near\n// the start and end of that range for the active hunk expansion state.\nexport function getExpandedRegion({\n isPartial,\n rangeSize,\n expandedHunks,\n hunkIndex,\n collapsedContextThreshold,\n}: GetExpandedRegionProps): ExpandedRegionResult {\n const normalizedRangeSize = Math.max(rangeSize, 0);\n if (normalizedRangeSize === 0 || isPartial) {\n return {\n fromStart: 0,\n fromEnd: 0,\n rangeSize: normalizedRangeSize,\n collapsedLines: normalizedRangeSize,\n renderAll: false,\n };\n }\n\n if (\n expandedHunks === true ||\n normalizedRangeSize <= collapsedContextThreshold\n ) {\n return {\n fromStart: normalizedRangeSize,\n fromEnd: 0,\n rangeSize: normalizedRangeSize,\n collapsedLines: 0,\n renderAll: true,\n };\n }\n\n const region = expandedHunks?.get(hunkIndex);\n const fromStart = Math.min(\n Math.max(region?.fromStart ?? 0, 0),\n normalizedRangeSize\n );\n const fromEnd = Math.min(\n Math.max(region?.fromEnd ?? 0, 0),\n normalizedRangeSize\n );\n const expandedCount = fromStart + fromEnd;\n const renderAll = expandedCount >= normalizedRangeSize;\n return {\n fromStart: renderAll ? normalizedRangeSize : fromStart,\n fromEnd: renderAll ? 0 : fromEnd,\n rangeSize: normalizedRangeSize,\n collapsedLines: Math.max(normalizedRangeSize - expandedCount, 0),\n renderAll,\n };\n}\n\nexport function hasTrailingContext(fileDiff: FileDiffMetadata): boolean {\n const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];\n if (\n lastHunk == null ||\n fileDiff.isPartial ||\n fileDiff.additionLines.length === 0 ||\n fileDiff.deletionLines.length === 0\n ) {\n return false;\n }\n\n const additionRemaining =\n fileDiff.additionLines.length -\n (lastHunk.additionLineIndex + lastHunk.additionCount);\n const deletionRemaining =\n fileDiff.deletionLines.length -\n (lastHunk.deletionLineIndex + lastHunk.deletionCount);\n\n return additionRemaining > 0 || deletionRemaining > 0;\n}\n\n// Returns true when trailing-context line counts disagree between sides.\nexport function hasTrailingContextMismatch(\n fileDiff: FileDiffMetadata\n): boolean {\n const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];\n if (\n lastHunk == null ||\n fileDiff.isPartial ||\n fileDiff.additionLines.length === 0 ||\n fileDiff.deletionLines.length === 0\n ) {\n return false;\n }\n\n const additionRemaining =\n fileDiff.additionLines.length -\n (lastHunk.additionLineIndex + lastHunk.additionCount);\n const deletionRemaining =\n fileDiff.deletionLines.length -\n (lastHunk.deletionLineIndex + lastHunk.deletionCount);\n\n if (additionRemaining <= 0 && deletionRemaining <= 0) {\n return false;\n }\n\n return additionRemaining !== deletionRemaining;\n}\n\n// Measures the unchanged tail after the final hunk. Both sides must have the\n// same remaining length because trailing context represents paired lines.\nexport function getTrailingContextRangeSize({\n fileDiff,\n errorPrefix,\n}: GetTrailingContextRangeSizeProps): number {\n const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];\n if (\n lastHunk == null ||\n fileDiff.isPartial ||\n fileDiff.additionLines.length === 0 ||\n fileDiff.deletionLines.length === 0\n ) {\n return 0;\n }\n\n const additionRemaining =\n fileDiff.additionLines.length -\n (lastHunk.additionLineIndex + lastHunk.additionCount);\n const deletionRemaining =\n fileDiff.deletionLines.length -\n (lastHunk.deletionLineIndex + lastHunk.deletionCount);\n\n if (additionRemaining <= 0 && deletionRemaining <= 0) {\n return 0;\n }\n\n if (additionRemaining !== deletionRemaining) {\n throw new Error(\n `${errorPrefix}: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${fileDiff.name}`\n );\n }\n return Math.min(additionRemaining, deletionRemaining);\n}\n\nexport function getTrailingExpandedRegion({\n fileDiff,\n hunkIndex,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix,\n}: GetTrailingExpandedRegionProps): ExpandedRegionResult | undefined {\n if (hunkIndex !== fileDiff.hunks.length - 1) {\n return undefined;\n }\n\n const trailingRangeSize = getTrailingContextRangeSize({\n fileDiff,\n errorPrefix,\n });\n if (trailingRangeSize <= 0) {\n return undefined;\n }\n\n if (\n expandedHunks === true ||\n trailingRangeSize <= collapsedContextThreshold\n ) {\n return {\n fromStart: trailingRangeSize,\n fromEnd: 0,\n rangeSize: trailingRangeSize,\n collapsedLines: 0,\n renderAll: true,\n };\n }\n\n // The final trailing separator only exposes upward partial expansion. Treat it\n // as a bottom-only pseudo-hunk and ignore unsupported downward expansion.\n const region = expandedHunks?.get(fileDiff.hunks.length);\n const fromStart = Math.min(\n Math.max(region?.fromStart ?? 0, 0),\n trailingRangeSize\n );\n return {\n fromStart,\n fromEnd: 0,\n rangeSize: trailingRangeSize,\n collapsedLines: trailingRangeSize - fromStart,\n renderAll: fromStart >= trailingRangeSize,\n };\n}\n\nexport function getHunkSeparatorHeight({\n type,\n metrics,\n}: HunkSeparatorBaseProps): number {\n return metrics.hunkSeparatorHeight ?? getDefaultHunkSeparatorHeight(type);\n}\n\nexport function getHunkSeparatorGap({\n type,\n metrics,\n}: HunkSeparatorBaseProps): number {\n return type === 'simple' || type === 'metadata' || type === 'line-info-basic'\n ? 0\n : metrics.spacing;\n}\n\nexport function hasLeadingHunkSeparator({\n type,\n hunkIndex,\n hunkSpecs,\n}: Omit<LeadingHunkSeparatorLayoutProps, 'metrics'>): boolean {\n switch (type) {\n case 'simple':\n return hunkIndex > 0;\n case 'metadata':\n return hunkSpecs != null;\n case 'line-info':\n case 'line-info-basic':\n case 'custom':\n return true;\n }\n}\n\nexport function hasTrailingHunkSeparator(type: HunkSeparators): boolean {\n return type !== 'simple' && type !== 'metadata';\n}\n\n// Mirrors the renderer/CSS spacing rules for the separator shown before a hunk.\nexport function getLeadingHunkSeparatorLayout({\n type,\n metrics,\n hunkIndex,\n hunkSpecs,\n}: LeadingHunkSeparatorLayoutProps): HunkSeparatorLayout | undefined {\n if (!hasLeadingHunkSeparator({ type, hunkIndex, hunkSpecs })) {\n return undefined;\n }\n\n const height = getHunkSeparatorHeight({ type, metrics });\n const gap = getHunkSeparatorGap({ type, metrics });\n const gapBefore = hunkIndex > 0 ? gap : 0;\n const gapAfter = gap;\n return {\n height,\n gapBefore,\n gapAfter,\n totalHeight: gapBefore + height + gapAfter,\n };\n}\n\n// Mirrors the renderer/CSS spacing rules for the separator shown after the last\n// hunk when trailing unchanged context is collapsed.\nexport function getTrailingHunkSeparatorLayout({\n type,\n metrics,\n}: HunkSeparatorBaseProps): HunkSeparatorLayout | undefined {\n if (!hasTrailingHunkSeparator(type)) {\n return undefined;\n }\n\n const height = getHunkSeparatorHeight({ type, metrics });\n const gapBefore = getHunkSeparatorGap({ type, metrics });\n return {\n height,\n gapBefore,\n gapAfter: 0,\n totalHeight: gapBefore + height,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"virtualDiffLayout.js","names":[],"sources":["../../src/utils/virtualDiffLayout.ts"],"sourcesContent":["import type {\n FileDiffMetadata,\n Hunk,\n HunkExpansionRegion,\n HunkSeparators,\n VirtualFileMetrics,\n} from '../types';\nimport { getDefaultHunkSeparatorHeight } from './computeVirtualFileMetrics';\nimport {\n getHunkSideEndBoundary,\n getHunkSideStartBoundary,\n} from './getHunkSideBoundaries';\n\nexport interface ExpandedRegionResult {\n fromStart: number;\n fromEnd: number;\n rangeSize: number;\n collapsedLines: number;\n renderAll: boolean;\n}\n\nexport interface GetExpandedRegionProps {\n isPartial: boolean;\n rangeSize: number;\n expandedHunks: Map<number, HunkExpansionRegion> | true | undefined;\n hunkIndex: number;\n collapsedContextThreshold: number;\n}\n\nexport interface GetTrailingContextRangeSizeProps {\n fileDiff: FileDiffMetadata;\n errorPrefix: string;\n}\n\nexport interface GetTrailingExpandedRegionProps extends GetTrailingContextRangeSizeProps {\n hunkIndex: number;\n expandedHunks: GetExpandedRegionProps['expandedHunks'];\n collapsedContextThreshold: number;\n}\n\n/** One-based new-file line range covered by a hunk, as `[start, end)`. */\nexport function getHunkAdditionLineRange(hunk: Hunk): [number, number] {\n return [\n getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount) + 1,\n getHunkSideEndBoundary(hunk.additionStart, hunk.additionCount) + 1,\n ];\n}\n\nexport interface HunkSeparatorLayout {\n height: number;\n gapBefore: number;\n gapAfter: number;\n totalHeight: number;\n}\n\ninterface HunkSeparatorBaseProps {\n type: HunkSeparators;\n metrics: VirtualFileMetrics;\n}\n\ninterface LeadingHunkSeparatorLayoutProps extends HunkSeparatorBaseProps {\n hunkIndex: number;\n hunkSpecs: string | undefined;\n}\n\n// Converts a collapsed unchanged range into the slices that should render near\n// the start and end of that range for the active hunk expansion state.\nexport function getExpandedRegion({\n isPartial,\n rangeSize,\n expandedHunks,\n hunkIndex,\n collapsedContextThreshold,\n}: GetExpandedRegionProps): ExpandedRegionResult {\n const normalizedRangeSize = Math.max(rangeSize, 0);\n if (normalizedRangeSize === 0 || isPartial) {\n return {\n fromStart: 0,\n fromEnd: 0,\n rangeSize: normalizedRangeSize,\n collapsedLines: normalizedRangeSize,\n renderAll: false,\n };\n }\n\n if (\n expandedHunks === true ||\n normalizedRangeSize <= collapsedContextThreshold\n ) {\n return {\n fromStart: normalizedRangeSize,\n fromEnd: 0,\n rangeSize: normalizedRangeSize,\n collapsedLines: 0,\n renderAll: true,\n };\n }\n\n const region = expandedHunks?.get(hunkIndex);\n const fromStart = Math.min(\n Math.max(region?.fromStart ?? 0, 0),\n normalizedRangeSize\n );\n const fromEnd = Math.min(\n Math.max(region?.fromEnd ?? 0, 0),\n normalizedRangeSize\n );\n const expandedCount = fromStart + fromEnd;\n const renderAll = expandedCount >= normalizedRangeSize;\n return {\n fromStart: renderAll ? normalizedRangeSize : fromStart,\n fromEnd: renderAll ? 0 : fromEnd,\n rangeSize: normalizedRangeSize,\n collapsedLines: Math.max(normalizedRangeSize - expandedCount, 0),\n renderAll,\n };\n}\n\nexport function hasTrailingContext(fileDiff: FileDiffMetadata): boolean {\n const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];\n if (\n lastHunk == null ||\n fileDiff.isPartial ||\n fileDiff.additionLines.length === 0 ||\n fileDiff.deletionLines.length === 0\n ) {\n return false;\n }\n\n const additionRemaining =\n fileDiff.additionLines.length -\n getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount);\n const deletionRemaining =\n fileDiff.deletionLines.length -\n getHunkSideEndBoundary(lastHunk.deletionStart, lastHunk.deletionCount);\n\n return additionRemaining > 0 || deletionRemaining > 0;\n}\n\n// Returns true when trailing-context line counts disagree between sides.\nexport function hasTrailingContextMismatch(\n fileDiff: FileDiffMetadata\n): boolean {\n const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];\n if (\n lastHunk == null ||\n fileDiff.isPartial ||\n fileDiff.additionLines.length === 0 ||\n fileDiff.deletionLines.length === 0\n ) {\n return false;\n }\n\n const additionRemaining =\n fileDiff.additionLines.length -\n getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount);\n const deletionRemaining =\n fileDiff.deletionLines.length -\n getHunkSideEndBoundary(lastHunk.deletionStart, lastHunk.deletionCount);\n\n if (additionRemaining <= 0 && deletionRemaining <= 0) {\n return false;\n }\n\n return additionRemaining !== deletionRemaining;\n}\n\n// Measures the unchanged tail after the final hunk. Both sides must have the\n// same remaining length because trailing context represents paired lines.\nexport function getTrailingContextRangeSize({\n fileDiff,\n errorPrefix,\n}: GetTrailingContextRangeSizeProps): number {\n const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];\n if (\n lastHunk == null ||\n fileDiff.isPartial ||\n fileDiff.additionLines.length === 0 ||\n fileDiff.deletionLines.length === 0\n ) {\n return 0;\n }\n\n const additionRemaining =\n fileDiff.additionLines.length -\n getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount);\n const deletionRemaining =\n fileDiff.deletionLines.length -\n getHunkSideEndBoundary(lastHunk.deletionStart, lastHunk.deletionCount);\n\n if (additionRemaining <= 0 && deletionRemaining <= 0) {\n return 0;\n }\n\n if (additionRemaining !== deletionRemaining) {\n throw new Error(\n `${errorPrefix}: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${fileDiff.name}`\n );\n }\n return Math.min(additionRemaining, deletionRemaining);\n}\n\nexport function getTrailingExpandedRegion({\n fileDiff,\n hunkIndex,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix,\n}: GetTrailingExpandedRegionProps): ExpandedRegionResult | undefined {\n if (hunkIndex !== fileDiff.hunks.length - 1) {\n return undefined;\n }\n\n const trailingRangeSize = getTrailingContextRangeSize({\n fileDiff,\n errorPrefix,\n });\n if (trailingRangeSize <= 0) {\n return undefined;\n }\n\n if (\n expandedHunks === true ||\n trailingRangeSize <= collapsedContextThreshold\n ) {\n return {\n fromStart: trailingRangeSize,\n fromEnd: 0,\n rangeSize: trailingRangeSize,\n collapsedLines: 0,\n renderAll: true,\n };\n }\n\n // The final trailing separator only exposes upward partial expansion. Treat it\n // as a bottom-only pseudo-hunk and ignore unsupported downward expansion.\n const region = expandedHunks?.get(fileDiff.hunks.length);\n const fromStart = Math.min(\n Math.max(region?.fromStart ?? 0, 0),\n trailingRangeSize\n );\n return {\n fromStart,\n fromEnd: 0,\n rangeSize: trailingRangeSize,\n collapsedLines: trailingRangeSize - fromStart,\n renderAll: fromStart >= trailingRangeSize,\n };\n}\n\nexport interface IsAdditionLineRenderableProps {\n fileDiff: FileDiffMetadata;\n /** One-based line number in the new file. */\n lineNumber: number;\n expandedHunks: Map<number, HunkExpansionRegion> | true | undefined;\n collapsedContextThreshold: number;\n}\n\n/**\n * Whether a one-based new-file line currently has (or will have on scroll) a\n * rendered row under the given expansion state — the editor-facing\n * visibility oracle. False only for lines hidden inside a collapsed\n * unchanged region; lines outside the diff's modeled range report true so\n * callers keep their existing missing-row handling. Computed from the same\n * inputs as `iterateOverDiff` so layout math and the oracle cannot diverge.\n */\nexport function isAdditionLineRenderable({\n fileDiff,\n lineNumber,\n expandedHunks,\n collapsedContextThreshold,\n}: IsAdditionLineRenderableProps): boolean {\n if (expandedHunks === true || fileDiff.isPartial) {\n return true;\n }\n\n for (const [hunkIndex, hunk] of fileDiff.hunks.entries()) {\n const [hunkStart, hunkEnd] = getHunkAdditionLineRange(hunk);\n if (lineNumber < hunkStart) {\n // Inside the collapsed gap before this hunk: renderable only within\n // the expanded slices at the gap's edges.\n const region = getExpandedRegion({\n isPartial: fileDiff.isPartial,\n rangeSize: hunk.collapsedBefore,\n expandedHunks,\n hunkIndex,\n collapsedContextThreshold,\n });\n const gapStart = hunkStart - region.rangeSize;\n return (\n region.renderAll ||\n lineNumber < gapStart + region.fromStart ||\n lineNumber >= hunkStart - region.fromEnd\n );\n }\n if (lineNumber < hunkEnd) {\n return true;\n }\n }\n\n const trailingRegion = getTrailingExpandedRegion({\n fileDiff,\n hunkIndex: fileDiff.hunks.length - 1,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix: 'isAdditionLineRenderable',\n });\n if (trailingRegion == null || trailingRegion.renderAll) {\n return true;\n }\n const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];\n const [, trailingStart] = getHunkAdditionLineRange(lastHunk);\n return (\n lineNumber < trailingStart + trailingRegion.fromStart ||\n lineNumber >= trailingStart + trailingRegion.rangeSize\n );\n}\n\nexport interface GetNearestRenderableAdditionLineProps extends IsAdditionLineRenderableProps {\n direction: 'up' | 'down';\n}\n\n/**\n * The nearest renderable new-file line at or beyond `lineNumber` in the\n * given direction (one-based), or undefined when every line that way is\n * hidden inside collapsed regions. Sequential caret motion uses this to skip\n * over collapsed regions like code folds; it walks the hunk metadata once\n * instead of probing line by line across a gap.\n */\nexport function getNearestRenderableAdditionLine({\n fileDiff,\n lineNumber,\n direction,\n expandedHunks,\n collapsedContextThreshold,\n}: GetNearestRenderableAdditionLineProps): number | undefined {\n if (expandedHunks === true || fileDiff.isPartial) {\n return lineNumber;\n }\n\n // Renderable [start, end) line ranges in ascending order, plus the\n // exclusive end of the modeled range — anything past it (the editor's\n // phantom document-end line) counts as renderable.\n const ranges: Array<[start: number, end: number]> = [];\n let modeledEnd = 1;\n for (const [hunkIndex, hunk] of fileDiff.hunks.entries()) {\n const [hunkStart, hunkEnd] = getHunkAdditionLineRange(hunk);\n const region = getExpandedRegion({\n isPartial: fileDiff.isPartial,\n rangeSize: hunk.collapsedBefore,\n expandedHunks,\n hunkIndex,\n collapsedContextThreshold,\n });\n const gapStart = hunkStart - region.rangeSize;\n if (region.renderAll) {\n ranges.push([gapStart, hunkStart]);\n } else {\n if (region.fromStart > 0) {\n ranges.push([gapStart, gapStart + region.fromStart]);\n }\n if (region.fromEnd > 0) {\n ranges.push([hunkStart - region.fromEnd, hunkStart]);\n }\n }\n ranges.push([hunkStart, hunkEnd]);\n modeledEnd = hunkEnd;\n }\n const trailingRegion = getTrailingExpandedRegion({\n fileDiff,\n hunkIndex: fileDiff.hunks.length - 1,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix: 'getNearestRenderableAdditionLine',\n });\n if (trailingRegion != null) {\n const trailingStart = modeledEnd;\n modeledEnd = trailingStart + trailingRegion.rangeSize;\n if (trailingRegion.renderAll) {\n ranges.push([trailingStart, modeledEnd]);\n } else if (trailingRegion.fromStart > 0) {\n ranges.push([trailingStart, trailingStart + trailingRegion.fromStart]);\n }\n }\n\n if (lineNumber >= modeledEnd) {\n return lineNumber;\n }\n if (direction === 'down') {\n for (const [start, end] of ranges) {\n if (end > lineNumber) {\n return Math.max(start, lineNumber);\n }\n }\n return undefined;\n }\n for (let index = ranges.length - 1; index >= 0; index--) {\n const [start, end] = ranges[index];\n if (start <= lineNumber) {\n return Math.min(end - 1, lineNumber);\n }\n }\n return undefined;\n}\n\nexport function getHunkSeparatorHeight({\n type,\n metrics,\n}: HunkSeparatorBaseProps): number {\n return metrics.hunkSeparatorHeight ?? getDefaultHunkSeparatorHeight(type);\n}\n\nexport function getHunkSeparatorGap({\n type,\n metrics,\n}: HunkSeparatorBaseProps): number {\n return type === 'simple' || type === 'metadata' || type === 'line-info-basic'\n ? 0\n : metrics.spacing;\n}\n\nexport function hasLeadingHunkSeparator({\n type,\n hunkIndex,\n hunkSpecs,\n}: Omit<LeadingHunkSeparatorLayoutProps, 'metrics'>): boolean {\n switch (type) {\n case 'simple':\n return hunkIndex > 0;\n case 'metadata':\n return hunkSpecs != null;\n case 'line-info':\n case 'line-info-basic':\n case 'custom':\n return true;\n }\n}\n\nexport function hasTrailingHunkSeparator(type: HunkSeparators): boolean {\n return type !== 'simple' && type !== 'metadata';\n}\n\n// Mirrors the renderer/CSS spacing rules for the separator shown before a hunk.\nexport function getLeadingHunkSeparatorLayout({\n type,\n metrics,\n hunkIndex,\n hunkSpecs,\n}: LeadingHunkSeparatorLayoutProps): HunkSeparatorLayout | undefined {\n if (!hasLeadingHunkSeparator({ type, hunkIndex, hunkSpecs })) {\n return undefined;\n }\n\n const height = getHunkSeparatorHeight({ type, metrics });\n const gap = getHunkSeparatorGap({ type, metrics });\n const gapBefore = hunkIndex > 0 ? gap : 0;\n const gapAfter = gap;\n return {\n height,\n gapBefore,\n gapAfter,\n totalHeight: gapBefore + height + gapAfter,\n };\n}\n\n// Mirrors the renderer/CSS spacing rules for the separator shown after the last\n// hunk when trailing unchanged context is collapsed.\nexport function getTrailingHunkSeparatorLayout({\n type,\n metrics,\n}: HunkSeparatorBaseProps): HunkSeparatorLayout | undefined {\n if (!hasTrailingHunkSeparator(type)) {\n return undefined;\n }\n\n const height = getHunkSeparatorHeight({ type, metrics });\n const gapBefore = getHunkSeparatorGap({ type, metrics });\n return {\n height,\n gapBefore,\n gapAfter: 0,\n totalHeight: gapBefore + height,\n };\n}\n"],"mappings":"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"}
@@ -12536,7 +12536,14 @@ function getLineNodes(nodes) {
12536
12536
  throw new Error("getLineNodes: Unable to find children");
12537
12537
  }
12538
12538
  //#endregion
12539
- //#region src/utils/virtualDiffLayout.ts
12539
+ //#region src/utils/getHunkSideBoundaries.ts
12540
+ /** Converts a unified hunk side's start/count into its consumed-file range. */
12541
+ function getHunkSideStartBoundary(start, count) {
12542
+ return start - (count === 0 ? 0 : 1);
12543
+ }
12544
+ function getHunkSideEndBoundary(start, count) {
12545
+ return getHunkSideStartBoundary(start, count) + count;
12546
+ }
12540
12547
  function getExpandedRegion({ isPartial, rangeSize, expandedHunks, hunkIndex, collapsedContextThreshold }) {
12541
12548
  const normalizedRangeSize = Math.max(rangeSize, 0);
12542
12549
  if (normalizedRangeSize === 0 || isPartial) return {
@@ -12569,8 +12576,8 @@ function getExpandedRegion({ isPartial, rangeSize, expandedHunks, hunkIndex, col
12569
12576
  function getTrailingContextRangeSize({ fileDiff, errorPrefix }) {
12570
12577
  const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];
12571
12578
  if (lastHunk == null || fileDiff.isPartial || fileDiff.additionLines.length === 0 || fileDiff.deletionLines.length === 0) return 0;
12572
- const additionRemaining = fileDiff.additionLines.length - (lastHunk.additionLineIndex + lastHunk.additionCount);
12573
- const deletionRemaining = fileDiff.deletionLines.length - (lastHunk.deletionLineIndex + lastHunk.deletionCount);
12579
+ const additionRemaining = fileDiff.additionLines.length - getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount);
12580
+ const deletionRemaining = fileDiff.deletionLines.length - getHunkSideEndBoundary(lastHunk.deletionStart, lastHunk.deletionCount);
12574
12581
  if (additionRemaining <= 0 && deletionRemaining <= 0) return 0;
12575
12582
  if (additionRemaining !== deletionRemaining) throw new Error(`${errorPrefix}: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${fileDiff.name}`);
12576
12583
  return Math.min(additionRemaining, deletionRemaining);
@@ -12661,6 +12668,10 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
12661
12668
  const hunk = diff.hunks[hunkIndex];
12662
12669
  if (hunk == null) throw new Error("iterateOverDiff: invalid hunk index");
12663
12670
  if (state.shouldBreak()) break;
12671
+ const deletionBoundary = getHunkSideStartBoundary(hunk.deletionStart, hunk.deletionCount);
12672
+ const additionBoundary = getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount);
12673
+ const deletionStartIndex = !diff.isPartial && hunk.deletionCount === 0 ? deletionBoundary : hunk.deletionLineIndex;
12674
+ const additionStartIndex = !diff.isPartial && hunk.additionCount === 0 ? additionBoundary : hunk.additionLineIndex;
12664
12675
  const leadingRegion = getExpandedRegion({
12665
12676
  isPartial: diff.isPartial,
12666
12677
  rangeSize: hunk.collapsedBefore,
@@ -12690,10 +12701,10 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
12690
12701
  if (!state.shouldSkip(expandedLineCount, expandedLineCount)) {
12691
12702
  let unifiedLineIndex = hunk.unifiedLineStart - leadingRegion.rangeSize;
12692
12703
  let splitLineIndex = hunk.splitLineStart - leadingRegion.rangeSize;
12693
- let deletionLineIndex = hunk.deletionLineIndex - leadingRegion.rangeSize;
12694
- let additionLineIndex = hunk.additionLineIndex - leadingRegion.rangeSize;
12695
- let deletionLineNumber = hunk.deletionStart - leadingRegion.rangeSize;
12696
- let additionLineNumber = hunk.additionStart - leadingRegion.rangeSize;
12704
+ let deletionLineIndex = deletionStartIndex - leadingRegion.rangeSize;
12705
+ let additionLineIndex = additionStartIndex - leadingRegion.rangeSize;
12706
+ let deletionLineNumber = deletionBoundary + 1 - leadingRegion.rangeSize;
12707
+ let additionLineNumber = additionBoundary + 1 - leadingRegion.rangeSize;
12697
12708
  if (walkContextLines(state, leadingRegion.fromStart, diffStyle, (index) => {
12698
12709
  return state.emit({
12699
12710
  hunkIndex,
@@ -12719,10 +12730,10 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
12719
12730
  })) break hunkIterator;
12720
12731
  unifiedLineIndex = hunk.unifiedLineStart - leadingRegion.fromEnd;
12721
12732
  splitLineIndex = hunk.splitLineStart - leadingRegion.fromEnd;
12722
- deletionLineIndex = hunk.deletionLineIndex - leadingRegion.fromEnd;
12723
- additionLineIndex = hunk.additionLineIndex - leadingRegion.fromEnd;
12724
- deletionLineNumber = hunk.deletionStart - leadingRegion.fromEnd;
12725
- additionLineNumber = hunk.additionStart - leadingRegion.fromEnd;
12733
+ deletionLineIndex = deletionStartIndex - leadingRegion.fromEnd;
12734
+ additionLineIndex = additionStartIndex - leadingRegion.fromEnd;
12735
+ deletionLineNumber = deletionBoundary + 1 - leadingRegion.fromEnd;
12736
+ additionLineNumber = additionBoundary + 1 - leadingRegion.fromEnd;
12726
12737
  if (walkContextLines(state, leadingRegion.fromEnd, diffStyle, (index) => {
12727
12738
  return state.emit({
12728
12739
  hunkIndex,
@@ -12754,10 +12765,10 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
12754
12765
  }
12755
12766
  let unifiedLineIndex = hunk.unifiedLineStart;
12756
12767
  let splitLineIndex = hunk.splitLineStart;
12757
- let deletionLineIndex = hunk.deletionLineIndex;
12758
- let additionLineIndex = hunk.additionLineIndex;
12759
- let deletionLineNumber = hunk.deletionStart;
12760
- let additionLineNumber = hunk.additionStart;
12768
+ let deletionLineIndex = deletionStartIndex;
12769
+ let additionLineIndex = additionStartIndex;
12770
+ let deletionLineNumber = deletionBoundary + 1;
12771
+ let additionLineNumber = additionBoundary + 1;
12761
12772
  const lastContent = hunk.hunkContent.at(-1);
12762
12773
  for (const content of hunk.hunkContent) {
12763
12774
  if (state.shouldBreak()) break hunkIterator;
@@ -13366,16 +13377,45 @@ function renderTwoFiles({ deletionFile, additionFile, deletionInfo, additionInfo
13366
13377
  //#region src/utils/computeFileOffsets.ts
13367
13378
  const LINE_FEED = 10;
13368
13379
  const CARRIAGE_RETURN = 13;
13380
+ const DENSE_LINE_BREAK_BLOCK_SIZE = 4;
13381
+ const DENSE_LINE_BREAK_BLOCK_SPAN = 40;
13369
13382
  /**
13370
13383
  * Computes line start offsets for a string.
13371
13384
  */
13372
13385
  function computeLineOffsets(contents) {
13373
13386
  const offsets = [0];
13374
- for (let i = 0; i < contents.length; i++) {
13375
- const char = contents.charCodeAt(i);
13376
- if (char === LINE_FEED || char === CARRIAGE_RETURN) {
13377
- if (char === CARRIAGE_RETURN && i + 1 < contents.length && contents.charCodeAt(i + 1) === LINE_FEED) i++;
13378
- offsets.push(i + 1);
13387
+ let carriageReturn = contents.indexOf("\r");
13388
+ let lineFeed = contents.indexOf("\n");
13389
+ let blockStart = 0;
13390
+ let blockBreaks = 0;
13391
+ while (carriageReturn !== -1 || lineFeed !== -1) {
13392
+ let nextOffset;
13393
+ if (carriageReturn !== -1 && (lineFeed === -1 || carriageReturn < lineFeed)) if (lineFeed === carriageReturn + 1) {
13394
+ nextOffset = lineFeed + 1;
13395
+ carriageReturn = contents.indexOf("\r", nextOffset);
13396
+ lineFeed = contents.indexOf("\n", nextOffset);
13397
+ } else {
13398
+ nextOffset = carriageReturn + 1;
13399
+ carriageReturn = contents.indexOf("\r", nextOffset);
13400
+ }
13401
+ else {
13402
+ nextOffset = lineFeed + 1;
13403
+ lineFeed = contents.indexOf("\n", nextOffset);
13404
+ }
13405
+ offsets.push(nextOffset);
13406
+ if (++blockBreaks === DENSE_LINE_BREAK_BLOCK_SIZE) {
13407
+ if (nextOffset - blockStart <= DENSE_LINE_BREAK_BLOCK_SPAN) {
13408
+ for (let i = nextOffset; i < contents.length; i++) {
13409
+ const char = contents.charCodeAt(i);
13410
+ if (char === LINE_FEED || char === CARRIAGE_RETURN) {
13411
+ if (char === CARRIAGE_RETURN && i + 1 < contents.length && contents.charCodeAt(i + 1) === LINE_FEED) i++;
13412
+ offsets.push(i + 1);
13413
+ }
13414
+ }
13415
+ return offsets;
13416
+ }
13417
+ blockStart = nextOffset;
13418
+ blockBreaks = 0;
13379
13419
  }
13380
13420
  }
13381
13421
  return offsets;