@pierre/diffs 1.6.0-beta.1 → 1.6.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 (151) hide show
  1. package/dist/components/CodeView.d.ts +3 -2
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +18 -14
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +31 -9
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +165 -84
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +32 -9
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +213 -124
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/UnresolvedFile.d.ts +14 -4
  14. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  15. package/dist/components/UnresolvedFile.js +35 -16
  16. package/dist/components/UnresolvedFile.js.map +1 -1
  17. package/dist/components/VirtualizedFile.d.ts +3 -2
  18. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  19. package/dist/components/VirtualizedFile.js +28 -20
  20. package/dist/components/VirtualizedFile.js.map +1 -1
  21. package/dist/components/VirtualizedFileDiff.d.ts +3 -2
  22. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  23. package/dist/components/VirtualizedFileDiff.js +31 -24
  24. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  25. package/dist/constants.d.ts +2 -1
  26. package/dist/constants.d.ts.map +1 -1
  27. package/dist/constants.js +2 -1
  28. package/dist/constants.js.map +1 -1
  29. package/dist/editor/platform.d.ts +1 -1
  30. package/dist/editor/platform.js +5 -4
  31. package/dist/editor/platform.js.map +1 -1
  32. package/dist/editor/selection.d.ts +2 -2
  33. package/dist/editor/selection.d.ts.map +1 -1
  34. package/dist/editor/selection.js +31 -6
  35. package/dist/editor/selection.js.map +1 -1
  36. package/dist/highlighter/shared_highlighter.d.ts +2 -1
  37. package/dist/highlighter/shared_highlighter.d.ts.map +1 -1
  38. package/dist/highlighter/shared_highlighter.js +2 -2
  39. package/dist/highlighter/shared_highlighter.js.map +1 -1
  40. package/dist/index.d.ts +10 -5
  41. package/dist/index.js +8 -3
  42. package/dist/react/CodeView.d.ts +4 -3
  43. package/dist/react/CodeView.d.ts.map +1 -1
  44. package/dist/react/CodeView.js +48 -39
  45. package/dist/react/CodeView.js.map +1 -1
  46. package/dist/react/File.js +3 -3
  47. package/dist/react/File.js.map +1 -1
  48. package/dist/react/FileDiff.js +3 -3
  49. package/dist/react/FileDiff.js.map +1 -1
  50. package/dist/react/MultiFileDiff.js +2 -2
  51. package/dist/react/MultiFileDiff.js.map +1 -1
  52. package/dist/react/PatchDiff.js +3 -3
  53. package/dist/react/PatchDiff.js.map +1 -1
  54. package/dist/react/UnresolvedFile.js +2 -2
  55. package/dist/react/UnresolvedFile.js.map +1 -1
  56. package/dist/react/index.d.ts +2 -2
  57. package/dist/react/types.d.ts +4 -2
  58. package/dist/react/types.d.ts.map +1 -1
  59. package/dist/react/utils/renderDiffChildren.js +1 -1
  60. package/dist/react/utils/renderDiffChildren.js.map +1 -1
  61. package/dist/react/utils/renderFileChildren.js +1 -1
  62. package/dist/react/utils/renderFileChildren.js.map +1 -1
  63. package/dist/react/utils/useFileDiffInstance.d.ts +1 -0
  64. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  65. package/dist/react/utils/useFileDiffInstance.js +29 -24
  66. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  67. package/dist/react/utils/useFileInstance.d.ts +2 -0
  68. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  69. package/dist/react/utils/useFileInstance.js +38 -28
  70. package/dist/react/utils/useFileInstance.js.map +1 -1
  71. package/dist/react/utils/useForceUpdate.d.ts +5 -0
  72. package/dist/react/utils/useForceUpdate.d.ts.map +1 -0
  73. package/dist/react/utils/useForceUpdate.js +27 -0
  74. package/dist/react/utils/useForceUpdate.js.map +1 -0
  75. package/dist/react/utils/useNextRenderData.d.ts +9 -0
  76. package/dist/react/utils/useNextRenderData.d.ts.map +1 -0
  77. package/dist/react/utils/useNextRenderData.js +9 -0
  78. package/dist/react/utils/useNextRenderData.js.map +1 -0
  79. package/dist/react/utils/useUnresolvedFileInstance.d.ts +1 -0
  80. package/dist/react/utils/useUnresolvedFileInstance.d.ts.map +1 -1
  81. package/dist/react/utils/useUnresolvedFileInstance.js +26 -11
  82. package/dist/react/utils/useUnresolvedFileInstance.js.map +1 -1
  83. package/dist/renderers/DiffHunksRenderer.d.ts +2 -1
  84. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  85. package/dist/renderers/DiffHunksRenderer.js +62 -32
  86. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  87. package/dist/renderers/FileRenderer.d.ts +4 -1
  88. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  89. package/dist/renderers/FileRenderer.js +55 -30
  90. package/dist/renderers/FileRenderer.js.map +1 -1
  91. package/dist/renderers/UnresolvedFileHunksRenderer.d.ts.map +1 -1
  92. package/dist/renderers/UnresolvedFileHunksRenderer.js +1 -1
  93. package/dist/renderers/UnresolvedFileHunksRenderer.js.map +1 -1
  94. package/dist/ssr/FileDiffReact.js +1 -1
  95. package/dist/types.d.ts +10 -0
  96. package/dist/types.d.ts.map +1 -1
  97. package/dist/utils/canUseWASM.d.ts +13 -0
  98. package/dist/utils/canUseWASM.d.ts.map +1 -0
  99. package/dist/utils/canUseWASM.js +59 -0
  100. package/dist/utils/canUseWASM.js.map +1 -0
  101. package/dist/utils/getCodeViewItemRenderData.d.ts +10 -0
  102. package/dist/utils/getCodeViewItemRenderData.d.ts.map +1 -0
  103. package/dist/utils/getCodeViewItemRenderData.js +19 -0
  104. package/dist/utils/getCodeViewItemRenderData.js.map +1 -0
  105. package/dist/utils/hastToHtml.d.ts +2 -2
  106. package/dist/utils/hastToHtml.js +2 -2
  107. package/dist/utils/hastToHtml.js.map +1 -1
  108. package/dist/utils/hydratePartialDiff.js +4 -0
  109. package/dist/utils/hydratePartialDiff.js.map +1 -1
  110. package/dist/utils/parseMergeConflictDiffFromFile.d.ts.map +1 -1
  111. package/dist/utils/parseMergeConflictDiffFromFile.js +2 -0
  112. package/dist/utils/parseMergeConflictDiffFromFile.js.map +1 -1
  113. package/dist/utils/parsePatchFiles.d.ts.map +1 -1
  114. package/dist/utils/parsePatchFiles.js +24 -4
  115. package/dist/utils/parsePatchFiles.js.map +1 -1
  116. package/dist/utils/renderTokenLines.d.ts +6 -1
  117. package/dist/utils/renderTokenLines.d.ts.map +1 -1
  118. package/dist/utils/renderTokenLines.js +4 -0
  119. package/dist/utils/renderTokenLines.js.map +1 -1
  120. package/dist/utils/resolveRegion.js +20 -10
  121. package/dist/utils/resolveRegion.js.map +1 -1
  122. package/dist/utils/tokenStyle.d.ts +5 -1
  123. package/dist/utils/tokenStyle.d.ts.map +1 -1
  124. package/dist/utils/tokenStyle.js +5 -1
  125. package/dist/utils/tokenStyle.js.map +1 -1
  126. package/dist/utils/tokensToHtml.d.ts +5 -2
  127. package/dist/utils/tokensToHtml.d.ts.map +1 -1
  128. package/dist/utils/tokensToHtml.js +5 -2
  129. package/dist/utils/tokensToHtml.js.map +1 -1
  130. package/dist/utils/updateDiffHunks.d.ts +2 -2
  131. package/dist/utils/updateDiffHunks.d.ts.map +1 -1
  132. package/dist/utils/updateDiffHunks.js +7 -3
  133. package/dist/utils/updateDiffHunks.js.map +1 -1
  134. package/dist/worker/WorkerPoolManager.d.ts +12 -0
  135. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  136. package/dist/worker/WorkerPoolManager.js +99 -21
  137. package/dist/worker/WorkerPoolManager.js.map +1 -1
  138. package/dist/worker/{highlights-sAHlxqlf.js → highlights-CpZm9DAR.js} +2 -2
  139. package/dist/worker/{highlights-sAHlxqlf.js.map → highlights-CpZm9DAR.js.map} +1 -1
  140. package/dist/worker/{shiki-Bx2JpdqZ.js → shiki-Ci4cdHsM.js} +2 -2
  141. package/dist/worker/{shiki-Bx2JpdqZ.js.map → shiki-Ci4cdHsM.js.map} +1 -1
  142. package/dist/worker/{stream-tokenizer-YZ6M1qSx.js → stream-tokenizer-DK9xp5M5.js} +8 -5
  143. package/dist/worker/stream-tokenizer-DK9xp5M5.js.map +1 -0
  144. package/dist/worker/types.d.ts +11 -0
  145. package/dist/worker/types.d.ts.map +1 -1
  146. package/dist/worker/worker-portable.js +4 -4
  147. package/dist/worker/worker-portable.js.map +1 -1
  148. package/dist/worker/worker.js +12 -3
  149. package/dist/worker/worker.js.map +1 -1
  150. package/package.json +28 -30
  151. package/dist/worker/stream-tokenizer-YZ6M1qSx.js.map +0 -1
@@ -2,9 +2,9 @@
2
2
  import { DIFFS_TAG_NAME } from "../constants.js";
3
3
  import { getLineAnnotationName } from "../utils/getLineAnnotationName.js";
4
4
  import { useEffect, useRef } from "react";
5
+ import { jsx } from "react/jsx-runtime";
5
6
  import { hydrateRoot } from "react-dom/client";
6
7
  import { renderToString } from "react-dom/server";
7
- import { jsx } from "react/jsx-runtime";
8
8
  //#region src/ssr/FileDiffReact.tsx
9
9
  function escapeHtml(unsafe) {
10
10
  return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
package/dist/types.d.ts CHANGED
@@ -282,6 +282,16 @@ interface FileDiffMetadata {
282
282
  * the patch.
283
283
  */
284
284
  additionLines: string[];
285
+ /**
286
+ * Total character count of every entry in `deletionLines`. Producers and
287
+ * anything that replaces or mutates `deletionLines` must keep it in sync.
288
+ */
289
+ deletionCharacterCount: number;
290
+ /**
291
+ * Total character count of every entry in `additionLines`. Producers and
292
+ * anything that replaces or mutates `additionLines` must keep it in sync.
293
+ */
294
+ additionCharacterCount: number;
285
295
  /**
286
296
  * Set while an attached editor's session-scoped hunk updates have reshaped
287
297
  * `hunks` away from a plain recompute (regions stay frozen during an edit
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;KAmBY;;;;;UAMK;;;EAGf;;EAEA;;;EAGA,OAAO;;EAEP;;EAEA;;UAGe;EACf,SAAS;EACT,SAAS;;UAGM;EACf;EACA,SAAS;;KAGC,sBACR,6BACA;KAEQ;EACN,SAAS;EAAc,SAAS;;EAChC;EAAe,SAAS;;EACxB,SAAS;EAAc;;KAEjB,qBACR;EACE;EAAqB;;KAEf,0BACV,UAAU,qBACP,QAAQ;KAED;KAEA,oBAAoB,cAAc,YAAY;KAI9C,kBAAkB;KAElB,aAAa,yBAAyB;;;;;;;;;KAUtC;;;;;UAWK;;;EAGf;;EAEA,OAAO;;;;;;;UAQQ;EACf;;EAEA;;;;;EAKA;;;;;EAKA;;;;;;;UAQe;EACf;;EAEA;;;;;EAKA;;EAEA;;;;;EAKA;;;;;;UAOe;;;;;EAKf;;;;;EAMA;;;;;;;;EAQA;;EAEA;;;;;EAKA;;;;;EAMA;;;;;;;;EAQA;;EAEA;;;;;EAKA;;;;;EAMA,cAAc,iBAAiB;;;;;EAK/B;;EAEA;;;;;EAMA;;EAEA;;EAGA;;EAEA;;;;;;;;EASA;;;;;;;;EAQA;;;;;;;;;;UAWe;;EAEf;;EAEA;;;;;;;EAOA,OAAO;;;;;EAMP;;;;;EAKA;;;;;EAMA;;EAEA;;EAGA,MAAM;;;EAIN,OAAO;;EAEP;;EAEA;;;;;;;;;;;;;;EAeA;;;;;;;;EASA;;;;;;;;EAQA;;;;;EAKA;;;;;EAKA;;;;;;;;;;EAUA;;;;;;;;;;;;EAYA;;KAGU;UAMK;EACf,MAAM;EACN;;EAEA;EACA;EACA;;EAEA;;KAGU,qBACR;KAMQ;KAOA;KAEA;;;;KAKA;KAOA;KAEA;UAEK;EACf,QAAQ,kBAAkB;EAC1B;EACA;EACA,YAAY;EACZ;EACA;EACA;EACA;EAGA,uBAAuB;EACvB;EACA;EACA;EACA;EAGA;;UAGe,wBAAwB;EACvC;EACA,iBAAiB;EACjB;EACA,iBAAiB;EACjB;;;;;;;EAOA,gBAAgB;EAEhB;EAIA,eAAe;EACf;EAGA;;;;;;EAOA,mBAAmB;;KAGT,8BAA8B,SACxC,KACE;KAKQ,sBAAsB;UAKjB,4BAA4B,SAC3C,KACE;EAIF;EACA;EACA;EACA,mBAAmB;;KAGT;KAEA,gCACV,UAAU,qBACP;KAEO,8BACV,UAAU,qBACP;KAEO,sCACV,UAAU,qBACP;KAEO,sBACV,MAAM,iBACH;KAEO,qBAAqB,eAAe;KAEpC;KACA;UAEK;EACf;EACA,OAAO;EACP;EACA,UAAU;;KAGP,iBAAiB,eAAe;EAC/B;;EACA,UAAU;;;;;;KAMJ,eAAe;EACzB;IACE,iBAAiB;;;;;KAMT,mBAAmB;EAC7B,MAAM;EACN;IACE,iBAAiB;KAET,iBAAiB;EAC3B;EACA;EACA,MAAM;EACN,cAAc,eAAe;EAC7B;EACA;;;;;;EAMA;;KAGU,iBAAiB;EAC3B;EACA;EACA,UAAU;EACV,cAAc,mBAAmB;EACjC;EACA;;;;;;EAMA;;KAGU,aAAa,eACrB,iBAAiB,eACjB,iBAAiB;UAEJ;EACf;EACA;EACA,WAAW;;UAGI;EACf;EACA;EACA;EACA,OAAO;EACP;EACA;EACA,WAAW;;UAGI;EACf;EACA;EACA,OAAO;EACP;EACA;EACA,WAAW;;UAGI;EACf;EACA;EACA,OAAO;;UAGQ;EACf;EACA;EACA;EACA;EACA,WAAW;;KAGD,uBACR,+BACA,2BACA,4BACA;KAEQ;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf,YAAY;EACZ,UAAU;;UAGK;EACf;EACA;;KAGU,YAAY,UAAU;KAKtB;UAMK;EACf,MAAM;EACN;EACA;EACA;;UAGe;EACf,WAAW,4BAA4B,4BAA4B;;UAGpD;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA,aAAa;EACb,eAAe;EACf;;UAGe,+BAA+B,KAC9C;EAGA;EACA,gBAAgB;EAChB,UAAU;;UAGK;EACf;EACA;EACA;EACA;EACA;EACA,cAAc;;UAGC,gCAAgC;EAC/C,MAAM;;UAGS;EACf;EACA;IACE,WAAW;IACX,OAAO;IACP;;EAEF;IACE,WAAW;IACX,OAAO;IACP;;EAEF;;UAGe;EACf;EACA,aAAa;EACb,eAAe;EACf;EACA;EACA;;KAGU;UAEK;EACf;EACA;EACA;EACA;EACA,MAAM;EACN;IACE;IACA;IACA;;;KAIQ,kBAAkB,eAAe,eAE3C,mBAAmB;KAGT;UAEK;EACf,MAAM;EACN;EACA;;UAGe;EACf,eAAe;EACf,eAAe;;UAGA;EACf,MAAM;EACN;EACA;;UAGe;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA,gBAAgB,YAAY;EAC5B;;UAGe;EACf;EACA;EACA;EACA;EAEA;;UAGe;EACf,OAAO,kBAAkB,yBAAyB;EAClD;EACA;;UAGe;EACf,OAAO,kBAAkB,yBAAyB;EAClD;EACA;EACA,cAAc;EACd;;UAGe;EACf,QAAQ;EACR,SAAS;;UAGM;EACf,QAAQ;EACR,SAAS;;UAGM;EACf,MAAM;EACN;EACA,SAAS;EACT,QAAQ;EACR,aAAa;EACb;;UAGe;EACf,MAAM;EACN;EACA,SAAS;EACT,QAAQ;EACR,aAAa;EACb;;;;;;;;;;;;;UAce;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;;UAGe;;EAEf;;EAEA;;UAGe;;EAEf;;EAEA;;EAEA;;;EAGA;;;EAGA;;;;EAIA;;;EAGA;;UAGe;EACf,UAAU;EACV;EACA;EACA;;UAGe;;EAEf;;EAEA;;EAEA;;UAGe;;;;;;EAMf;;;;;;EAMA;;;;;;EAMA;;UAGe;EACf;EACA,MAAM;;KAGI;KAEA;UAEK;EACf,MAAM;EACN;;;;;;;;;UAUe;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;UAGe;EACf,OAAO,kBAAkB;EACzB;EACA,WAAW;EACX;EACA;;UAGe;EACf;EACA"}
@@ -0,0 +1,13 @@
1
+ //#region src/utils/canUseWASM.d.ts
2
+ /**
3
+ * Whether WebAssembly, including SIMD, can be compiled and instantiated
4
+ * synchronously. The `highlights` and `shiki-wasm` highlighters need
5
+ * WebAssembly, so use this to fall back to `shiki-js` in environments that lack
6
+ * it or block it (for example, a CSP without `wasm-unsafe-eval`). SIMD is
7
+ * required because `highlights` uses it; engines without it (such as Safari
8
+ * before 16.4) report false. The result is cached after the first call.
9
+ */
10
+ declare function canUseWASM(): boolean;
11
+ //#endregion
12
+ export { canUseWASM };
13
+ //# sourceMappingURL=canUseWASM.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canUseWASM.d.ts","names":[],"sources":["../../src/utils/canUseWASM.ts"],"mappings":";;;;;;;;;iBAUgB"}
@@ -0,0 +1,59 @@
1
+ //#region src/utils/canUseWASM.ts
2
+ let canUseWASMResult;
3
+ /**
4
+ * Whether WebAssembly, including SIMD, can be compiled and instantiated
5
+ * synchronously. The `highlights` and `shiki-wasm` highlighters need
6
+ * WebAssembly, so use this to fall back to `shiki-js` in environments that lack
7
+ * it or block it (for example, a CSP without `wasm-unsafe-eval`). SIMD is
8
+ * required because `highlights` uses it; engines without it (such as Safari
9
+ * before 16.4) report false. The result is cached after the first call.
10
+ */
11
+ function canUseWASM() {
12
+ return canUseWASMResult ??= probeWASM();
13
+ }
14
+ function probeWASM() {
15
+ try {
16
+ if (typeof WebAssembly !== "object") return false;
17
+ const wasmModule = new WebAssembly.Module(new Uint8Array([
18
+ 0,
19
+ 97,
20
+ 115,
21
+ 109,
22
+ 1,
23
+ 0,
24
+ 0,
25
+ 0,
26
+ 1,
27
+ 5,
28
+ 1,
29
+ 96,
30
+ 0,
31
+ 1,
32
+ 123,
33
+ 3,
34
+ 2,
35
+ 1,
36
+ 0,
37
+ 10,
38
+ 10,
39
+ 1,
40
+ 8,
41
+ 0,
42
+ 65,
43
+ 0,
44
+ 253,
45
+ 15,
46
+ 253,
47
+ 98,
48
+ 11
49
+ ]));
50
+ new WebAssembly.Instance(wasmModule);
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+ //#endregion
57
+ export { canUseWASM };
58
+
59
+ //# sourceMappingURL=canUseWASM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canUseWASM.js","names":[],"sources":["../../src/utils/canUseWASM.ts"],"sourcesContent":["let canUseWASMResult: boolean | undefined;\n\n/**\n * Whether WebAssembly, including SIMD, can be compiled and instantiated\n * synchronously. The `highlights` and `shiki-wasm` highlighters need\n * WebAssembly, so use this to fall back to `shiki-js` in environments that lack\n * it or block it (for example, a CSP without `wasm-unsafe-eval`). SIMD is\n * required because `highlights` uses it; engines without it (such as Safari\n * before 16.4) report false. The result is cached after the first call.\n */\nexport function canUseWASM(): boolean {\n return (canUseWASMResult ??= probeWASM());\n}\n\nfunction probeWASM(): boolean {\n try {\n if (typeof WebAssembly !== 'object') {\n return false;\n }\n // A module with one function, `(func (result v128) i32.const 0\n // i8x16.splat i8x16.popcnt)`. Compiling it fails without SIMD support, and\n // compiling or instantiating fails when a CSP blocks WebAssembly.\n const wasmModule = new WebAssembly.Module(\n new Uint8Array([\n 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60,\n 0x00, 0x01, 0x7b, 0x03, 0x02, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x08, 0x00,\n 0x41, 0x00, 0xfd, 0x0f, 0xfd, 0x62, 0x0b,\n ])\n );\n new WebAssembly.Instance(wasmModule);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";AAAA,IAAI;;;;;;;;;AAUJ,SAAgB,aAAsB;CACpC,OAAQ,qBAAqB,UAAU;AACzC;AAEA,SAAS,YAAqB;CAC5B,IAAI;EACF,IAAI,OAAO,gBAAgB,UACzB,OAAO;EAKT,MAAM,aAAa,IAAI,YAAY,OACjC,IAAI,WAAW;GACb;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAClE;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;GAClE;GAAM;GAAM;GAAM;GAAM;GAAM;GAAM;EACtC,CAAC,CACH;EACA,IAAI,YAAY,SAAS,UAAU;EACnC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF"}
@@ -0,0 +1,10 @@
1
+ import { FileRenderData } from "../components/File.js";
2
+ import { FileDiffRenderData } from "../components/FileDiff.js";
3
+ import { CodeViewRenderedDiffItem, CodeViewRenderedFileItem, CodeViewRenderedItem } from "../components/CodeView.js";
4
+ //#region src/utils/getCodeViewItemRenderData.d.ts
5
+ declare function getCodeViewItemRenderData<LAnnotation, Caret>(renderedItem: CodeViewRenderedDiffItem<LAnnotation, Caret>): FileDiffRenderData<LAnnotation>;
6
+ declare function getCodeViewItemRenderData<LAnnotation, Caret>(renderedItem: CodeViewRenderedFileItem<LAnnotation, Caret>): FileRenderData<LAnnotation>;
7
+ declare function getCodeViewItemRenderData<LAnnotation, Caret>(renderedItem: CodeViewRenderedItem<LAnnotation, Caret>): FileDiffRenderData<LAnnotation> | FileRenderData<LAnnotation>;
8
+ //#endregion
9
+ export { getCodeViewItemRenderData };
10
+ //# sourceMappingURL=getCodeViewItemRenderData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getCodeViewItemRenderData.d.ts","names":[],"sources":["../../src/utils/getCodeViewItemRenderData.ts"],"mappings":";;;;iBAWgB,0BAA0B,aAAa,OACrD,cAAc,yBAAyB,aAAa,SACnD,mBAAmB;iBACN,0BAA0B,aAAa,OACrD,cAAc,yBAAyB,aAAa,SACnD,eAAe;iBACF,0BAA0B,aAAa,OACrD,cAAc,qBAAqB,aAAa,SAC/C,mBAAmB,eAAe,eAAe"}
@@ -0,0 +1,19 @@
1
+ //#region src/utils/getCodeViewItemRenderData.ts
2
+ function getCodeViewItemRenderData(renderedItem) {
3
+ if (renderedItem.type === "diff") {
4
+ const { instance, item } = renderedItem;
5
+ return instance.__getNextRenderData({
6
+ fileDiff: instance.fileDiff ?? item.fileDiff,
7
+ lineAnnotations: void 0
8
+ });
9
+ }
10
+ const { instance, item } = renderedItem;
11
+ return instance.__getNextRenderData({
12
+ file: instance.file ?? item.file,
13
+ lineAnnotations: void 0
14
+ });
15
+ }
16
+ //#endregion
17
+ export { getCodeViewItemRenderData };
18
+
19
+ //# sourceMappingURL=getCodeViewItemRenderData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getCodeViewItemRenderData.js","names":[],"sources":["../../src/utils/getCodeViewItemRenderData.ts"],"sourcesContent":["import type {\n CodeViewRenderedDiffItem,\n CodeViewRenderedFileItem,\n CodeViewRenderedItem,\n} from '../components/CodeView';\nimport type { FileRenderData } from '../components/File';\nimport type { FileDiffRenderData } from '../components/FileDiff';\n\n// Return the data a rendered item's next render will display. CodeView applies\n// item changes to the instance before rendering it, so the instance's own\n// input describes that render.\nexport function getCodeViewItemRenderData<LAnnotation, Caret>(\n renderedItem: CodeViewRenderedDiffItem<LAnnotation, Caret>\n): FileDiffRenderData<LAnnotation>;\nexport function getCodeViewItemRenderData<LAnnotation, Caret>(\n renderedItem: CodeViewRenderedFileItem<LAnnotation, Caret>\n): FileRenderData<LAnnotation>;\nexport function getCodeViewItemRenderData<LAnnotation, Caret>(\n renderedItem: CodeViewRenderedItem<LAnnotation, Caret>\n): FileDiffRenderData<LAnnotation> | FileRenderData<LAnnotation>;\nexport function getCodeViewItemRenderData<LAnnotation, Caret>(\n renderedItem: CodeViewRenderedItem<LAnnotation, Caret>\n): FileDiffRenderData<LAnnotation> | FileRenderData<LAnnotation> {\n if (renderedItem.type === 'diff') {\n const { instance, item } = renderedItem;\n return instance.__getNextRenderData({\n fileDiff: instance.fileDiff ?? item.fileDiff,\n lineAnnotations: undefined,\n });\n }\n const { instance, item } = renderedItem;\n return instance.__getNextRenderData({\n file: instance.file ?? item.file,\n lineAnnotations: undefined,\n });\n}\n"],"mappings":";AAoBA,SAAgB,0BACd,cAC+D;CAC/D,IAAI,aAAa,SAAS,QAAQ;EAChC,MAAM,EAAE,UAAU,SAAS;EAC3B,OAAO,SAAS,oBAAoB;GAClC,UAAU,SAAS,YAAY,KAAK;GACpC,iBAAiB,KAAA;EACnB,CAAC;CACH;CACA,MAAM,EAAE,UAAU,SAAS;CAC3B,OAAO,SAAS,oBAAoB;EAClC,MAAM,SAAS,QAAQ,KAAK;EAC5B,iBAAiB,KAAA;CACnB,CAAC;AACH"}
@@ -1,8 +1,8 @@
1
1
  import { Nodes, RootContent } from "../node_modules/.pnpm/@types_hast@3.0.4/node_modules/@types/hast/index.js";
2
2
  //#region src/utils/hastToHtml.d.ts
3
3
  /**
4
- * Avoid schema lookups for common token spans; delegate other nodes and
5
- * attributes to toHtml(). Output must match its default serialization exactly.
4
+ * Serializes a HAST node or node array to HTML. Common token spans use a fast
5
+ * path; other nodes and attributes use hast-util-to-html's default serialization.
6
6
  */
7
7
  declare function hastToHtml(tree: Nodes | RootContent[]): string;
8
8
  //#endregion
@@ -51,8 +51,8 @@ const CHARACTER_REFERENCES = {
51
51
  };
52
52
  const MAX_CACHED_STYLES = 256;
53
53
  /**
54
- * Avoid schema lookups for common token spans; delegate other nodes and
55
- * attributes to toHtml(). Output must match its default serialization exactly.
54
+ * Serializes a HAST node or node array to HTML. Common token spans use a fast
55
+ * path; other nodes and attributes use hast-util-to-html's default serialization.
56
56
  */
57
57
  function hastToHtml(tree) {
58
58
  const styles = /* @__PURE__ */ new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"hastToHtml.js","names":[],"sources":["../../src/utils/hastToHtml.ts"],"sourcesContent":["import type { Element, Nodes, Properties, RootContent } from 'hast';\nimport { toHtml } from 'hast-util-to-html';\n\n// Must match the html-void-elements list used by toHtml().\nconst VOID_ELEMENTS = new Set([\n 'area',\n 'base',\n 'basefont',\n 'bgsound',\n 'br',\n 'col',\n 'command',\n 'embed',\n 'frame',\n 'hr',\n 'image',\n 'img',\n 'input',\n 'keygen',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n]);\n\n// These elements require schema, fragment or raw-text handling in toHtml().\nconst DELEGATED_TAGS = new Set(['svg', 'template', 'script', 'style']);\n\n// Boolean and comma-separated attributes require the full HTML schema.\nconst ATTRIBUTE_NAMES: Record<string, string | undefined> = {\n class: 'class',\n className: 'class',\n id: 'id',\n role: 'role',\n style: 'style',\n tabIndex: 'tabindex',\n tabindex: 'tabindex',\n title: 'title',\n};\nconst DATA_ATTRIBUTE = /^data-[\\w.:-]+$/;\n\nconst TEXT_ESCAPE = /[<&]/g;\nconst ATTRIBUTE_ESCAPE = /[\\0\"&'`]/g;\n// Must match stringify-entities' default hexadecimal encoding.\nconst CHARACTER_REFERENCES: Record<string, string> = {\n '<': '&#x3C;',\n '&': '&#x26;',\n '\"': '&#x22;',\n \"'\": '&#x27;',\n '`': '&#x60;',\n '\\0': '&#x0;',\n};\nconst MAX_CACHED_STYLES = 256;\n\n/**\n * Avoid schema lookups for common token spans; delegate other nodes and\n * attributes to toHtml(). Output must match its default serialization exactly.\n */\nexport function hastToHtml(tree: Nodes | RootContent[]): string {\n const styles = new Map<string, string>();\n return Array.isArray(tree)\n ? serializeChildren(tree, styles)\n : serializeNode(tree, styles);\n}\n\nfunction serializeChildren(\n children: readonly RootContent[],\n styles: Map<string, string>\n): string {\n let html = '';\n for (const child of children) {\n html += serializeNode(child, styles);\n }\n return html;\n}\n\nfunction serializeNode(node: Nodes, styles: Map<string, string>): string {\n switch (node.type) {\n case 'text':\n return escapeText(node.value);\n case 'element':\n return serializeElement(node, styles);\n case 'root':\n return serializeChildren(node.children, styles);\n default:\n return toHtml(node);\n }\n}\n\nfunction serializeElement(node: Element, styles: Map<string, string>): string {\n const { tagName } = node;\n if (\n tagName === 'span' &&\n node.children.length === 1 &&\n node.children[0].type === 'text' &&\n typeof node.properties?.style === 'string'\n ) {\n let styleOnly = true;\n for (const key in node.properties) {\n if (key !== 'style' && node.properties[key] != null) {\n styleOnly = false;\n break;\n }\n }\n if (styleOnly) {\n return `<span ${serializeStyle(node.properties.style, styles)}>${escapeText(node.children[0].value)}</span>`;\n }\n }\n if (DELEGATED_TAGS.has(tagName)) {\n return toHtml(node);\n }\n const attributes = serializeAttributes(node.properties, styles);\n if (attributes == null) {\n return toHtml(node);\n }\n const open =\n attributes === '' ? `<${tagName}>` : `<${tagName} ${attributes}>`;\n const content = serializeChildren(node.children, styles);\n if (content === '' && VOID_ELEMENTS.has(tagName.toLowerCase())) {\n return open;\n }\n return `${open}${content}</${tagName}>`;\n}\n\n// Cap the per-render cache because custom styles may be unique per token.\nfunction serializeStyle(value: string, styles: Map<string, string>): string {\n let attribute = styles.get(value);\n if (attribute == null) {\n attribute = `style=\"${escapeAttribute(value)}\"`;\n if (styles.size < MAX_CACHED_STYLES) styles.set(value, attribute);\n }\n return attribute;\n}\n\n// Returns undefined when a property needs the HTML schema to serialize.\nfunction serializeAttributes(\n properties: Properties | undefined,\n styles: Map<string, string>\n): string | undefined {\n let attributes = '';\n for (const key in properties) {\n const value = properties[key];\n if (value == null) {\n continue;\n }\n const name =\n ATTRIBUTE_NAMES[key] ?? (DATA_ATTRIBUTE.test(key) ? key : undefined);\n if (name == null) {\n return undefined;\n }\n if (value === false || (typeof value === 'number' && Number.isNaN(value))) {\n continue;\n }\n const attribute =\n key === 'style' && typeof value === 'string'\n ? serializeStyle(value, styles)\n : value === true\n ? name\n : `${name}=\"${escapeAttribute(\n Array.isArray(value) ? value.join(' ').trim() : String(value)\n )}\"`;\n attributes += attributes === '' ? attribute : ` ${attribute}`;\n }\n return attributes;\n}\n\nfunction escapeText(value: string): string {\n return value.indexOf('<') < 0 && value.indexOf('&') < 0\n ? value\n : value.replace(TEXT_ESCAPE, toCharacterReference);\n}\n\nfunction escapeAttribute(value: string): string {\n return value.replace(ATTRIBUTE_ESCAPE, toCharacterReference);\n}\n\nfunction toCharacterReference(character: string): string {\n return CHARACTER_REFERENCES[character];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hastToHtml.js","names":[],"sources":["../../src/utils/hastToHtml.ts"],"sourcesContent":["import type { Element, Nodes, Properties, RootContent } from 'hast';\nimport { toHtml } from 'hast-util-to-html';\n\n// Must match the html-void-elements list used by toHtml().\nconst VOID_ELEMENTS = new Set([\n 'area',\n 'base',\n 'basefont',\n 'bgsound',\n 'br',\n 'col',\n 'command',\n 'embed',\n 'frame',\n 'hr',\n 'image',\n 'img',\n 'input',\n 'keygen',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n]);\n\n// These elements require schema, fragment or raw-text handling in toHtml().\nconst DELEGATED_TAGS = new Set(['svg', 'template', 'script', 'style']);\n\n// Boolean and comma-separated attributes require the full HTML schema.\nconst ATTRIBUTE_NAMES: Record<string, string | undefined> = {\n class: 'class',\n className: 'class',\n id: 'id',\n role: 'role',\n style: 'style',\n tabIndex: 'tabindex',\n tabindex: 'tabindex',\n title: 'title',\n};\nconst DATA_ATTRIBUTE = /^data-[\\w.:-]+$/;\n\nconst TEXT_ESCAPE = /[<&]/g;\nconst ATTRIBUTE_ESCAPE = /[\\0\"&'`]/g;\n// Must match stringify-entities' default hexadecimal encoding.\nconst CHARACTER_REFERENCES: Record<string, string> = {\n '<': '&#x3C;',\n '&': '&#x26;',\n '\"': '&#x22;',\n \"'\": '&#x27;',\n '`': '&#x60;',\n '\\0': '&#x0;',\n};\nconst MAX_CACHED_STYLES = 256;\n\n/**\n * Serializes a HAST node or node array to HTML. Common token spans use a fast\n * path; other nodes and attributes use hast-util-to-html's default serialization.\n */\nexport function hastToHtml(tree: Nodes | RootContent[]): string {\n const styles = new Map<string, string>();\n return Array.isArray(tree)\n ? serializeChildren(tree, styles)\n : serializeNode(tree, styles);\n}\n\nfunction serializeChildren(\n children: readonly RootContent[],\n styles: Map<string, string>\n): string {\n let html = '';\n for (const child of children) {\n html += serializeNode(child, styles);\n }\n return html;\n}\n\nfunction serializeNode(node: Nodes, styles: Map<string, string>): string {\n switch (node.type) {\n case 'text':\n return escapeText(node.value);\n case 'element':\n return serializeElement(node, styles);\n case 'root':\n return serializeChildren(node.children, styles);\n default:\n return toHtml(node);\n }\n}\n\nfunction serializeElement(node: Element, styles: Map<string, string>): string {\n const { tagName } = node;\n if (\n tagName === 'span' &&\n node.children.length === 1 &&\n node.children[0].type === 'text' &&\n typeof node.properties?.style === 'string'\n ) {\n let styleOnly = true;\n for (const key in node.properties) {\n if (key !== 'style' && node.properties[key] != null) {\n styleOnly = false;\n break;\n }\n }\n if (styleOnly) {\n return `<span ${serializeStyle(node.properties.style, styles)}>${escapeText(node.children[0].value)}</span>`;\n }\n }\n if (DELEGATED_TAGS.has(tagName)) {\n return toHtml(node);\n }\n const attributes = serializeAttributes(node.properties, styles);\n if (attributes == null) {\n return toHtml(node);\n }\n const open =\n attributes === '' ? `<${tagName}>` : `<${tagName} ${attributes}>`;\n const content = serializeChildren(node.children, styles);\n if (content === '' && VOID_ELEMENTS.has(tagName.toLowerCase())) {\n return open;\n }\n return `${open}${content}</${tagName}>`;\n}\n\n// Cap the per-render cache because custom styles may be unique per token.\nfunction serializeStyle(value: string, styles: Map<string, string>): string {\n let attribute = styles.get(value);\n if (attribute == null) {\n attribute = `style=\"${escapeAttribute(value)}\"`;\n if (styles.size < MAX_CACHED_STYLES) styles.set(value, attribute);\n }\n return attribute;\n}\n\n// Returns undefined when a property needs the HTML schema to serialize.\nfunction serializeAttributes(\n properties: Properties | undefined,\n styles: Map<string, string>\n): string | undefined {\n let attributes = '';\n for (const key in properties) {\n const value = properties[key];\n if (value == null) {\n continue;\n }\n const name =\n ATTRIBUTE_NAMES[key] ?? (DATA_ATTRIBUTE.test(key) ? key : undefined);\n if (name == null) {\n return undefined;\n }\n if (value === false || (typeof value === 'number' && Number.isNaN(value))) {\n continue;\n }\n const attribute =\n key === 'style' && typeof value === 'string'\n ? serializeStyle(value, styles)\n : value === true\n ? name\n : `${name}=\"${escapeAttribute(\n Array.isArray(value) ? value.join(' ').trim() : String(value)\n )}\"`;\n attributes += attributes === '' ? attribute : ` ${attribute}`;\n }\n return attributes;\n}\n\nfunction escapeText(value: string): string {\n return value.indexOf('<') < 0 && value.indexOf('&') < 0\n ? value\n : value.replace(TEXT_ESCAPE, toCharacterReference);\n}\n\nfunction escapeAttribute(value: string): string {\n return value.replace(ATTRIBUTE_ESCAPE, toCharacterReference);\n}\n\nfunction toCharacterReference(character: string): string {\n return CHARACTER_REFERENCES[character];\n}\n"],"mappings":"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"}
@@ -19,6 +19,8 @@ function hydratePartialDiff(type, fileDiff, files) {
19
19
  targetFileDiff.isPartial = false;
20
20
  targetFileDiff.deletionLines = lines;
21
21
  targetFileDiff.additionLines = lines;
22
+ targetFileDiff.deletionCharacterCount = newFile.contents.length;
23
+ targetFileDiff.additionCharacterCount = newFile.contents.length;
22
24
  setHydratedCacheKey(targetFileDiff, null, newFile);
23
25
  return targetFileDiff;
24
26
  }
@@ -35,6 +37,8 @@ function hydrateTwoSidedFileDiff(fileDiff, oldFile, newFile) {
35
37
  fileDiff.isPartial = false;
36
38
  fileDiff.deletionLines = deletionLines;
37
39
  fileDiff.additionLines = additionLines;
40
+ fileDiff.deletionCharacterCount = oldFile.contents.length;
41
+ fileDiff.additionCharacterCount = newFile.contents.length;
38
42
  setHydratedCacheKey(fileDiff, oldFile, newFile);
39
43
  return fileDiff;
40
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"hydratePartialDiff.js","names":[],"sources":["../../src/utils/hydratePartialDiff.ts"],"sourcesContent":["import type {\n FileContents,\n FileDiffLoadedFiles,\n FileDiffMetadata,\n Hunk,\n} from '../types';\nimport { cloneFileDiffMetadata } from './cloneFileDiffMetadata';\nimport { composeCacheKey } from './composeCacheKey';\nimport {\n getHunkSideEndBoundary,\n getHunkSideStartBoundary,\n} from './getHunkSideBoundaries';\nimport { splitFileContents } from './splitFileContents';\n\ninterface HydratedHunksResult {\n hunks: Hunk[];\n splitLineCount: number;\n unifiedLineCount: number;\n}\n\n/**\n * Hydrates a partial diff in place with full file line arrays.\n */\nexport function hydratePartialDiff(\n type: 'clone' | 'merge',\n fileDiff: FileDiffMetadata,\n files: FileDiffLoadedFiles\n): FileDiffMetadata {\n const targetFileDiff =\n type === 'clone' ? cloneFileDiffMetadata(fileDiff) : fileDiff;\n\n if (!targetFileDiff.isPartial) {\n throw new Error('hydratePartialDiff: fileDiff must be partial');\n }\n\n switch (targetFileDiff.type) {\n case 'change':\n case 'rename-changed': {\n const oldFile = requireOldFile(targetFileDiff, files);\n const newFile = requireNewFile(targetFileDiff, files);\n return hydrateTwoSidedFileDiff(targetFileDiff, oldFile, newFile);\n }\n case 'rename-pure': {\n const newFile = requireNewFile(targetFileDiff, files);\n requireMissingOldFile(targetFileDiff, files);\n const lines = splitFileContents(newFile.contents);\n targetFileDiff.isPartial = false;\n targetFileDiff.deletionLines = lines;\n targetFileDiff.additionLines = lines;\n setHydratedCacheKey(targetFileDiff, null, newFile);\n return targetFileDiff;\n }\n }\n throw new Error(\n `hydratePartialDiff: ${targetFileDiff.type} diffs cannot be hydrated from loaded files`\n );\n}\n\nfunction hydrateTwoSidedFileDiff(\n fileDiff: FileDiffMetadata,\n oldFile: FileContents,\n newFile: FileContents\n): FileDiffMetadata {\n const deletionLines = splitFileContents(oldFile.contents);\n const additionLines = splitFileContents(newFile.contents);\n const { hunks, splitLineCount, unifiedLineCount } = hydrateHunks(\n fileDiff.hunks,\n additionLines.length\n );\n\n fileDiff.hunks = hunks;\n fileDiff.splitLineCount = splitLineCount;\n fileDiff.unifiedLineCount = unifiedLineCount;\n fileDiff.isPartial = false;\n fileDiff.deletionLines = deletionLines;\n fileDiff.additionLines = additionLines;\n setHydratedCacheKey(fileDiff, oldFile, newFile);\n return fileDiff;\n}\n\nfunction hydrateHunks(\n hunks: Hunk[],\n totalAdditionLines: number\n): HydratedHunksResult {\n let splitLineCount = 0;\n let unifiedLineCount = 0;\n let lastHunkAdditionEnd = 0;\n\n const hydratedHunks: Hunk[] = [];\n\n for (const hunk of hunks) {\n const additionLineIndex = Math.max(hunk.additionStart - 1, 0);\n const deletionLineIndex = Math.max(hunk.deletionStart - 1, 0);\n let contentAdditionLineIndex = additionLineIndex;\n let contentDeletionLineIndex = deletionLineIndex;\n let hunkAdditionLines = 0;\n let hunkDeletionLines = 0;\n let hunkSplitLineCount = 0;\n let hunkUnifiedLineCount = 0;\n const hunkContent: Hunk['hunkContent'] = [];\n\n for (const content of hunk.hunkContent) {\n if (content.type === 'context') {\n hunkContent.push({\n ...content,\n additionLineIndex: contentAdditionLineIndex,\n deletionLineIndex: contentDeletionLineIndex,\n });\n contentAdditionLineIndex += content.lines;\n contentDeletionLineIndex += content.lines;\n hunkSplitLineCount += content.lines;\n hunkUnifiedLineCount += content.lines;\n continue;\n }\n\n hunkContent.push({\n ...content,\n additionLineIndex: contentAdditionLineIndex,\n deletionLineIndex: contentDeletionLineIndex,\n });\n contentAdditionLineIndex += content.additions;\n contentDeletionLineIndex += content.deletions;\n hunkAdditionLines += content.additions;\n hunkDeletionLines += content.deletions;\n hunkSplitLineCount += Math.max(content.additions, content.deletions);\n hunkUnifiedLineCount += content.additions + content.deletions;\n }\n\n const collapsedBefore = Math.max(\n getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount) -\n lastHunkAdditionEnd,\n 0\n );\n hydratedHunks.push({\n ...hunk,\n collapsedBefore,\n additionLineIndex,\n deletionLineIndex,\n additionLines: hunkAdditionLines,\n deletionLines: hunkDeletionLines,\n hunkContent,\n splitLineStart: splitLineCount + collapsedBefore,\n unifiedLineStart: unifiedLineCount + collapsedBefore,\n splitLineCount: hunkSplitLineCount,\n unifiedLineCount: hunkUnifiedLineCount,\n });\n\n splitLineCount += collapsedBefore + hunkSplitLineCount;\n unifiedLineCount += collapsedBefore + hunkUnifiedLineCount;\n lastHunkAdditionEnd = getHunkSideEndBoundary(\n hunk.additionStart,\n hunk.additionCount\n );\n }\n\n if (hydratedHunks.length > 0) {\n const lastHunk = hydratedHunks[hydratedHunks.length - 1];\n const lastHunkEnd = getHunkSideEndBoundary(\n lastHunk.additionStart,\n lastHunk.additionCount\n );\n const collapsedAfter = Math.max(totalAdditionLines - lastHunkEnd, 0);\n splitLineCount += collapsedAfter;\n unifiedLineCount += collapsedAfter;\n }\n\n return { hunks: hydratedHunks, splitLineCount, unifiedLineCount };\n}\n\nfunction requireOldFile(\n fileDiff: FileDiffMetadata,\n files: FileDiffLoadedFiles\n): FileContents {\n if (files.oldFile == null) {\n throw new Error(\n `hydratePartialDiff: ${fileDiff.type} diff for ${fileDiff.name} requires oldFile`\n );\n }\n return files.oldFile;\n}\n\nfunction requireNewFile(\n fileDiff: FileDiffMetadata,\n files: FileDiffLoadedFiles\n): FileContents {\n if (files.newFile == null) {\n throw new Error(\n `hydratePartialDiff: ${fileDiff.type} diff for ${fileDiff.name} requires newFile`\n );\n }\n return files.newFile;\n}\n\nfunction requireMissingOldFile(\n fileDiff: FileDiffMetadata,\n files: FileDiffLoadedFiles\n): void {\n if (files.oldFile !== null) {\n throw new Error(\n `hydratePartialDiff: ${fileDiff.type} diff for ${fileDiff.name} requires oldFile to be null`\n );\n }\n}\n\nfunction getHydratedCacheKey(\n fileDiff: FileDiffMetadata,\n oldFile: FileContents | null,\n newFile: FileContents | null\n): string | undefined {\n if (fileDiff.cacheKey != null) {\n return `${fileDiff.cacheKey}:hydrated`;\n }\n return getLoadedFileCacheKey(oldFile, newFile);\n}\n\nfunction setHydratedCacheKey(\n fileDiff: FileDiffMetadata,\n oldFile: FileContents | null,\n newFile: FileContents | null\n): void {\n const cacheKey = getHydratedCacheKey(fileDiff, oldFile, newFile);\n if (cacheKey == null) {\n delete fileDiff.cacheKey;\n return;\n }\n fileDiff.cacheKey = cacheKey;\n}\n\nfunction getLoadedFileCacheKey(\n oldFile: FileContents | null,\n newFile: FileContents | null\n): string | undefined {\n if (oldFile != null && newFile != null) {\n return oldFile.cacheKey != null && newFile.cacheKey != null\n ? composeCacheKey('hydrated-files', oldFile.cacheKey, newFile.cacheKey)\n : undefined;\n }\n return oldFile?.cacheKey ?? newFile?.cacheKey;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hydratePartialDiff.js","names":[],"sources":["../../src/utils/hydratePartialDiff.ts"],"sourcesContent":["import type {\n FileContents,\n FileDiffLoadedFiles,\n FileDiffMetadata,\n Hunk,\n} from '../types';\nimport { cloneFileDiffMetadata } from './cloneFileDiffMetadata';\nimport { composeCacheKey } from './composeCacheKey';\nimport {\n getHunkSideEndBoundary,\n getHunkSideStartBoundary,\n} from './getHunkSideBoundaries';\nimport { splitFileContents } from './splitFileContents';\n\ninterface HydratedHunksResult {\n hunks: Hunk[];\n splitLineCount: number;\n unifiedLineCount: number;\n}\n\n/**\n * Hydrates a partial diff in place with full file line arrays.\n */\nexport function hydratePartialDiff(\n type: 'clone' | 'merge',\n fileDiff: FileDiffMetadata,\n files: FileDiffLoadedFiles\n): FileDiffMetadata {\n const targetFileDiff =\n type === 'clone' ? cloneFileDiffMetadata(fileDiff) : fileDiff;\n\n if (!targetFileDiff.isPartial) {\n throw new Error('hydratePartialDiff: fileDiff must be partial');\n }\n\n switch (targetFileDiff.type) {\n case 'change':\n case 'rename-changed': {\n const oldFile = requireOldFile(targetFileDiff, files);\n const newFile = requireNewFile(targetFileDiff, files);\n return hydrateTwoSidedFileDiff(targetFileDiff, oldFile, newFile);\n }\n case 'rename-pure': {\n const newFile = requireNewFile(targetFileDiff, files);\n requireMissingOldFile(targetFileDiff, files);\n const lines = splitFileContents(newFile.contents);\n targetFileDiff.isPartial = false;\n targetFileDiff.deletionLines = lines;\n targetFileDiff.additionLines = lines;\n targetFileDiff.deletionCharacterCount = newFile.contents.length;\n targetFileDiff.additionCharacterCount = newFile.contents.length;\n setHydratedCacheKey(targetFileDiff, null, newFile);\n return targetFileDiff;\n }\n }\n throw new Error(\n `hydratePartialDiff: ${targetFileDiff.type} diffs cannot be hydrated from loaded files`\n );\n}\n\nfunction hydrateTwoSidedFileDiff(\n fileDiff: FileDiffMetadata,\n oldFile: FileContents,\n newFile: FileContents\n): FileDiffMetadata {\n const deletionLines = splitFileContents(oldFile.contents);\n const additionLines = splitFileContents(newFile.contents);\n const { hunks, splitLineCount, unifiedLineCount } = hydrateHunks(\n fileDiff.hunks,\n additionLines.length\n );\n\n fileDiff.hunks = hunks;\n fileDiff.splitLineCount = splitLineCount;\n fileDiff.unifiedLineCount = unifiedLineCount;\n fileDiff.isPartial = false;\n fileDiff.deletionLines = deletionLines;\n fileDiff.additionLines = additionLines;\n fileDiff.deletionCharacterCount = oldFile.contents.length;\n fileDiff.additionCharacterCount = newFile.contents.length;\n setHydratedCacheKey(fileDiff, oldFile, newFile);\n return fileDiff;\n}\n\nfunction hydrateHunks(\n hunks: Hunk[],\n totalAdditionLines: number\n): HydratedHunksResult {\n let splitLineCount = 0;\n let unifiedLineCount = 0;\n let lastHunkAdditionEnd = 0;\n\n const hydratedHunks: Hunk[] = [];\n\n for (const hunk of hunks) {\n const additionLineIndex = Math.max(hunk.additionStart - 1, 0);\n const deletionLineIndex = Math.max(hunk.deletionStart - 1, 0);\n let contentAdditionLineIndex = additionLineIndex;\n let contentDeletionLineIndex = deletionLineIndex;\n let hunkAdditionLines = 0;\n let hunkDeletionLines = 0;\n let hunkSplitLineCount = 0;\n let hunkUnifiedLineCount = 0;\n const hunkContent: Hunk['hunkContent'] = [];\n\n for (const content of hunk.hunkContent) {\n if (content.type === 'context') {\n hunkContent.push({\n ...content,\n additionLineIndex: contentAdditionLineIndex,\n deletionLineIndex: contentDeletionLineIndex,\n });\n contentAdditionLineIndex += content.lines;\n contentDeletionLineIndex += content.lines;\n hunkSplitLineCount += content.lines;\n hunkUnifiedLineCount += content.lines;\n continue;\n }\n\n hunkContent.push({\n ...content,\n additionLineIndex: contentAdditionLineIndex,\n deletionLineIndex: contentDeletionLineIndex,\n });\n contentAdditionLineIndex += content.additions;\n contentDeletionLineIndex += content.deletions;\n hunkAdditionLines += content.additions;\n hunkDeletionLines += content.deletions;\n hunkSplitLineCount += Math.max(content.additions, content.deletions);\n hunkUnifiedLineCount += content.additions + content.deletions;\n }\n\n const collapsedBefore = Math.max(\n getHunkSideStartBoundary(hunk.additionStart, hunk.additionCount) -\n lastHunkAdditionEnd,\n 0\n );\n hydratedHunks.push({\n ...hunk,\n collapsedBefore,\n additionLineIndex,\n deletionLineIndex,\n additionLines: hunkAdditionLines,\n deletionLines: hunkDeletionLines,\n hunkContent,\n splitLineStart: splitLineCount + collapsedBefore,\n unifiedLineStart: unifiedLineCount + collapsedBefore,\n splitLineCount: hunkSplitLineCount,\n unifiedLineCount: hunkUnifiedLineCount,\n });\n\n splitLineCount += collapsedBefore + hunkSplitLineCount;\n unifiedLineCount += collapsedBefore + hunkUnifiedLineCount;\n lastHunkAdditionEnd = getHunkSideEndBoundary(\n hunk.additionStart,\n hunk.additionCount\n );\n }\n\n if (hydratedHunks.length > 0) {\n const lastHunk = hydratedHunks[hydratedHunks.length - 1];\n const lastHunkEnd = getHunkSideEndBoundary(\n lastHunk.additionStart,\n lastHunk.additionCount\n );\n const collapsedAfter = Math.max(totalAdditionLines - lastHunkEnd, 0);\n splitLineCount += collapsedAfter;\n unifiedLineCount += collapsedAfter;\n }\n\n return { hunks: hydratedHunks, splitLineCount, unifiedLineCount };\n}\n\nfunction requireOldFile(\n fileDiff: FileDiffMetadata,\n files: FileDiffLoadedFiles\n): FileContents {\n if (files.oldFile == null) {\n throw new Error(\n `hydratePartialDiff: ${fileDiff.type} diff for ${fileDiff.name} requires oldFile`\n );\n }\n return files.oldFile;\n}\n\nfunction requireNewFile(\n fileDiff: FileDiffMetadata,\n files: FileDiffLoadedFiles\n): FileContents {\n if (files.newFile == null) {\n throw new Error(\n `hydratePartialDiff: ${fileDiff.type} diff for ${fileDiff.name} requires newFile`\n );\n }\n return files.newFile;\n}\n\nfunction requireMissingOldFile(\n fileDiff: FileDiffMetadata,\n files: FileDiffLoadedFiles\n): void {\n if (files.oldFile !== null) {\n throw new Error(\n `hydratePartialDiff: ${fileDiff.type} diff for ${fileDiff.name} requires oldFile to be null`\n );\n }\n}\n\nfunction getHydratedCacheKey(\n fileDiff: FileDiffMetadata,\n oldFile: FileContents | null,\n newFile: FileContents | null\n): string | undefined {\n if (fileDiff.cacheKey != null) {\n return `${fileDiff.cacheKey}:hydrated`;\n }\n return getLoadedFileCacheKey(oldFile, newFile);\n}\n\nfunction setHydratedCacheKey(\n fileDiff: FileDiffMetadata,\n oldFile: FileContents | null,\n newFile: FileContents | null\n): void {\n const cacheKey = getHydratedCacheKey(fileDiff, oldFile, newFile);\n if (cacheKey == null) {\n delete fileDiff.cacheKey;\n return;\n }\n fileDiff.cacheKey = cacheKey;\n}\n\nfunction getLoadedFileCacheKey(\n oldFile: FileContents | null,\n newFile: FileContents | null\n): string | undefined {\n if (oldFile != null && newFile != null) {\n return oldFile.cacheKey != null && newFile.cacheKey != null\n ? composeCacheKey('hydrated-files', oldFile.cacheKey, newFile.cacheKey)\n : undefined;\n }\n return oldFile?.cacheKey ?? newFile?.cacheKey;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseMergeConflictDiffFromFile.d.ts","names":[],"sources":["../../src/utils/parseMergeConflictDiffFromFile.ts"],"mappings":";;UAsDiB;EACf,UAAU;EACV,aAAa;EACb,cAAc;EACd,UAAU;EACV,YAAY;;UAGG,gCAAgC;EAG/C,UAAU;EACV;EACA;IACE;IACA;IACA;IACA;;;UAIM;EACR;EACA;;iBAqGc,6BACd,QAAQ,yBACR,UAAU,mBACT;iBAoBa,+BACd,MAAM,cACN,2BACC;iBAg0Ba,6BACd,UAAU,kBACV,UAAU,yCACT"}
1
+ {"version":3,"file":"parseMergeConflictDiffFromFile.d.ts","names":[],"sources":["../../src/utils/parseMergeConflictDiffFromFile.ts"],"mappings":";;UAsDiB;EACf,UAAU;EACV,aAAa;EACb,cAAc;EACd,UAAU;EACV,YAAY;;UAGG,gCAAgC;EAG/C,UAAU;EACV;EACA;IACE;IACA;IACA;IACA;;;UAIM;EACR;EACA;;iBAqGc,6BACd,QAAQ,yBACR,UAAU,mBACT;iBAoBa,+BACd,MAAM,cACN,2BACC;iBAk0Ba,6BACd,UAAU,kBACV,UAAU,yCACT"}
@@ -71,6 +71,8 @@ function parseMergeConflictDiffFromFile(file, maxContextLines = 6) {
71
71
  isPartial: false,
72
72
  deletionLines: s.deletionLines,
73
73
  additionLines: s.additionLines,
74
+ deletionCharacterCount: currentContents.length,
75
+ additionCharacterCount: incomingContents.length,
74
76
  cacheKey: file.cacheKey != null ? `${file.cacheKey}:merge-conflict-diff` : void 0
75
77
  };
76
78
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"parseMergeConflictDiffFromFile.js","names":[],"sources":["../../src/utils/parseMergeConflictDiffFromFile.ts"],"sourcesContent":["// Parses a file containing git merge conflict markers (<<<<<<< / ======= / >>>>>>>)\n// into a synthetic unified diff. The core idea: treat the conflict file as though\n// \"current\" lines are deletions and \"incoming\" lines are additions. Lines outside\n// conflicts (and optional \"base\" sections from diff3) become shared context.\n//\n// The result is a standard FileDiffMetadata with hunks — identical in shape to what\n// you'd get from parsing a real unified diff — plus a parallel array of\n// MergeConflictDiffActions that anchor each conflict region to positions within the\n// hunk structure. Downstream consumers (e.g. the merge conflict UI) use these\n// anchors to overlay conflict markers onto the diff view.\n//\n// Architecture note: all helper functions are module-level (not closures inside the\n// main function) and receive a shared ParseState object by reference. This avoids\n// per-call scope-chain traversal on the hot path (~20K lines), where every line\n// triggers 2-3 helper calls.\n//\n// ---\n// NOTE: This file was nearly entirely written and optimized by AI. It has a\n// verification harness that any future changes (human or AI) should be validated\n// against:\n//\n// Snapshot tests (from packages/diffs/):\n// bun test parseMergeConflictDiffFromFile\n//\n// Performance benchmark (checksum must match 31314920):\n// moonx diffs:benchmark-parse-merge-conflict\n//\n// If you encounter a bug:\n// 1. Add a new test case in test/parseMergeConflictDiffFromFile.test.ts with\n// input that reproduces the failure. Use toMatchSnapshot() so the expected\n// output is captured automatically once fixed.\n// 2. Run `bun test parseMergeConflictDiffFromFile` from packages/diffs/ to\n// confirm the new test fails.\n// 3. Use an AI agent with extended/high thinking to fix the logic — the\n// snapshot tests and benchmark provide a tight feedback loop. The agent\n// should iterate until all snapshots pass AND the benchmark checksum\n// matches. Update snapshots with `bun test test/parseMergeConflictDiffFromFile.test.ts -u`\n// only after verifying the new output is correct.\n// ---\n\nimport type {\n FileContents,\n FileDiffMetadata,\n Hunk,\n MergeConflictMarkerRow,\n MergeConflictMarkerRowType,\n MergeConflictRegion,\n ProcessFileConflictData,\n} from '../types';\nimport {\n getHunkSideEndBoundary,\n getHunkSideStartBoundary,\n} from './getHunkSideBoundaries';\n\nexport interface ParseMergeConflictDiffFromFileResult {\n fileDiff: FileDiffMetadata;\n currentFile: FileContents;\n incomingFile: FileContents;\n actions: (MergeConflictDiffAction | undefined)[];\n markerRows: MergeConflictMarkerRow[];\n}\n\nexport interface MergeConflictDiffAction extends ProcessFileConflictData {\n // Kept for callback consumers that still need the original unresolved-region\n // source-line coordinates alongside structural hunk-content anchors.\n conflict: MergeConflictRegion;\n conflictIndex: number;\n markerLines: {\n start: string;\n base?: string;\n separator: string;\n end: string;\n };\n}\n\ninterface GetMergeConflictActionAnchorReturn {\n hunkIndex: number;\n lineIndex: number;\n}\n\n// Which section of a conflict we're currently inside while scanning lines.\n// Progresses: current → (optional) base → incoming.\ntype MergeConflictStage = 'current' | 'base' | 'incoming';\ntype MergeConflictSide = MergeConflictStage;\ntype MergeConflictMarkerType = 'start' | 'base' | 'separator' | 'end';\n\n// Controls how buffered context lines are trimmed when flushed to hunkContent:\n// 'leading' — first flush of a hunk; trim excess from the start\n// 'before-change' — flush between changes; emit all buffered lines\n// 'trailing' — last flush of a hunk; trim excess from the end\ntype ContextFlushMode = 'before-change' | 'leading' | 'trailing';\n\n// Mutable accumulator for building a single Hunk. Tracks line counts, the\n// hunkContent array (sequence of context/change groups), and a \"context buffer\"\n// that defers writing context lines until we know whether they're leading,\n// trailing, or mid-hunk context.\n//\n// The context buffer avoids eagerly committing context lines to hunkContent.\n// When a change line arrives, we flush the buffer — trimming to maxContextLines\n// if it's the leading or trailing edge of a hunk, or splitting into two hunks\n// if the gap between changes exceeds maxContextLines * 2.\ninterface HunkBuilder {\n additionStart: number;\n deletionStart: number;\n additionCount: number;\n deletionCount: number;\n additionLines: number;\n deletionLines: number;\n additionLineIndex: number;\n deletionLineIndex: number;\n hunkContent: Hunk['hunkContent'];\n // Context buffer: instead of storing per-line index arrays, we track the\n // starting indices and a count. Since context lines always push to both\n // additionLines and deletionLines consecutively, indices can be derived.\n contextBufferAdditionStart: number;\n contextBufferDeletionStart: number;\n contextBufferCount: number;\n // Sparse map of buffer-offset → conflictIndex for base-section context lines.\n // Empty for most buffers since base lines are rare.\n contextBufferBaseConflicts: Map<number, number> | undefined;\n}\n\n// Tracks an in-progress conflict as we scan through its lines. Pushed onto\n// conflictStack when we hit a <<<<<<< marker, and popped + finalized when we\n// hit the matching >>>>>>> marker. The `stage` field tells processLine which\n// section we're in so it knows whether to emit deletions, context, or additions.\ninterface ConflictFrame {\n conflictIndex: number;\n stage: MergeConflictStage;\n startLineIndex: number;\n baseMarkerLineIndex?: number;\n separatorLineIndex?: number;\n markerLines: {\n start: string;\n base?: string;\n separator?: string;\n };\n}\n\ninterface ConflictActionBuilder {\n action: MergeConflictDiffAction;\n completed: boolean;\n}\n\n// Bundles all mutable state shared across parse helper functions, replacing\n// closure-captured variables with a single object passed by reference.\n//\n// The two key arrays — deletionLines and additionLines — are the synthetic\n// \"before\" and \"after\" file contents. Context lines are pushed to both arrays\n// (identical on both sides). Current-side conflict lines go only into\n// deletionLines; incoming-side lines go only into additionLines. After parsing,\n// joining each array produces the resolved file for that side.\ninterface ParseState {\n // \"Before\" file lines (context + current-side conflict content).\n deletionLines: string[];\n // \"After\" file lines (context + incoming-side conflict content).\n additionLines: string[];\n // Stack of open conflict regions (supports nested conflicts, though rare).\n conflictStack: ConflictFrame[];\n // Parallel to actions[]; accumulates content indices during parsing.\n conflictBuilders: ConflictActionBuilder[];\n // Final output: one action per conflict, indexed by conflictIndex.\n actions: (MergeConflictDiffAction | undefined)[];\n // Finalized hunks, appended as context gaps cause hunk splits.\n hunks: Hunk[];\n nextConflictIndex: number;\n // Running line totals used to compute hunk splitLineStart/unifiedLineStart.\n splitLineCount: number;\n unifiedLineCount: number;\n // 1-based line number where the previous hunk ended (for collapsedBefore).\n lastHunkEnd: number;\n // The hunk currently being built; undefined between hunks.\n activeHunk: HunkBuilder | undefined;\n maxContextLines: number;\n // Cached maxContextLines * 2 (the threshold for splitting a hunk).\n maxContextLines2: number;\n}\n\nexport function getMergeConflictActionAnchor(\n action: MergeConflictDiffAction,\n fileDiff: FileDiffMetadata\n): GetMergeConflictActionAnchorReturn | undefined {\n const hunk = fileDiff.hunks[action.hunkIndex];\n if (hunk == null) {\n return undefined;\n }\n return {\n hunkIndex: action.hunkIndex,\n lineIndex: getUnifiedLineStartForContent(hunk, action.startContentIndex),\n };\n}\n\n// Main entry point. Walks every line of the conflict file exactly once,\n// dispatching each line through processLine which routes it to the appropriate\n// emitter (context or change). After the loop, finalizes the last hunk,\n// validates all conflicts were closed, and assembles the result.\n//\n// The three phases are:\n// 1. Line-by-line scan — builds hunks and conflict actions incrementally\n// 2. Post-loop cleanup — flushes trailing context, finalizes last hunk\n// 3. Result assembly — joins line arrays, builds marker rows for the UI\nexport function parseMergeConflictDiffFromFile(\n file: FileContents,\n maxContextLines: number = 6\n): ParseMergeConflictDiffFromFileResult {\n // Never allow maxContextLines to drop below 1 or else things break.\n maxContextLines = Math.max(maxContextLines, 1);\n\n const s: ParseState = {\n deletionLines: [],\n additionLines: [],\n conflictStack: [],\n conflictBuilders: [],\n actions: [],\n hunks: [],\n nextConflictIndex: 0,\n splitLineCount: 0,\n unifiedLineCount: 0,\n lastHunkEnd: 0,\n activeHunk: undefined,\n maxContextLines,\n maxContextLines2: maxContextLines * 2,\n };\n\n // Phase 1: Line-by-line scan. We inline the indexOf loop here (rather than\n // calling a helper with a callback) to avoid creating a closure on the hot\n // path. Each line is sliced and dispatched to processLine.\n const contents = file.contents;\n const contentLength = contents.length;\n if (contentLength > 0) {\n let lineStart = 0;\n let lineIndex = 0;\n let newlinePos = contents.indexOf('\\n', lineStart);\n while (newlinePos !== -1) {\n processLine(s, contents.slice(lineStart, newlinePos + 1), lineIndex);\n lineStart = newlinePos + 1;\n lineIndex++;\n newlinePos = contents.indexOf('\\n', lineStart);\n }\n if (lineStart < contentLength) {\n processLine(s, contents.slice(lineStart), lineIndex);\n }\n }\n\n // Phase 2: Post-loop cleanup. Any unclosed conflict is an error. If the\n // last hunk has buffered context lines, flush them as trailing context and\n // finalize the hunk.\n if (s.conflictStack.length > 0) {\n throw new Error(\n 'parseMergeConflictDiffFromFile: unfinished merge conflict marker stack'\n );\n }\n\n if (s.activeHunk != null && s.activeHunk.hunkContent.length > 0) {\n flushBufferedContext(s, s.activeHunk, 'trailing');\n finalizeActiveHunk(s);\n }\n\n for (\n let conflictIndex = 0;\n conflictIndex < s.conflictBuilders.length;\n conflictIndex++\n ) {\n const builder = s.conflictBuilders[conflictIndex];\n if (builder == null || !builder.completed) {\n throw new Error(\n `parseMergeConflictDiffFromFile: failed to build merge conflict action ${conflictIndex}`\n );\n }\n }\n\n // Phase 3: Result assembly. Account for any collapsed lines after the last\n // hunk, then join the line arrays to produce resolved file contents.\n if (\n s.hunks.length > 0 &&\n s.additionLines.length > 0 &&\n s.deletionLines.length > 0\n ) {\n const lastHunk = s.hunks[s.hunks.length - 1];\n const collapsedAfter = Math.max(\n s.additionLines.length -\n getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount),\n 0\n );\n s.splitLineCount += collapsedAfter;\n s.unifiedLineCount += collapsedAfter;\n }\n\n const currentContents = s.deletionLines.join('');\n const incomingContents = s.additionLines.join('');\n const currentFile = createResolvedConflictFile(\n file,\n 'current',\n currentContents\n );\n const incomingFile = createResolvedConflictFile(\n file,\n 'incoming',\n incomingContents\n );\n\n let type: FileDiffMetadata['type'] = 'change';\n if (incomingContents === '') {\n type = 'deleted';\n } else if (currentContents === '') {\n type = 'new';\n }\n\n const fileDiff: FileDiffMetadata = {\n name: file.name,\n prevName: undefined,\n type,\n hunks: s.hunks,\n splitLineCount: s.splitLineCount,\n unifiedLineCount: s.unifiedLineCount,\n isPartial: false,\n deletionLines: s.deletionLines,\n additionLines: s.additionLines,\n cacheKey:\n file.cacheKey != null\n ? `${file.cacheKey}:merge-conflict-diff`\n : undefined,\n };\n\n return {\n fileDiff,\n currentFile,\n incomingFile,\n actions: s.actions,\n markerRows: buildMergeConflictMarkerRows(fileDiff, s.actions),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Module-level parse helpers. Each receives ParseState by reference rather\n// than capturing variables via closure. The call graph from the hot path is:\n//\n// processLine\n// ├─ emitContextLine → ensureActiveHunk\n// ├─ emitChangeLine → ensureActiveHunk, splitHunkWithBufferedContext,\n// │ flushBufferedContext, appendChangeLine,\n// │ assignConflictContent\n// ├─ handleStartMarker\n// └─ finalizeConflict\n// ---------------------------------------------------------------------------\n\n// Routes a single source line to the right emitter based on whether we're\n// inside a conflict and, if so, which section (current/base/incoming).\n// Outside conflicts, only the start marker (<<<<<<< / charCode 60) can\n// change state, so we skip the full marker check for non-'<' lines.\nfunction processLine(s: ParseState, line: string, index: number): void {\n const frame = s.conflictStack[s.conflictStack.length - 1];\n\n // Outside any conflict: only start markers (<<<<<<<) can transition state.\n // Skip the full marker check for lines that can't be start markers.\n if (frame == null) {\n if (\n line.length >= 7 &&\n line.charCodeAt(0) === 60 &&\n getMergeConflictMarkerType(line) === 'start'\n ) {\n handleStartMarker(s, line, index);\n return;\n }\n emitContextLine(s, line);\n return;\n }\n\n // Inside a conflict: all marker types must be checked.\n const markerType = getMergeConflictMarkerType(line);\n\n if (markerType === 'start') {\n handleStartMarker(s, line, index);\n return;\n }\n\n if (markerType === 'base') {\n frame.stage = 'base';\n frame.baseMarkerLineIndex = index;\n frame.markerLines.base = line;\n return;\n }\n\n if (markerType === 'separator') {\n frame.stage = 'incoming';\n frame.separatorLineIndex = index;\n frame.markerLines.separator = line;\n return;\n }\n\n if (markerType === 'end') {\n const completedFrame = s.conflictStack.pop();\n if (completedFrame == null) {\n throw new Error(\n 'parseMergeConflictDiffFromFile: encountered end marker before start marker'\n );\n }\n finalizeConflict(s, completedFrame, index, line);\n return;\n }\n\n if (frame.stage === 'current') {\n emitChangeLine(s, 'deletion', line, frame.conflictIndex, 'current');\n } else if (frame.stage === 'base') {\n emitContextLine(s, line, frame.conflictIndex);\n } else {\n emitChangeLine(s, 'addition', line, frame.conflictIndex, 'incoming');\n }\n}\n\n// Lazily creates the active HunkBuilder if one doesn't exist yet. The hunk's\n// start positions are derived from the current length of the line arrays\n// (1-based, matching unified diff conventions).\nfunction ensureActiveHunk(s: ParseState): HunkBuilder {\n s.activeHunk ??= createHunkBuilder(\n s.additionLines.length + 1,\n s.deletionLines.length + 1\n );\n return s.activeHunk;\n}\n\n// \"Anchors\" a conflict to its position in the hunk's content array. Each\n// conflict needs to know which hunk it lives in (hunkIndex) and which content\n// entries correspond to its current/base/incoming sections. This is called\n// every time we emit a change or context line that belongs to a conflict, and\n// it incrementally widens the start/end content range.\nfunction assignConflictContent(\n s: ParseState,\n conflictIndex: number,\n role: MergeConflictSide,\n contentIndex: number\n): void {\n const builder = s.conflictBuilders[conflictIndex];\n if (builder == null) {\n throw new Error(\n `parseMergeConflictDiffFromFile: failed to locate conflict action ${conflictIndex}`\n );\n }\n\n const action = builder.action;\n const hunkIndex = s.hunks.length;\n if (action.hunkIndex < 0) {\n action.hunkIndex = hunkIndex;\n } else if (action.hunkIndex !== hunkIndex) {\n throw new Error(\n `parseMergeConflictDiffFromFile: conflict ${conflictIndex} spans multiple hunks and cannot be anchored`\n );\n }\n\n if (action.startContentIndex < 0) {\n action.startContentIndex = contentIndex;\n }\n action.endContentIndex = contentIndex;\n action.endMarkerContentIndex = contentIndex;\n\n if (role === 'current') {\n action.currentContentIndex ??= contentIndex;\n return;\n }\n if (role === 'base') {\n action.baseContentIndex ??= contentIndex;\n return;\n }\n action.incomingContentIndex = contentIndex;\n}\n\n// Appends a change line to the hunk's content array. If the previous entry is\n// already a 'change' group, we just bump its addition/deletion count instead\n// of creating a new entry — this keeps hunkContent compact. Returns the\n// content index so the caller can anchor the conflict to it.\nfunction appendChangeLine(\n hunk: HunkBuilder,\n lineType: 'addition' | 'deletion',\n additionLineIndex: number,\n deletionLineIndex: number\n): number {\n const hunkContent = hunk.hunkContent;\n const lastContent = hunkContent[hunkContent.length - 1];\n if (lastContent?.type === 'change') {\n if (lineType === 'addition') {\n lastContent.additions++;\n } else {\n lastContent.deletions++;\n }\n return hunkContent.length - 1;\n }\n hunkContent.push({\n type: 'change',\n additions: lineType === 'addition' ? 1 : 0,\n deletions: lineType === 'deletion' ? 1 : 0,\n additionLineIndex,\n deletionLineIndex,\n });\n return hunkContent.length - 1;\n}\n\n// Drains the hunk's context buffer into hunkContent, applying mode-dependent\n// trimming. The buffer accumulates context lines without committing them,\n// because we don't know yet whether they'll be leading context (trim start),\n// trailing context (trim end), or mid-hunk context (keep all). The mode tells\n// us which case we're in:\n//\n// 'leading' — first change in a new hunk; drop lines beyond\n// maxContextLines from the front, and shift the hunk's\n// start position forward accordingly.\n// 'trailing' — last flush before hunk finalization; keep at most\n// maxContextLines from the front of the buffer.\n// 'before-change' — mid-hunk context between two changes; emit everything.\nfunction flushBufferedContext(\n s: ParseState,\n hunk: HunkBuilder,\n mode: ContextFlushMode\n): void {\n let count = hunk.contextBufferCount;\n let addStart = hunk.contextBufferAdditionStart;\n let delStart = hunk.contextBufferDeletionStart;\n\n if (mode === 'leading' && count > s.maxContextLines) {\n const difference = count - s.maxContextLines;\n addStart += difference;\n delStart += difference;\n count = s.maxContextLines;\n hunk.additionStart += difference;\n hunk.deletionStart += difference;\n hunk.additionLineIndex += difference;\n hunk.deletionLineIndex += difference;\n }\n\n if (mode === 'trailing' && count > s.maxContextLines) {\n count = s.maxContextLines;\n }\n\n if (count === 0) {\n hunk.contextBufferCount = 0;\n hunk.contextBufferBaseConflicts = undefined;\n return;\n }\n\n // Bulk-append context: coalesce with previous context entry or create new\n // one. This avoids a per-line loop — significant when maxContextLines is\n // large.\n const hunkContent = hunk.hunkContent;\n const lastContent = hunkContent[hunkContent.length - 1];\n let contentIndex: number;\n if (lastContent?.type === 'context') {\n lastContent.lines += count;\n contentIndex = hunkContent.length - 1;\n } else {\n hunkContent.push({\n type: 'context',\n lines: count,\n additionLineIndex: addStart,\n deletionLineIndex: delStart,\n });\n contentIndex = hunkContent.length - 1;\n }\n hunk.additionCount += count;\n hunk.deletionCount += count;\n\n // Assign base-section conflict anchors (rare — only when base lines exist)\n const baseConflicts = hunk.contextBufferBaseConflicts;\n if (baseConflicts != null) {\n const bufferStartOffset = addStart - hunk.contextBufferAdditionStart;\n for (const [offset, conflictIndex] of baseConflicts) {\n if (offset >= bufferStartOffset && offset < bufferStartOffset + count) {\n assignConflictContent(s, conflictIndex, 'base', contentIndex);\n }\n }\n }\n hunk.contextBufferCount = 0;\n hunk.contextBufferBaseConflicts = undefined;\n}\n\n// Converts the mutable HunkBuilder into an immutable Hunk and pushes it onto\n// s.hunks. Computes line counts for split and unified view, the collapsed-\n// before gap (lines between the previous hunk and this one), and the hunk\n// header string (e.g. \"@@ -1,5 +1,7 @@\").\nfunction finalizeActiveHunk(s: ParseState): void {\n if (s.activeHunk == null) {\n return;\n }\n\n const hunk = s.activeHunk;\n s.activeHunk = undefined;\n if (hunk.hunkContent.length === 0) {\n return;\n }\n\n let hunkSplitLineCount = 0;\n let hunkUnifiedLineCount = 0;\n for (const content of hunk.hunkContent) {\n if (content.type === 'context') {\n hunkSplitLineCount += content.lines;\n hunkUnifiedLineCount += content.lines;\n } else {\n hunkSplitLineCount += Math.max(content.additions, content.deletions);\n hunkUnifiedLineCount += content.additions + content.deletions;\n }\n }\n\n // While building, each side's start is \"next line to consume\" (1-based). A\n // side that finished with zero lines must shift down to the unified `N,0`\n // convention (start names the line the hunk applies after), which is what\n // hunk headers, getHunkSideStartBoundary, and downstream boundary math all\n // assume — e.g. a whole-file deletion emits `+0,0`, not `+1,0`.\n const additionStart =\n hunk.additionCount === 0 ? hunk.additionStart - 1 : hunk.additionStart;\n const deletionStart =\n hunk.deletionCount === 0 ? hunk.deletionStart - 1 : hunk.deletionStart;\n\n const collapsedBefore = Math.max(\n getHunkSideStartBoundary(additionStart, hunk.additionCount) - s.lastHunkEnd,\n 0\n );\n const finalizedHunk: Hunk = {\n collapsedBefore,\n additionStart,\n additionCount: hunk.additionCount,\n additionLines: hunk.additionLines,\n additionLineIndex: hunk.additionLineIndex,\n deletionStart,\n deletionCount: hunk.deletionCount,\n deletionLines: hunk.deletionLines,\n deletionLineIndex: hunk.deletionLineIndex,\n hunkContent: hunk.hunkContent,\n hunkContext: undefined,\n hunkSpecs: `@@ -${formatHunkRange(deletionStart, hunk.deletionCount)} +${formatHunkRange(additionStart, hunk.additionCount)} @@\\n`,\n splitLineStart: s.splitLineCount + collapsedBefore,\n splitLineCount: hunkSplitLineCount,\n unifiedLineStart: s.unifiedLineCount + collapsedBefore,\n unifiedLineCount: hunkUnifiedLineCount,\n noEOFCRAdditions: false,\n noEOFCRDeletions: false,\n };\n\n s.hunks.push(finalizedHunk);\n s.splitLineCount += collapsedBefore + hunkSplitLineCount;\n s.unifiedLineCount += collapsedBefore + hunkUnifiedLineCount;\n s.lastHunkEnd = getHunkSideEndBoundary(additionStart, hunk.additionCount);\n}\n\n// Called when the context buffer between two changes exceeds maxContextLines*2.\n// This means there's a big enough gap to warrant splitting into separate hunks\n// (just like `diff -U` does). The procedure:\n// 1. Flush the first maxContextLines of the buffer as trailing context\n// 2. Finalize the current hunk\n// 3. Start a new hunk pre-seeded with the last maxContextLines as leading context\n// The middle portion of the buffer (between the two maxContextLines slices) is\n// the \"collapsed\" region — lines omitted from the diff view.\nfunction splitHunkWithBufferedContext(s: ParseState): void {\n if (s.activeHunk == null) {\n return;\n }\n\n const hunk = s.activeHunk;\n const count = hunk.contextBufferCount;\n const omittedContextLineCount = count - s.maxContextLines2;\n\n // Save trailing context start indices for the next hunk.\n const nextAddStart =\n hunk.contextBufferAdditionStart + count - s.maxContextLines;\n const nextDelStart =\n hunk.contextBufferDeletionStart + count - s.maxContextLines;\n\n // Extract base conflicts that fall within the trailing portion.\n let nextBaseConflicts: Map<number, number> | undefined;\n if (hunk.contextBufferBaseConflicts != null) {\n const tailOffset = count - s.maxContextLines;\n for (const [offset, ci] of hunk.contextBufferBaseConflicts) {\n if (offset >= tailOffset) {\n nextBaseConflicts ??= new Map();\n nextBaseConflicts.set(offset - tailOffset, ci);\n }\n }\n }\n\n flushBufferedContext(s, hunk, 'trailing');\n const emittedAdditionCount = hunk.additionCount;\n const emittedDeletionCount = hunk.deletionCount;\n finalizeActiveHunk(s);\n\n s.activeHunk = createHunkBuilder(\n hunk.additionStart + emittedAdditionCount + omittedContextLineCount,\n hunk.deletionStart + emittedDeletionCount + omittedContextLineCount\n );\n s.activeHunk.contextBufferAdditionStart = nextAddStart;\n s.activeHunk.contextBufferDeletionStart = nextDelStart;\n s.activeHunk.contextBufferCount = s.maxContextLines;\n s.activeHunk.contextBufferBaseConflicts = nextBaseConflicts;\n}\n\n// Adds a context line (identical on both sides of the diff). The line is pushed\n// to both additionLines and deletionLines, then buffered in the hunk's context\n// buffer rather than committed to hunkContent immediately. This deferred write\n// is what enables the leading/trailing trim logic in flushBufferedContext.\n//\n// For base-section lines inside a diff3 conflict, pass the conflict index so\n// the buffer can record the association; when the buffer is flushed, those\n// lines get anchored to the conflict via assignConflictContent.\nfunction emitContextLine(\n s: ParseState,\n line: string,\n baseConflictIndex: number = -1\n): void {\n const hunk = ensureActiveHunk(s);\n // Reset buffer start on first line after a flush/creation.\n if (hunk.contextBufferCount === 0) {\n hunk.contextBufferAdditionStart = s.additionLines.length;\n hunk.contextBufferDeletionStart = s.deletionLines.length;\n }\n s.additionLines.push(line);\n s.deletionLines.push(line);\n if (baseConflictIndex >= 0) {\n hunk.contextBufferBaseConflicts ??= new Map();\n hunk.contextBufferBaseConflicts.set(\n hunk.contextBufferCount,\n baseConflictIndex\n );\n }\n hunk.contextBufferCount++;\n}\n\n// Adds a change line (addition or deletion) to the current hunk. This is the\n// main \"work\" function on the hot path and orchestrates several steps:\n// 1. If there's a large context gap since the last change, split the hunk\n// 2. Flush any buffered context lines (leading trim on first change, or\n// pass-through for mid-hunk context)\n// 3. Push the line to the appropriate line array (additions or deletions)\n// 4. Append/coalesce the change into hunkContent\n// 5. Anchor the conflict action to the content index\nfunction emitChangeLine(\n s: ParseState,\n lineType: 'addition' | 'deletion',\n line: string,\n conflictIndex: number,\n role: MergeConflictSide\n): void {\n let hunk = ensureActiveHunk(s);\n // If the context gap since the last change exceeds 2x maxContextLines,\n // split into two hunks: trailing context for the old, leading for the new.\n if (\n hunk.hunkContent.length > 0 &&\n hunk.contextBufferCount > s.maxContextLines2\n ) {\n splitHunkWithBufferedContext(s);\n hunk = s.activeHunk!;\n }\n\n flushBufferedContext(\n s,\n hunk,\n hunk.hunkContent.length === 0 ? 'leading' : 'before-change'\n );\n\n const additionLineIndex = s.additionLines.length;\n const deletionLineIndex = s.deletionLines.length;\n if (lineType === 'addition') {\n s.additionLines.push(line);\n } else {\n s.deletionLines.push(line);\n }\n\n const contentIndex = appendChangeLine(\n hunk,\n lineType,\n additionLineIndex,\n deletionLineIndex\n );\n\n if (lineType === 'addition') {\n hunk.additionCount++;\n hunk.additionLines++;\n } else {\n hunk.deletionCount++;\n hunk.deletionLines++;\n }\n assignConflictContent(s, conflictIndex, role, contentIndex);\n}\n\n// Called when we hit a >>>>>>> end marker. Takes the completed ConflictFrame\n// and writes the final source-line coordinates and marker text into the\n// conflict action. Also handles empty-side conflicts: if one side had no\n// content lines, we fall back to the other side's content index so the action\n// always has valid anchors. This is what makes conflicts like \"add vs nothing\"\n// or \"nothing vs add\" representable.\nfunction finalizeConflict(\n s: ParseState,\n frame: ConflictFrame,\n endLineIndex: number,\n endMarkerLine: string\n): void {\n if (frame.separatorLineIndex == null || frame.markerLines.separator == null) {\n throw new Error(\n `parseMergeConflictDiffFromFile: conflict ${frame.conflictIndex} is missing a separator marker`\n );\n }\n\n const builder = s.conflictBuilders[frame.conflictIndex];\n if (builder == null) {\n throw new Error(\n `parseMergeConflictDiffFromFile: failed to finalize conflict ${frame.conflictIndex}`\n );\n }\n\n const action = builder.action;\n action.markerLines.separator = frame.markerLines.separator;\n action.markerLines.end = endMarkerLine;\n if (frame.markerLines.base != null) {\n action.markerLines.base = frame.markerLines.base;\n }\n\n action.conflict = {\n conflictIndex: frame.conflictIndex,\n startLineIndex: frame.startLineIndex,\n startLineNumber: frame.startLineIndex + 1,\n separatorLineIndex: frame.separatorLineIndex,\n separatorLineNumber: frame.separatorLineIndex + 1,\n endLineIndex,\n endLineNumber: endLineIndex + 1,\n baseMarkerLineIndex: frame.baseMarkerLineIndex,\n baseMarkerLineNumber:\n frame.baseMarkerLineIndex != null\n ? frame.baseMarkerLineIndex + 1\n : undefined,\n };\n\n // If one side of the conflict was empty (e.g. \"add vs nothing\"), its content\n // index will be undefined. Use the other side as a fallback so the action\n // always has a valid anchor for the UI to render.\n const fallbackContentIndex =\n action.currentContentIndex ?? action.incomingContentIndex;\n action.currentContentIndex ??= fallbackContentIndex;\n action.incomingContentIndex ??= fallbackContentIndex;\n if (action.startContentIndex < 0 && fallbackContentIndex != null) {\n action.startContentIndex = fallbackContentIndex;\n }\n if (action.endContentIndex < 0 && fallbackContentIndex != null) {\n action.endContentIndex = fallbackContentIndex;\n }\n if (action.endMarkerContentIndex < 0 && fallbackContentIndex != null) {\n action.endMarkerContentIndex = fallbackContentIndex;\n }\n\n if (\n action.hunkIndex < 0 ||\n action.startContentIndex < 0 ||\n action.endContentIndex < 0 ||\n action.endMarkerContentIndex < 0\n ) {\n throw new Error(\n `parseMergeConflictDiffFromFile: failed to anchor merge conflict ${frame.conflictIndex}`\n );\n }\n\n s.actions[action.conflictIndex] = action;\n builder.completed = true;\n}\n\n// Pushes a new ConflictFrame onto the stack and creates a placeholder\n// ConflictActionBuilder. The builder starts with sentinel values (-1 for\n// indices) that get filled in as we encounter content lines and markers.\n// The frame tracks which section we're scanning (current → base → incoming);\n// the builder accumulates the final action that downstream consumers use.\nfunction handleStartMarker(\n s: ParseState,\n line: string,\n lineIndex: number\n): void {\n const conflictIndex = s.nextConflictIndex;\n s.nextConflictIndex++;\n s.conflictStack.push({\n conflictIndex,\n stage: 'current',\n startLineIndex: lineIndex,\n markerLines: { start: line },\n });\n s.conflictBuilders[conflictIndex] = {\n completed: false,\n action: {\n conflict: {\n conflictIndex,\n startLineIndex: lineIndex,\n startLineNumber: lineIndex + 1,\n separatorLineIndex: lineIndex,\n separatorLineNumber: lineIndex + 1,\n endLineIndex: lineIndex,\n endLineNumber: lineIndex + 1,\n baseMarkerLineIndex: undefined,\n baseMarkerLineNumber: undefined,\n },\n conflictIndex,\n hunkIndex: -1,\n startContentIndex: -1,\n endContentIndex: -1,\n endMarkerContentIndex: -1,\n markerLines: {\n start: line,\n separator: '',\n end: '',\n },\n },\n };\n}\n\nfunction createHunkBuilder(\n additionStart: number,\n deletionStart: number\n): HunkBuilder {\n return {\n additionStart,\n deletionStart,\n additionCount: 0,\n deletionCount: 0,\n additionLines: 0,\n deletionLines: 0,\n additionLineIndex: Math.max(additionStart - 1, 0),\n deletionLineIndex: Math.max(deletionStart - 1, 0),\n hunkContent: [],\n contextBufferAdditionStart: Math.max(additionStart - 1, 0),\n contextBufferDeletionStart: Math.max(deletionStart - 1, 0),\n contextBufferCount: 0,\n contextBufferBaseConflicts: undefined,\n };\n}\n\nfunction formatHunkRange(start: number, count: number): string {\n return count === 1 ? `${start}` : `${start},${count}`;\n}\n\n// Detects whether a line is a merge conflict marker by inspecting the first\n// character and counting consecutive repetitions. Git conflict markers are\n// 7+ repeated characters:\n// '<' (60) = start (<<<<<<< current)\n// '|' (124) = base (||||||| base)\n// '=' (61) = separator (=======)\n// '>' (62) = end (>>>>>>> incoming)\n// The separator must be exactly '=======' with no trailing text; other markers\n// allow an optional space + label (e.g. \"<<<<<<< HEAD\").\nfunction getMergeConflictMarkerType(\n line: string\n): MergeConflictMarkerType | undefined {\n if (line.length < 7) {\n return undefined;\n }\n\n const markerCode = line.charCodeAt(0);\n if (\n markerCode !== 60 &&\n markerCode !== 62 &&\n markerCode !== 61 &&\n markerCode !== 124\n ) {\n return undefined;\n }\n\n const lineEnd = getLineContentEndIndex(line);\n if (lineEnd < 7) {\n return undefined;\n }\n\n let markerLength = 1;\n while (\n markerLength < lineEnd &&\n line.charCodeAt(markerLength) === markerCode\n ) {\n markerLength++;\n }\n\n if (markerLength < 7) {\n return undefined;\n }\n\n if (markerCode === 61) {\n return markerLength === lineEnd ? 'separator' : undefined;\n }\n\n if (\n markerLength !== lineEnd &&\n !isWhitespaceCode(line.charCodeAt(markerLength))\n ) {\n return undefined;\n }\n\n if (markerCode === 60) {\n return 'start';\n }\n if (markerCode === 62) {\n return 'end';\n }\n return 'base';\n}\n\nfunction getLineContentEndIndex(line: string): number {\n let end = line.length;\n if (end > 0 && line.charCodeAt(end - 1) === 10) {\n end--;\n }\n if (end > 0 && line.charCodeAt(end - 1) === 13) {\n end--;\n }\n return end;\n}\n\nfunction isWhitespaceCode(code: number): boolean {\n return (\n code === 9 ||\n code === 10 ||\n code === 11 ||\n code === 12 ||\n code === 13 ||\n code === 32\n );\n}\n\nfunction createResolvedConflictFile(\n file: FileContents,\n side: 'current' | 'incoming',\n contents: string\n): FileContents {\n return {\n ...file,\n contents,\n cacheKey:\n file.cacheKey != null\n ? `${file.cacheKey}:merge-conflict-${side}`\n : undefined,\n };\n}\n\n// Builds the marker row array that tells the UI where to render conflict\n// decorations (start/base/separator/end lines) in the diff view. Each marker\n// row maps a conflict marker to a specific line index in unified view.\n//\n// This is a post-processing step over the finalized hunks and actions. It\n// caches cumulative line-start positions per hunk to avoid recomputing them\n// for every marker.\nexport function buildMergeConflictMarkerRows(\n fileDiff: FileDiffMetadata,\n actions: (MergeConflictDiffAction | undefined)[]\n): MergeConflictMarkerRow[] {\n const markerRows: MergeConflictMarkerRow[] = [];\n const hunkLineStartCache: (number[] | undefined)[] = new Array(\n fileDiff.hunks.length\n );\n\n const getLineStart = (hunkIndex: number, contentIndex: number): number => {\n const hunk = fileDiff.hunks[hunkIndex];\n if (hunk == null) {\n return 0;\n }\n let starts = hunkLineStartCache[hunkIndex];\n if (starts == null) {\n starts = new Array<number>(hunk.hunkContent.length + 1);\n let lineIndex = hunk.unifiedLineStart;\n starts[0] = lineIndex;\n for (let index = 0; index < hunk.hunkContent.length; index++) {\n const content = hunk.hunkContent[index];\n lineIndex +=\n content.type === 'context'\n ? content.lines\n : content.deletions + content.additions;\n starts[index + 1] = lineIndex;\n }\n hunkLineStartCache[hunkIndex] = starts;\n }\n return starts[Math.max(contentIndex, 0)] ?? hunk.unifiedLineStart;\n };\n\n const getLineEnd = (hunkIndex: number, contentIndex: number): number => {\n const lineStart = getLineStart(hunkIndex, contentIndex);\n const starts = hunkLineStartCache[hunkIndex];\n const lineEndExclusive =\n starts?.[Math.max(contentIndex + 1, 0)] ??\n getLineStart(hunkIndex, contentIndex + 1);\n return Math.max(lineStart, lineEndExclusive - 1);\n };\n\n for (const action of actions) {\n if (action == null) {\n continue;\n }\n\n const hunk = fileDiff.hunks[action.hunkIndex];\n if (hunk == null) {\n continue;\n }\n\n const actionLineIndex = getLineStart(\n action.hunkIndex,\n action.startContentIndex\n );\n markerRows.push(\n createMergeConflictMarkerRow(\n action,\n 'marker-start',\n action.startContentIndex,\n action.markerLines.start,\n actionLineIndex\n )\n );\n\n if (action.baseContentIndex != null) {\n const currentContentIndex = action.currentContentIndex;\n const incomingContentIndex = action.incomingContentIndex;\n if (currentContentIndex == null || incomingContentIndex == null) {\n continue;\n }\n\n const baseMarkerLine = action.markerLines.base;\n if (baseMarkerLine == null) {\n continue;\n }\n\n const currentChange = hunk.hunkContent[currentContentIndex];\n const baseContext = hunk.hunkContent[action.baseContentIndex];\n const incomingChange = hunk.hunkContent[incomingContentIndex];\n if (\n currentChange?.type !== 'change' ||\n baseContext?.type !== 'context' ||\n incomingChange?.type !== 'change'\n ) {\n continue;\n }\n\n const currentStart = getLineStart(action.hunkIndex, currentContentIndex);\n const incomingStart = getLineStart(\n action.hunkIndex,\n incomingContentIndex\n );\n markerRows.push(\n createMergeConflictMarkerRow(\n action,\n 'marker-base',\n action.baseContentIndex,\n baseMarkerLine,\n currentStart + currentChange.deletions\n )\n );\n\n markerRows.push(\n createMergeConflictMarkerRow(\n action,\n 'marker-separator',\n action.baseContentIndex,\n action.markerLines.separator,\n incomingStart\n ),\n createMergeConflictMarkerRow(\n action,\n 'marker-end',\n action.endMarkerContentIndex,\n action.markerLines.end,\n getLineEnd(action.hunkIndex, action.endMarkerContentIndex)\n )\n );\n continue;\n }\n\n const currentContentIndex = action.currentContentIndex;\n if (currentContentIndex == null) {\n continue;\n }\n const content = hunk.hunkContent[currentContentIndex];\n if (content?.type !== 'change') {\n continue;\n }\n\n const contentStart = getLineStart(action.hunkIndex, currentContentIndex);\n const separatorLineIndex =\n content.deletions > 0\n ? contentStart + content.deletions\n : actionLineIndex;\n\n markerRows.push(\n createMergeConflictMarkerRow(\n action,\n 'marker-separator',\n currentContentIndex,\n action.markerLines.separator,\n separatorLineIndex\n ),\n createMergeConflictMarkerRow(\n action,\n 'marker-end',\n action.endMarkerContentIndex,\n action.markerLines.end,\n getLineEnd(action.hunkIndex, action.endMarkerContentIndex)\n )\n );\n }\n\n return markerRows;\n}\n\nfunction createMergeConflictMarkerRow(\n action: MergeConflictDiffAction,\n type: MergeConflictMarkerRowType,\n contentIndex: number,\n lineText: string,\n lineIndex: number\n): MergeConflictMarkerRow {\n return {\n type,\n hunkIndex: action.hunkIndex,\n contentIndex,\n conflictIndex: action.conflictIndex,\n lineText,\n lineIndex,\n };\n}\n\nfunction getUnifiedLineStartForContent(\n hunk: Hunk,\n contentIndex: number\n): number {\n let lineIndex = hunk.unifiedLineStart;\n for (let index = 0; index < contentIndex; index++) {\n const content = hunk.hunkContent[index];\n lineIndex +=\n content.type === 'context'\n ? content.lines\n : content.deletions + content.additions;\n }\n return lineIndex;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"parseMergeConflictDiffFromFile.js","names":[],"sources":["../../src/utils/parseMergeConflictDiffFromFile.ts"],"sourcesContent":["// Parses a file containing git merge conflict markers (<<<<<<< / ======= / >>>>>>>)\n// into a synthetic unified diff. The core idea: treat the conflict file as though\n// \"current\" lines are deletions and \"incoming\" lines are additions. Lines outside\n// conflicts (and optional \"base\" sections from diff3) become shared context.\n//\n// The result is a standard FileDiffMetadata with hunks — identical in shape to what\n// you'd get from parsing a real unified diff — plus a parallel array of\n// MergeConflictDiffActions that anchor each conflict region to positions within the\n// hunk structure. Downstream consumers (e.g. the merge conflict UI) use these\n// anchors to overlay conflict markers onto the diff view.\n//\n// Architecture note: all helper functions are module-level (not closures inside the\n// main function) and receive a shared ParseState object by reference. This avoids\n// per-call scope-chain traversal on the hot path (~20K lines), where every line\n// triggers 2-3 helper calls.\n//\n// ---\n// NOTE: This file was nearly entirely written and optimized by AI. It has a\n// verification harness that any future changes (human or AI) should be validated\n// against:\n//\n// Snapshot tests (from packages/diffs/):\n// bun test parseMergeConflictDiffFromFile\n//\n// Performance benchmark (checksum must match 31314920):\n// moonx diffs:benchmark-parse-merge-conflict\n//\n// If you encounter a bug:\n// 1. Add a new test case in test/parseMergeConflictDiffFromFile.test.ts with\n// input that reproduces the failure. Use toMatchSnapshot() so the expected\n// output is captured automatically once fixed.\n// 2. Run `bun test parseMergeConflictDiffFromFile` from packages/diffs/ to\n// confirm the new test fails.\n// 3. Use an AI agent with extended/high thinking to fix the logic — the\n// snapshot tests and benchmark provide a tight feedback loop. The agent\n// should iterate until all snapshots pass AND the benchmark checksum\n// matches. Update snapshots with `bun test test/parseMergeConflictDiffFromFile.test.ts -u`\n// only after verifying the new output is correct.\n// ---\n\nimport type {\n FileContents,\n FileDiffMetadata,\n Hunk,\n MergeConflictMarkerRow,\n MergeConflictMarkerRowType,\n MergeConflictRegion,\n ProcessFileConflictData,\n} from '../types';\nimport {\n getHunkSideEndBoundary,\n getHunkSideStartBoundary,\n} from './getHunkSideBoundaries';\n\nexport interface ParseMergeConflictDiffFromFileResult {\n fileDiff: FileDiffMetadata;\n currentFile: FileContents;\n incomingFile: FileContents;\n actions: (MergeConflictDiffAction | undefined)[];\n markerRows: MergeConflictMarkerRow[];\n}\n\nexport interface MergeConflictDiffAction extends ProcessFileConflictData {\n // Kept for callback consumers that still need the original unresolved-region\n // source-line coordinates alongside structural hunk-content anchors.\n conflict: MergeConflictRegion;\n conflictIndex: number;\n markerLines: {\n start: string;\n base?: string;\n separator: string;\n end: string;\n };\n}\n\ninterface GetMergeConflictActionAnchorReturn {\n hunkIndex: number;\n lineIndex: number;\n}\n\n// Which section of a conflict we're currently inside while scanning lines.\n// Progresses: current → (optional) base → incoming.\ntype MergeConflictStage = 'current' | 'base' | 'incoming';\ntype MergeConflictSide = MergeConflictStage;\ntype MergeConflictMarkerType = 'start' | 'base' | 'separator' | 'end';\n\n// Controls how buffered context lines are trimmed when flushed to hunkContent:\n// 'leading' — first flush of a hunk; trim excess from the start\n// 'before-change' — flush between changes; emit all buffered lines\n// 'trailing' — last flush of a hunk; trim excess from the end\ntype ContextFlushMode = 'before-change' | 'leading' | 'trailing';\n\n// Mutable accumulator for building a single Hunk. Tracks line counts, the\n// hunkContent array (sequence of context/change groups), and a \"context buffer\"\n// that defers writing context lines until we know whether they're leading,\n// trailing, or mid-hunk context.\n//\n// The context buffer avoids eagerly committing context lines to hunkContent.\n// When a change line arrives, we flush the buffer — trimming to maxContextLines\n// if it's the leading or trailing edge of a hunk, or splitting into two hunks\n// if the gap between changes exceeds maxContextLines * 2.\ninterface HunkBuilder {\n additionStart: number;\n deletionStart: number;\n additionCount: number;\n deletionCount: number;\n additionLines: number;\n deletionLines: number;\n additionLineIndex: number;\n deletionLineIndex: number;\n hunkContent: Hunk['hunkContent'];\n // Context buffer: instead of storing per-line index arrays, we track the\n // starting indices and a count. Since context lines always push to both\n // additionLines and deletionLines consecutively, indices can be derived.\n contextBufferAdditionStart: number;\n contextBufferDeletionStart: number;\n contextBufferCount: number;\n // Sparse map of buffer-offset → conflictIndex for base-section context lines.\n // Empty for most buffers since base lines are rare.\n contextBufferBaseConflicts: Map<number, number> | undefined;\n}\n\n// Tracks an in-progress conflict as we scan through its lines. Pushed onto\n// conflictStack when we hit a <<<<<<< marker, and popped + finalized when we\n// hit the matching >>>>>>> marker. The `stage` field tells processLine which\n// section we're in so it knows whether to emit deletions, context, or additions.\ninterface ConflictFrame {\n conflictIndex: number;\n stage: MergeConflictStage;\n startLineIndex: number;\n baseMarkerLineIndex?: number;\n separatorLineIndex?: number;\n markerLines: {\n start: string;\n base?: string;\n separator?: string;\n };\n}\n\ninterface ConflictActionBuilder {\n action: MergeConflictDiffAction;\n completed: boolean;\n}\n\n// Bundles all mutable state shared across parse helper functions, replacing\n// closure-captured variables with a single object passed by reference.\n//\n// The two key arrays — deletionLines and additionLines — are the synthetic\n// \"before\" and \"after\" file contents. Context lines are pushed to both arrays\n// (identical on both sides). Current-side conflict lines go only into\n// deletionLines; incoming-side lines go only into additionLines. After parsing,\n// joining each array produces the resolved file for that side.\ninterface ParseState {\n // \"Before\" file lines (context + current-side conflict content).\n deletionLines: string[];\n // \"After\" file lines (context + incoming-side conflict content).\n additionLines: string[];\n // Stack of open conflict regions (supports nested conflicts, though rare).\n conflictStack: ConflictFrame[];\n // Parallel to actions[]; accumulates content indices during parsing.\n conflictBuilders: ConflictActionBuilder[];\n // Final output: one action per conflict, indexed by conflictIndex.\n actions: (MergeConflictDiffAction | undefined)[];\n // Finalized hunks, appended as context gaps cause hunk splits.\n hunks: Hunk[];\n nextConflictIndex: number;\n // Running line totals used to compute hunk splitLineStart/unifiedLineStart.\n splitLineCount: number;\n unifiedLineCount: number;\n // 1-based line number where the previous hunk ended (for collapsedBefore).\n lastHunkEnd: number;\n // The hunk currently being built; undefined between hunks.\n activeHunk: HunkBuilder | undefined;\n maxContextLines: number;\n // Cached maxContextLines * 2 (the threshold for splitting a hunk).\n maxContextLines2: number;\n}\n\nexport function getMergeConflictActionAnchor(\n action: MergeConflictDiffAction,\n fileDiff: FileDiffMetadata\n): GetMergeConflictActionAnchorReturn | undefined {\n const hunk = fileDiff.hunks[action.hunkIndex];\n if (hunk == null) {\n return undefined;\n }\n return {\n hunkIndex: action.hunkIndex,\n lineIndex: getUnifiedLineStartForContent(hunk, action.startContentIndex),\n };\n}\n\n// Main entry point. Walks every line of the conflict file exactly once,\n// dispatching each line through processLine which routes it to the appropriate\n// emitter (context or change). After the loop, finalizes the last hunk,\n// validates all conflicts were closed, and assembles the result.\n//\n// The three phases are:\n// 1. Line-by-line scan — builds hunks and conflict actions incrementally\n// 2. Post-loop cleanup — flushes trailing context, finalizes last hunk\n// 3. Result assembly — joins line arrays, builds marker rows for the UI\nexport function parseMergeConflictDiffFromFile(\n file: FileContents,\n maxContextLines: number = 6\n): ParseMergeConflictDiffFromFileResult {\n // Never allow maxContextLines to drop below 1 or else things break.\n maxContextLines = Math.max(maxContextLines, 1);\n\n const s: ParseState = {\n deletionLines: [],\n additionLines: [],\n conflictStack: [],\n conflictBuilders: [],\n actions: [],\n hunks: [],\n nextConflictIndex: 0,\n splitLineCount: 0,\n unifiedLineCount: 0,\n lastHunkEnd: 0,\n activeHunk: undefined,\n maxContextLines,\n maxContextLines2: maxContextLines * 2,\n };\n\n // Phase 1: Line-by-line scan. We inline the indexOf loop here (rather than\n // calling a helper with a callback) to avoid creating a closure on the hot\n // path. Each line is sliced and dispatched to processLine.\n const contents = file.contents;\n const contentLength = contents.length;\n if (contentLength > 0) {\n let lineStart = 0;\n let lineIndex = 0;\n let newlinePos = contents.indexOf('\\n', lineStart);\n while (newlinePos !== -1) {\n processLine(s, contents.slice(lineStart, newlinePos + 1), lineIndex);\n lineStart = newlinePos + 1;\n lineIndex++;\n newlinePos = contents.indexOf('\\n', lineStart);\n }\n if (lineStart < contentLength) {\n processLine(s, contents.slice(lineStart), lineIndex);\n }\n }\n\n // Phase 2: Post-loop cleanup. Any unclosed conflict is an error. If the\n // last hunk has buffered context lines, flush them as trailing context and\n // finalize the hunk.\n if (s.conflictStack.length > 0) {\n throw new Error(\n 'parseMergeConflictDiffFromFile: unfinished merge conflict marker stack'\n );\n }\n\n if (s.activeHunk != null && s.activeHunk.hunkContent.length > 0) {\n flushBufferedContext(s, s.activeHunk, 'trailing');\n finalizeActiveHunk(s);\n }\n\n for (\n let conflictIndex = 0;\n conflictIndex < s.conflictBuilders.length;\n conflictIndex++\n ) {\n const builder = s.conflictBuilders[conflictIndex];\n if (builder == null || !builder.completed) {\n throw new Error(\n `parseMergeConflictDiffFromFile: failed to build merge conflict action ${conflictIndex}`\n );\n }\n }\n\n // Phase 3: Result assembly. Account for any collapsed lines after the last\n // hunk, then join the line arrays to produce resolved file contents.\n if (\n s.hunks.length > 0 &&\n s.additionLines.length > 0 &&\n s.deletionLines.length > 0\n ) {\n const lastHunk = s.hunks[s.hunks.length - 1];\n const collapsedAfter = Math.max(\n s.additionLines.length -\n getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount),\n 0\n );\n s.splitLineCount += collapsedAfter;\n s.unifiedLineCount += collapsedAfter;\n }\n\n const currentContents = s.deletionLines.join('');\n const incomingContents = s.additionLines.join('');\n const currentFile = createResolvedConflictFile(\n file,\n 'current',\n currentContents\n );\n const incomingFile = createResolvedConflictFile(\n file,\n 'incoming',\n incomingContents\n );\n\n let type: FileDiffMetadata['type'] = 'change';\n if (incomingContents === '') {\n type = 'deleted';\n } else if (currentContents === '') {\n type = 'new';\n }\n\n const fileDiff: FileDiffMetadata = {\n name: file.name,\n prevName: undefined,\n type,\n hunks: s.hunks,\n splitLineCount: s.splitLineCount,\n unifiedLineCount: s.unifiedLineCount,\n isPartial: false,\n deletionLines: s.deletionLines,\n additionLines: s.additionLines,\n deletionCharacterCount: currentContents.length,\n additionCharacterCount: incomingContents.length,\n cacheKey:\n file.cacheKey != null\n ? `${file.cacheKey}:merge-conflict-diff`\n : undefined,\n };\n\n return {\n fileDiff,\n currentFile,\n incomingFile,\n actions: s.actions,\n markerRows: buildMergeConflictMarkerRows(fileDiff, s.actions),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Module-level parse helpers. Each receives ParseState by reference rather\n// than capturing variables via closure. The call graph from the hot path is:\n//\n// processLine\n// ├─ emitContextLine → ensureActiveHunk\n// ├─ emitChangeLine → ensureActiveHunk, splitHunkWithBufferedContext,\n// │ flushBufferedContext, appendChangeLine,\n// │ assignConflictContent\n// ├─ handleStartMarker\n// └─ finalizeConflict\n// ---------------------------------------------------------------------------\n\n// Routes a single source line to the right emitter based on whether we're\n// inside a conflict and, if so, which section (current/base/incoming).\n// Outside conflicts, only the start marker (<<<<<<< / charCode 60) can\n// change state, so we skip the full marker check for non-'<' lines.\nfunction processLine(s: ParseState, line: string, index: number): void {\n const frame = s.conflictStack[s.conflictStack.length - 1];\n\n // Outside any conflict: only start markers (<<<<<<<) can transition state.\n // Skip the full marker check for lines that can't be start markers.\n if (frame == null) {\n if (\n line.length >= 7 &&\n line.charCodeAt(0) === 60 &&\n getMergeConflictMarkerType(line) === 'start'\n ) {\n handleStartMarker(s, line, index);\n return;\n }\n emitContextLine(s, line);\n return;\n }\n\n // Inside a conflict: all marker types must be checked.\n const markerType = getMergeConflictMarkerType(line);\n\n if (markerType === 'start') {\n handleStartMarker(s, line, index);\n return;\n }\n\n if (markerType === 'base') {\n frame.stage = 'base';\n frame.baseMarkerLineIndex = index;\n frame.markerLines.base = line;\n return;\n }\n\n if (markerType === 'separator') {\n frame.stage = 'incoming';\n frame.separatorLineIndex = index;\n frame.markerLines.separator = line;\n return;\n }\n\n if (markerType === 'end') {\n const completedFrame = s.conflictStack.pop();\n if (completedFrame == null) {\n throw new Error(\n 'parseMergeConflictDiffFromFile: encountered end marker before start marker'\n );\n }\n finalizeConflict(s, completedFrame, index, line);\n return;\n }\n\n if (frame.stage === 'current') {\n emitChangeLine(s, 'deletion', line, frame.conflictIndex, 'current');\n } else if (frame.stage === 'base') {\n emitContextLine(s, line, frame.conflictIndex);\n } else {\n emitChangeLine(s, 'addition', line, frame.conflictIndex, 'incoming');\n }\n}\n\n// Lazily creates the active HunkBuilder if one doesn't exist yet. The hunk's\n// start positions are derived from the current length of the line arrays\n// (1-based, matching unified diff conventions).\nfunction ensureActiveHunk(s: ParseState): HunkBuilder {\n s.activeHunk ??= createHunkBuilder(\n s.additionLines.length + 1,\n s.deletionLines.length + 1\n );\n return s.activeHunk;\n}\n\n// \"Anchors\" a conflict to its position in the hunk's content array. Each\n// conflict needs to know which hunk it lives in (hunkIndex) and which content\n// entries correspond to its current/base/incoming sections. This is called\n// every time we emit a change or context line that belongs to a conflict, and\n// it incrementally widens the start/end content range.\nfunction assignConflictContent(\n s: ParseState,\n conflictIndex: number,\n role: MergeConflictSide,\n contentIndex: number\n): void {\n const builder = s.conflictBuilders[conflictIndex];\n if (builder == null) {\n throw new Error(\n `parseMergeConflictDiffFromFile: failed to locate conflict action ${conflictIndex}`\n );\n }\n\n const action = builder.action;\n const hunkIndex = s.hunks.length;\n if (action.hunkIndex < 0) {\n action.hunkIndex = hunkIndex;\n } else if (action.hunkIndex !== hunkIndex) {\n throw new Error(\n `parseMergeConflictDiffFromFile: conflict ${conflictIndex} spans multiple hunks and cannot be anchored`\n );\n }\n\n if (action.startContentIndex < 0) {\n action.startContentIndex = contentIndex;\n }\n action.endContentIndex = contentIndex;\n action.endMarkerContentIndex = contentIndex;\n\n if (role === 'current') {\n action.currentContentIndex ??= contentIndex;\n return;\n }\n if (role === 'base') {\n action.baseContentIndex ??= contentIndex;\n return;\n }\n action.incomingContentIndex = contentIndex;\n}\n\n// Appends a change line to the hunk's content array. If the previous entry is\n// already a 'change' group, we just bump its addition/deletion count instead\n// of creating a new entry — this keeps hunkContent compact. Returns the\n// content index so the caller can anchor the conflict to it.\nfunction appendChangeLine(\n hunk: HunkBuilder,\n lineType: 'addition' | 'deletion',\n additionLineIndex: number,\n deletionLineIndex: number\n): number {\n const hunkContent = hunk.hunkContent;\n const lastContent = hunkContent[hunkContent.length - 1];\n if (lastContent?.type === 'change') {\n if (lineType === 'addition') {\n lastContent.additions++;\n } else {\n lastContent.deletions++;\n }\n return hunkContent.length - 1;\n }\n hunkContent.push({\n type: 'change',\n additions: lineType === 'addition' ? 1 : 0,\n deletions: lineType === 'deletion' ? 1 : 0,\n additionLineIndex,\n deletionLineIndex,\n });\n return hunkContent.length - 1;\n}\n\n// Drains the hunk's context buffer into hunkContent, applying mode-dependent\n// trimming. The buffer accumulates context lines without committing them,\n// because we don't know yet whether they'll be leading context (trim start),\n// trailing context (trim end), or mid-hunk context (keep all). The mode tells\n// us which case we're in:\n//\n// 'leading' — first change in a new hunk; drop lines beyond\n// maxContextLines from the front, and shift the hunk's\n// start position forward accordingly.\n// 'trailing' — last flush before hunk finalization; keep at most\n// maxContextLines from the front of the buffer.\n// 'before-change' — mid-hunk context between two changes; emit everything.\nfunction flushBufferedContext(\n s: ParseState,\n hunk: HunkBuilder,\n mode: ContextFlushMode\n): void {\n let count = hunk.contextBufferCount;\n let addStart = hunk.contextBufferAdditionStart;\n let delStart = hunk.contextBufferDeletionStart;\n\n if (mode === 'leading' && count > s.maxContextLines) {\n const difference = count - s.maxContextLines;\n addStart += difference;\n delStart += difference;\n count = s.maxContextLines;\n hunk.additionStart += difference;\n hunk.deletionStart += difference;\n hunk.additionLineIndex += difference;\n hunk.deletionLineIndex += difference;\n }\n\n if (mode === 'trailing' && count > s.maxContextLines) {\n count = s.maxContextLines;\n }\n\n if (count === 0) {\n hunk.contextBufferCount = 0;\n hunk.contextBufferBaseConflicts = undefined;\n return;\n }\n\n // Bulk-append context: coalesce with previous context entry or create new\n // one. This avoids a per-line loop — significant when maxContextLines is\n // large.\n const hunkContent = hunk.hunkContent;\n const lastContent = hunkContent[hunkContent.length - 1];\n let contentIndex: number;\n if (lastContent?.type === 'context') {\n lastContent.lines += count;\n contentIndex = hunkContent.length - 1;\n } else {\n hunkContent.push({\n type: 'context',\n lines: count,\n additionLineIndex: addStart,\n deletionLineIndex: delStart,\n });\n contentIndex = hunkContent.length - 1;\n }\n hunk.additionCount += count;\n hunk.deletionCount += count;\n\n // Assign base-section conflict anchors (rare — only when base lines exist)\n const baseConflicts = hunk.contextBufferBaseConflicts;\n if (baseConflicts != null) {\n const bufferStartOffset = addStart - hunk.contextBufferAdditionStart;\n for (const [offset, conflictIndex] of baseConflicts) {\n if (offset >= bufferStartOffset && offset < bufferStartOffset + count) {\n assignConflictContent(s, conflictIndex, 'base', contentIndex);\n }\n }\n }\n hunk.contextBufferCount = 0;\n hunk.contextBufferBaseConflicts = undefined;\n}\n\n// Converts the mutable HunkBuilder into an immutable Hunk and pushes it onto\n// s.hunks. Computes line counts for split and unified view, the collapsed-\n// before gap (lines between the previous hunk and this one), and the hunk\n// header string (e.g. \"@@ -1,5 +1,7 @@\").\nfunction finalizeActiveHunk(s: ParseState): void {\n if (s.activeHunk == null) {\n return;\n }\n\n const hunk = s.activeHunk;\n s.activeHunk = undefined;\n if (hunk.hunkContent.length === 0) {\n return;\n }\n\n let hunkSplitLineCount = 0;\n let hunkUnifiedLineCount = 0;\n for (const content of hunk.hunkContent) {\n if (content.type === 'context') {\n hunkSplitLineCount += content.lines;\n hunkUnifiedLineCount += content.lines;\n } else {\n hunkSplitLineCount += Math.max(content.additions, content.deletions);\n hunkUnifiedLineCount += content.additions + content.deletions;\n }\n }\n\n // While building, each side's start is \"next line to consume\" (1-based). A\n // side that finished with zero lines must shift down to the unified `N,0`\n // convention (start names the line the hunk applies after), which is what\n // hunk headers, getHunkSideStartBoundary, and downstream boundary math all\n // assume — e.g. a whole-file deletion emits `+0,0`, not `+1,0`.\n const additionStart =\n hunk.additionCount === 0 ? hunk.additionStart - 1 : hunk.additionStart;\n const deletionStart =\n hunk.deletionCount === 0 ? hunk.deletionStart - 1 : hunk.deletionStart;\n\n const collapsedBefore = Math.max(\n getHunkSideStartBoundary(additionStart, hunk.additionCount) - s.lastHunkEnd,\n 0\n );\n const finalizedHunk: Hunk = {\n collapsedBefore,\n additionStart,\n additionCount: hunk.additionCount,\n additionLines: hunk.additionLines,\n additionLineIndex: hunk.additionLineIndex,\n deletionStart,\n deletionCount: hunk.deletionCount,\n deletionLines: hunk.deletionLines,\n deletionLineIndex: hunk.deletionLineIndex,\n hunkContent: hunk.hunkContent,\n hunkContext: undefined,\n hunkSpecs: `@@ -${formatHunkRange(deletionStart, hunk.deletionCount)} +${formatHunkRange(additionStart, hunk.additionCount)} @@\\n`,\n splitLineStart: s.splitLineCount + collapsedBefore,\n splitLineCount: hunkSplitLineCount,\n unifiedLineStart: s.unifiedLineCount + collapsedBefore,\n unifiedLineCount: hunkUnifiedLineCount,\n noEOFCRAdditions: false,\n noEOFCRDeletions: false,\n };\n\n s.hunks.push(finalizedHunk);\n s.splitLineCount += collapsedBefore + hunkSplitLineCount;\n s.unifiedLineCount += collapsedBefore + hunkUnifiedLineCount;\n s.lastHunkEnd = getHunkSideEndBoundary(additionStart, hunk.additionCount);\n}\n\n// Called when the context buffer between two changes exceeds maxContextLines*2.\n// This means there's a big enough gap to warrant splitting into separate hunks\n// (just like `diff -U` does). The procedure:\n// 1. Flush the first maxContextLines of the buffer as trailing context\n// 2. Finalize the current hunk\n// 3. Start a new hunk pre-seeded with the last maxContextLines as leading context\n// The middle portion of the buffer (between the two maxContextLines slices) is\n// the \"collapsed\" region — lines omitted from the diff view.\nfunction splitHunkWithBufferedContext(s: ParseState): void {\n if (s.activeHunk == null) {\n return;\n }\n\n const hunk = s.activeHunk;\n const count = hunk.contextBufferCount;\n const omittedContextLineCount = count - s.maxContextLines2;\n\n // Save trailing context start indices for the next hunk.\n const nextAddStart =\n hunk.contextBufferAdditionStart + count - s.maxContextLines;\n const nextDelStart =\n hunk.contextBufferDeletionStart + count - s.maxContextLines;\n\n // Extract base conflicts that fall within the trailing portion.\n let nextBaseConflicts: Map<number, number> | undefined;\n if (hunk.contextBufferBaseConflicts != null) {\n const tailOffset = count - s.maxContextLines;\n for (const [offset, ci] of hunk.contextBufferBaseConflicts) {\n if (offset >= tailOffset) {\n nextBaseConflicts ??= new Map();\n nextBaseConflicts.set(offset - tailOffset, ci);\n }\n }\n }\n\n flushBufferedContext(s, hunk, 'trailing');\n const emittedAdditionCount = hunk.additionCount;\n const emittedDeletionCount = hunk.deletionCount;\n finalizeActiveHunk(s);\n\n s.activeHunk = createHunkBuilder(\n hunk.additionStart + emittedAdditionCount + omittedContextLineCount,\n hunk.deletionStart + emittedDeletionCount + omittedContextLineCount\n );\n s.activeHunk.contextBufferAdditionStart = nextAddStart;\n s.activeHunk.contextBufferDeletionStart = nextDelStart;\n s.activeHunk.contextBufferCount = s.maxContextLines;\n s.activeHunk.contextBufferBaseConflicts = nextBaseConflicts;\n}\n\n// Adds a context line (identical on both sides of the diff). The line is pushed\n// to both additionLines and deletionLines, then buffered in the hunk's context\n// buffer rather than committed to hunkContent immediately. This deferred write\n// is what enables the leading/trailing trim logic in flushBufferedContext.\n//\n// For base-section lines inside a diff3 conflict, pass the conflict index so\n// the buffer can record the association; when the buffer is flushed, those\n// lines get anchored to the conflict via assignConflictContent.\nfunction emitContextLine(\n s: ParseState,\n line: string,\n baseConflictIndex: number = -1\n): void {\n const hunk = ensureActiveHunk(s);\n // Reset buffer start on first line after a flush/creation.\n if (hunk.contextBufferCount === 0) {\n hunk.contextBufferAdditionStart = s.additionLines.length;\n hunk.contextBufferDeletionStart = s.deletionLines.length;\n }\n s.additionLines.push(line);\n s.deletionLines.push(line);\n if (baseConflictIndex >= 0) {\n hunk.contextBufferBaseConflicts ??= new Map();\n hunk.contextBufferBaseConflicts.set(\n hunk.contextBufferCount,\n baseConflictIndex\n );\n }\n hunk.contextBufferCount++;\n}\n\n// Adds a change line (addition or deletion) to the current hunk. This is the\n// main \"work\" function on the hot path and orchestrates several steps:\n// 1. If there's a large context gap since the last change, split the hunk\n// 2. Flush any buffered context lines (leading trim on first change, or\n// pass-through for mid-hunk context)\n// 3. Push the line to the appropriate line array (additions or deletions)\n// 4. Append/coalesce the change into hunkContent\n// 5. Anchor the conflict action to the content index\nfunction emitChangeLine(\n s: ParseState,\n lineType: 'addition' | 'deletion',\n line: string,\n conflictIndex: number,\n role: MergeConflictSide\n): void {\n let hunk = ensureActiveHunk(s);\n // If the context gap since the last change exceeds 2x maxContextLines,\n // split into two hunks: trailing context for the old, leading for the new.\n if (\n hunk.hunkContent.length > 0 &&\n hunk.contextBufferCount > s.maxContextLines2\n ) {\n splitHunkWithBufferedContext(s);\n hunk = s.activeHunk!;\n }\n\n flushBufferedContext(\n s,\n hunk,\n hunk.hunkContent.length === 0 ? 'leading' : 'before-change'\n );\n\n const additionLineIndex = s.additionLines.length;\n const deletionLineIndex = s.deletionLines.length;\n if (lineType === 'addition') {\n s.additionLines.push(line);\n } else {\n s.deletionLines.push(line);\n }\n\n const contentIndex = appendChangeLine(\n hunk,\n lineType,\n additionLineIndex,\n deletionLineIndex\n );\n\n if (lineType === 'addition') {\n hunk.additionCount++;\n hunk.additionLines++;\n } else {\n hunk.deletionCount++;\n hunk.deletionLines++;\n }\n assignConflictContent(s, conflictIndex, role, contentIndex);\n}\n\n// Called when we hit a >>>>>>> end marker. Takes the completed ConflictFrame\n// and writes the final source-line coordinates and marker text into the\n// conflict action. Also handles empty-side conflicts: if one side had no\n// content lines, we fall back to the other side's content index so the action\n// always has valid anchors. This is what makes conflicts like \"add vs nothing\"\n// or \"nothing vs add\" representable.\nfunction finalizeConflict(\n s: ParseState,\n frame: ConflictFrame,\n endLineIndex: number,\n endMarkerLine: string\n): void {\n if (frame.separatorLineIndex == null || frame.markerLines.separator == null) {\n throw new Error(\n `parseMergeConflictDiffFromFile: conflict ${frame.conflictIndex} is missing a separator marker`\n );\n }\n\n const builder = s.conflictBuilders[frame.conflictIndex];\n if (builder == null) {\n throw new Error(\n `parseMergeConflictDiffFromFile: failed to finalize conflict ${frame.conflictIndex}`\n );\n }\n\n const action = builder.action;\n action.markerLines.separator = frame.markerLines.separator;\n action.markerLines.end = endMarkerLine;\n if (frame.markerLines.base != null) {\n action.markerLines.base = frame.markerLines.base;\n }\n\n action.conflict = {\n conflictIndex: frame.conflictIndex,\n startLineIndex: frame.startLineIndex,\n startLineNumber: frame.startLineIndex + 1,\n separatorLineIndex: frame.separatorLineIndex,\n separatorLineNumber: frame.separatorLineIndex + 1,\n endLineIndex,\n endLineNumber: endLineIndex + 1,\n baseMarkerLineIndex: frame.baseMarkerLineIndex,\n baseMarkerLineNumber:\n frame.baseMarkerLineIndex != null\n ? frame.baseMarkerLineIndex + 1\n : undefined,\n };\n\n // If one side of the conflict was empty (e.g. \"add vs nothing\"), its content\n // index will be undefined. Use the other side as a fallback so the action\n // always has a valid anchor for the UI to render.\n const fallbackContentIndex =\n action.currentContentIndex ?? action.incomingContentIndex;\n action.currentContentIndex ??= fallbackContentIndex;\n action.incomingContentIndex ??= fallbackContentIndex;\n if (action.startContentIndex < 0 && fallbackContentIndex != null) {\n action.startContentIndex = fallbackContentIndex;\n }\n if (action.endContentIndex < 0 && fallbackContentIndex != null) {\n action.endContentIndex = fallbackContentIndex;\n }\n if (action.endMarkerContentIndex < 0 && fallbackContentIndex != null) {\n action.endMarkerContentIndex = fallbackContentIndex;\n }\n\n if (\n action.hunkIndex < 0 ||\n action.startContentIndex < 0 ||\n action.endContentIndex < 0 ||\n action.endMarkerContentIndex < 0\n ) {\n throw new Error(\n `parseMergeConflictDiffFromFile: failed to anchor merge conflict ${frame.conflictIndex}`\n );\n }\n\n s.actions[action.conflictIndex] = action;\n builder.completed = true;\n}\n\n// Pushes a new ConflictFrame onto the stack and creates a placeholder\n// ConflictActionBuilder. The builder starts with sentinel values (-1 for\n// indices) that get filled in as we encounter content lines and markers.\n// The frame tracks which section we're scanning (current → base → incoming);\n// the builder accumulates the final action that downstream consumers use.\nfunction handleStartMarker(\n s: ParseState,\n line: string,\n lineIndex: number\n): void {\n const conflictIndex = s.nextConflictIndex;\n s.nextConflictIndex++;\n s.conflictStack.push({\n conflictIndex,\n stage: 'current',\n startLineIndex: lineIndex,\n markerLines: { start: line },\n });\n s.conflictBuilders[conflictIndex] = {\n completed: false,\n action: {\n conflict: {\n conflictIndex,\n startLineIndex: lineIndex,\n startLineNumber: lineIndex + 1,\n separatorLineIndex: lineIndex,\n separatorLineNumber: lineIndex + 1,\n endLineIndex: lineIndex,\n endLineNumber: lineIndex + 1,\n baseMarkerLineIndex: undefined,\n baseMarkerLineNumber: undefined,\n },\n conflictIndex,\n hunkIndex: -1,\n startContentIndex: -1,\n endContentIndex: -1,\n endMarkerContentIndex: -1,\n markerLines: {\n start: line,\n separator: '',\n end: '',\n },\n },\n };\n}\n\nfunction createHunkBuilder(\n additionStart: number,\n deletionStart: number\n): HunkBuilder {\n return {\n additionStart,\n deletionStart,\n additionCount: 0,\n deletionCount: 0,\n additionLines: 0,\n deletionLines: 0,\n additionLineIndex: Math.max(additionStart - 1, 0),\n deletionLineIndex: Math.max(deletionStart - 1, 0),\n hunkContent: [],\n contextBufferAdditionStart: Math.max(additionStart - 1, 0),\n contextBufferDeletionStart: Math.max(deletionStart - 1, 0),\n contextBufferCount: 0,\n contextBufferBaseConflicts: undefined,\n };\n}\n\nfunction formatHunkRange(start: number, count: number): string {\n return count === 1 ? `${start}` : `${start},${count}`;\n}\n\n// Detects whether a line is a merge conflict marker by inspecting the first\n// character and counting consecutive repetitions. Git conflict markers are\n// 7+ repeated characters:\n// '<' (60) = start (<<<<<<< current)\n// '|' (124) = base (||||||| base)\n// '=' (61) = separator (=======)\n// '>' (62) = end (>>>>>>> incoming)\n// The separator must be exactly '=======' with no trailing text; other markers\n// allow an optional space + label (e.g. \"<<<<<<< HEAD\").\nfunction getMergeConflictMarkerType(\n line: string\n): MergeConflictMarkerType | undefined {\n if (line.length < 7) {\n return undefined;\n }\n\n const markerCode = line.charCodeAt(0);\n if (\n markerCode !== 60 &&\n markerCode !== 62 &&\n markerCode !== 61 &&\n markerCode !== 124\n ) {\n return undefined;\n }\n\n const lineEnd = getLineContentEndIndex(line);\n if (lineEnd < 7) {\n return undefined;\n }\n\n let markerLength = 1;\n while (\n markerLength < lineEnd &&\n line.charCodeAt(markerLength) === markerCode\n ) {\n markerLength++;\n }\n\n if (markerLength < 7) {\n return undefined;\n }\n\n if (markerCode === 61) {\n return markerLength === lineEnd ? 'separator' : undefined;\n }\n\n if (\n markerLength !== lineEnd &&\n !isWhitespaceCode(line.charCodeAt(markerLength))\n ) {\n return undefined;\n }\n\n if (markerCode === 60) {\n return 'start';\n }\n if (markerCode === 62) {\n return 'end';\n }\n return 'base';\n}\n\nfunction getLineContentEndIndex(line: string): number {\n let end = line.length;\n if (end > 0 && line.charCodeAt(end - 1) === 10) {\n end--;\n }\n if (end > 0 && line.charCodeAt(end - 1) === 13) {\n end--;\n }\n return end;\n}\n\nfunction isWhitespaceCode(code: number): boolean {\n return (\n code === 9 ||\n code === 10 ||\n code === 11 ||\n code === 12 ||\n code === 13 ||\n code === 32\n );\n}\n\nfunction createResolvedConflictFile(\n file: FileContents,\n side: 'current' | 'incoming',\n contents: string\n): FileContents {\n return {\n ...file,\n contents,\n cacheKey:\n file.cacheKey != null\n ? `${file.cacheKey}:merge-conflict-${side}`\n : undefined,\n };\n}\n\n// Builds the marker row array that tells the UI where to render conflict\n// decorations (start/base/separator/end lines) in the diff view. Each marker\n// row maps a conflict marker to a specific line index in unified view.\n//\n// This is a post-processing step over the finalized hunks and actions. It\n// caches cumulative line-start positions per hunk to avoid recomputing them\n// for every marker.\nexport function buildMergeConflictMarkerRows(\n fileDiff: FileDiffMetadata,\n actions: (MergeConflictDiffAction | undefined)[]\n): MergeConflictMarkerRow[] {\n const markerRows: MergeConflictMarkerRow[] = [];\n const hunkLineStartCache: (number[] | undefined)[] = new Array(\n fileDiff.hunks.length\n );\n\n const getLineStart = (hunkIndex: number, contentIndex: number): number => {\n const hunk = fileDiff.hunks[hunkIndex];\n if (hunk == null) {\n return 0;\n }\n let starts = hunkLineStartCache[hunkIndex];\n if (starts == null) {\n starts = new Array<number>(hunk.hunkContent.length + 1);\n let lineIndex = hunk.unifiedLineStart;\n starts[0] = lineIndex;\n for (let index = 0; index < hunk.hunkContent.length; index++) {\n const content = hunk.hunkContent[index];\n lineIndex +=\n content.type === 'context'\n ? content.lines\n : content.deletions + content.additions;\n starts[index + 1] = lineIndex;\n }\n hunkLineStartCache[hunkIndex] = starts;\n }\n return starts[Math.max(contentIndex, 0)] ?? hunk.unifiedLineStart;\n };\n\n const getLineEnd = (hunkIndex: number, contentIndex: number): number => {\n const lineStart = getLineStart(hunkIndex, contentIndex);\n const starts = hunkLineStartCache[hunkIndex];\n const lineEndExclusive =\n starts?.[Math.max(contentIndex + 1, 0)] ??\n getLineStart(hunkIndex, contentIndex + 1);\n return Math.max(lineStart, lineEndExclusive - 1);\n };\n\n for (const action of actions) {\n if (action == null) {\n continue;\n }\n\n const hunk = fileDiff.hunks[action.hunkIndex];\n if (hunk == null) {\n continue;\n }\n\n const actionLineIndex = getLineStart(\n action.hunkIndex,\n action.startContentIndex\n );\n markerRows.push(\n createMergeConflictMarkerRow(\n action,\n 'marker-start',\n action.startContentIndex,\n action.markerLines.start,\n actionLineIndex\n )\n );\n\n if (action.baseContentIndex != null) {\n const currentContentIndex = action.currentContentIndex;\n const incomingContentIndex = action.incomingContentIndex;\n if (currentContentIndex == null || incomingContentIndex == null) {\n continue;\n }\n\n const baseMarkerLine = action.markerLines.base;\n if (baseMarkerLine == null) {\n continue;\n }\n\n const currentChange = hunk.hunkContent[currentContentIndex];\n const baseContext = hunk.hunkContent[action.baseContentIndex];\n const incomingChange = hunk.hunkContent[incomingContentIndex];\n if (\n currentChange?.type !== 'change' ||\n baseContext?.type !== 'context' ||\n incomingChange?.type !== 'change'\n ) {\n continue;\n }\n\n const currentStart = getLineStart(action.hunkIndex, currentContentIndex);\n const incomingStart = getLineStart(\n action.hunkIndex,\n incomingContentIndex\n );\n markerRows.push(\n createMergeConflictMarkerRow(\n action,\n 'marker-base',\n action.baseContentIndex,\n baseMarkerLine,\n currentStart + currentChange.deletions\n )\n );\n\n markerRows.push(\n createMergeConflictMarkerRow(\n action,\n 'marker-separator',\n action.baseContentIndex,\n action.markerLines.separator,\n incomingStart\n ),\n createMergeConflictMarkerRow(\n action,\n 'marker-end',\n action.endMarkerContentIndex,\n action.markerLines.end,\n getLineEnd(action.hunkIndex, action.endMarkerContentIndex)\n )\n );\n continue;\n }\n\n const currentContentIndex = action.currentContentIndex;\n if (currentContentIndex == null) {\n continue;\n }\n const content = hunk.hunkContent[currentContentIndex];\n if (content?.type !== 'change') {\n continue;\n }\n\n const contentStart = getLineStart(action.hunkIndex, currentContentIndex);\n const separatorLineIndex =\n content.deletions > 0\n ? contentStart + content.deletions\n : actionLineIndex;\n\n markerRows.push(\n createMergeConflictMarkerRow(\n action,\n 'marker-separator',\n currentContentIndex,\n action.markerLines.separator,\n separatorLineIndex\n ),\n createMergeConflictMarkerRow(\n action,\n 'marker-end',\n action.endMarkerContentIndex,\n action.markerLines.end,\n getLineEnd(action.hunkIndex, action.endMarkerContentIndex)\n )\n );\n }\n\n return markerRows;\n}\n\nfunction createMergeConflictMarkerRow(\n action: MergeConflictDiffAction,\n type: MergeConflictMarkerRowType,\n contentIndex: number,\n lineText: string,\n lineIndex: number\n): MergeConflictMarkerRow {\n return {\n type,\n hunkIndex: action.hunkIndex,\n contentIndex,\n conflictIndex: action.conflictIndex,\n lineText,\n lineIndex,\n };\n}\n\nfunction getUnifiedLineStartForContent(\n hunk: Hunk,\n contentIndex: number\n): number {\n let lineIndex = hunk.unifiedLineStart;\n for (let index = 0; index < contentIndex; index++) {\n const content = hunk.hunkContent[index];\n lineIndex +=\n content.type === 'context'\n ? content.lines\n : content.deletions + content.additions;\n }\n return lineIndex;\n}\n"],"mappings":"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"}