@pierre/diffs 1.2.6 → 1.3.0-beta.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 (165) hide show
  1. package/dist/components/CodeView.d.ts +0 -1
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +0 -23
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +7 -2
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +36 -2
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +8 -2
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +73 -1
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  14. package/dist/components/UnresolvedFile.js +2 -2
  15. package/dist/components/VirtualizedFile.d.ts +2 -1
  16. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  17. package/dist/components/VirtualizedFile.js +48 -48
  18. package/dist/components/VirtualizedFile.js.map +1 -1
  19. package/dist/components/VirtualizedFileDiff.js +42 -22
  20. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  21. package/dist/components/Virtualizer.d.ts +1 -1
  22. package/dist/components/Virtualizer.d.ts.map +1 -1
  23. package/dist/components/Virtualizer.js +3 -5
  24. package/dist/components/Virtualizer.js.map +1 -1
  25. package/dist/constants.d.ts.map +1 -1
  26. package/dist/editor/command.d.ts +6 -0
  27. package/dist/editor/command.d.ts.map +1 -0
  28. package/dist/editor/command.js +31 -0
  29. package/dist/editor/command.js.map +1 -0
  30. package/dist/editor/css.d.ts +6 -0
  31. package/dist/editor/css.d.ts.map +1 -0
  32. package/dist/editor/css.js +218 -0
  33. package/dist/editor/css.js.map +1 -0
  34. package/dist/editor/editStack.d.ts +66 -0
  35. package/dist/editor/editStack.d.ts.map +1 -0
  36. package/dist/editor/editStack.js +218 -0
  37. package/dist/editor/editStack.js.map +1 -0
  38. package/dist/editor/editor.d.ts +22 -0
  39. package/dist/editor/editor.d.ts.map +1 -0
  40. package/dist/editor/editor.js +1323 -0
  41. package/dist/editor/editor.js.map +1 -0
  42. package/dist/editor/index.d.ts +3 -0
  43. package/dist/editor/index.js +4 -0
  44. package/dist/editor/lineAnnotations.d.ts +8 -0
  45. package/dist/editor/lineAnnotations.d.ts.map +1 -0
  46. package/dist/editor/lineAnnotations.js +32 -0
  47. package/dist/editor/lineAnnotations.js.map +1 -0
  48. package/dist/editor/pieceTable.d.ts +33 -0
  49. package/dist/editor/pieceTable.d.ts.map +1 -0
  50. package/dist/editor/pieceTable.js +590 -0
  51. package/dist/editor/pieceTable.js.map +1 -0
  52. package/dist/editor/platform.d.ts +12 -0
  53. package/dist/editor/platform.d.ts.map +1 -0
  54. package/dist/editor/platform.js +44 -0
  55. package/dist/editor/platform.js.map +1 -0
  56. package/dist/editor/quickEdit.d.ts +29 -0
  57. package/dist/editor/quickEdit.d.ts.map +1 -0
  58. package/dist/editor/quickEdit.js +81 -0
  59. package/dist/editor/quickEdit.js.map +1 -0
  60. package/dist/editor/searchPanel.d.ts +30 -0
  61. package/dist/editor/searchPanel.d.ts.map +1 -0
  62. package/dist/editor/searchPanel.js +219 -0
  63. package/dist/editor/searchPanel.js.map +1 -0
  64. package/dist/editor/selection.d.ts +126 -0
  65. package/dist/editor/selection.d.ts.map +1 -0
  66. package/dist/editor/selection.js +900 -0
  67. package/dist/editor/selection.js.map +1 -0
  68. package/dist/editor/textDocument.d.ts +139 -0
  69. package/dist/editor/textDocument.d.ts.map +1 -0
  70. package/dist/editor/textDocument.js +202 -0
  71. package/dist/editor/textDocument.js.map +1 -0
  72. package/dist/editor/textMeasure.d.ts +32 -0
  73. package/dist/editor/textMeasure.d.ts.map +1 -0
  74. package/dist/editor/textMeasure.js +108 -0
  75. package/dist/editor/textMeasure.js.map +1 -0
  76. package/dist/editor/tokenzier.d.ts +37 -0
  77. package/dist/editor/tokenzier.d.ts.map +1 -0
  78. package/dist/editor/tokenzier.js +348 -0
  79. package/dist/editor/tokenzier.js.map +1 -0
  80. package/dist/editor/utils.d.ts +16 -0
  81. package/dist/editor/utils.d.ts.map +1 -0
  82. package/dist/editor/utils.js +37 -0
  83. package/dist/editor/utils.js.map +1 -0
  84. package/dist/index.d.ts +2 -2
  85. package/dist/index.js +2 -2
  86. package/dist/react/EditorContext.d.ts +16 -0
  87. package/dist/react/EditorContext.d.ts.map +1 -0
  88. package/dist/react/EditorContext.js +26 -0
  89. package/dist/react/EditorContext.js.map +1 -0
  90. package/dist/react/File.d.ts +2 -1
  91. package/dist/react/File.d.ts.map +1 -1
  92. package/dist/react/File.js +3 -2
  93. package/dist/react/File.js.map +1 -1
  94. package/dist/react/FileDiff.d.ts +3 -1
  95. package/dist/react/FileDiff.d.ts.map +1 -1
  96. package/dist/react/FileDiff.js +3 -2
  97. package/dist/react/FileDiff.js.map +1 -1
  98. package/dist/react/MultiFileDiff.d.ts +3 -1
  99. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  100. package/dist/react/MultiFileDiff.js +3 -2
  101. package/dist/react/MultiFileDiff.js.map +1 -1
  102. package/dist/react/PatchDiff.d.ts +3 -1
  103. package/dist/react/PatchDiff.d.ts.map +1 -1
  104. package/dist/react/PatchDiff.js +3 -2
  105. package/dist/react/PatchDiff.js.map +1 -1
  106. package/dist/react/index.d.ts +3 -2
  107. package/dist/react/index.js +2 -1
  108. package/dist/react/jsx.d.ts +0 -1
  109. package/dist/react/jsx.d.ts.map +1 -1
  110. package/dist/react/types.d.ts +1 -0
  111. package/dist/react/types.d.ts.map +1 -1
  112. package/dist/react/types.js +0 -1
  113. package/dist/react/utils/useFileDiffInstance.d.ts +3 -1
  114. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  115. package/dist/react/utils/useFileDiffInstance.js +31 -5
  116. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  117. package/dist/react/utils/useFileInstance.d.ts +4 -1
  118. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  119. package/dist/react/utils/useFileInstance.js +30 -5
  120. package/dist/react/utils/useFileInstance.js.map +1 -1
  121. package/dist/renderers/DiffHunksRenderer.d.ts +2 -2
  122. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  123. package/dist/renderers/DiffHunksRenderer.js +9 -5
  124. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  125. package/dist/renderers/FileRenderer.d.ts +5 -1
  126. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  127. package/dist/renderers/FileRenderer.js +108 -41
  128. package/dist/renderers/FileRenderer.js.map +1 -1
  129. package/dist/ssr/index.d.ts +2 -2
  130. package/dist/style.js +1 -1
  131. package/dist/style.js.map +1 -1
  132. package/dist/types.d.ts +45 -1
  133. package/dist/types.d.ts.map +1 -1
  134. package/dist/utils/cleanLastNewline.js +6 -1
  135. package/dist/utils/cleanLastNewline.js.map +1 -1
  136. package/dist/utils/computeEstimatedDiffHeights.js +20 -9
  137. package/dist/utils/computeEstimatedDiffHeights.js.map +1 -1
  138. package/dist/utils/computeFileOffsets.d.ts +13 -0
  139. package/dist/utils/computeFileOffsets.d.ts.map +1 -0
  140. package/dist/utils/computeFileOffsets.js +33 -0
  141. package/dist/utils/computeFileOffsets.js.map +1 -0
  142. package/dist/utils/createTransformerWithState.js +9 -0
  143. package/dist/utils/createTransformerWithState.js.map +1 -1
  144. package/dist/utils/iterateOverDiff.js +182 -147
  145. package/dist/utils/iterateOverDiff.js.map +1 -1
  146. package/dist/utils/renderDiffWithHighlighter.js +1 -1
  147. package/dist/utils/renderFileWithHighlighter.js +5 -14
  148. package/dist/utils/renderFileWithHighlighter.js.map +1 -1
  149. package/dist/utils/virtualDiffLayout.d.ts +2 -23
  150. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  151. package/dist/utils/virtualDiffLayout.js +1 -41
  152. package/dist/utils/virtualDiffLayout.js.map +1 -1
  153. package/dist/worker/WorkerPoolManager.js +1 -1
  154. package/dist/worker/{wasm-BaDzIkIn.js → wasm-D4DU5jgR.js} +2 -2
  155. package/dist/worker/wasm-D4DU5jgR.js.map +1 -0
  156. package/dist/worker/worker-portable.js +349 -363
  157. package/dist/worker/worker-portable.js.map +1 -1
  158. package/dist/worker/worker.js +222 -243
  159. package/dist/worker/worker.js.map +1 -1
  160. package/package.json +9 -1
  161. package/dist/utils/iterateOverFile.d.ts +0 -50
  162. package/dist/utils/iterateOverFile.d.ts.map +0 -1
  163. package/dist/utils/iterateOverFile.js +0 -49
  164. package/dist/utils/iterateOverFile.js.map +0 -1
  165. package/dist/worker/wasm-BaDzIkIn.js.map +0 -1
@@ -12,7 +12,6 @@ const DIFFS_DEVELOPMENT_BUILD = (() => {
12
12
  return false;
13
13
  }
14
14
  })();
15
- const SPLIT_WITH_NEWLINES = /(?<=\n)/;
16
15
  const DEFAULT_THEMES = {
17
16
  dark: "pierre-dark",
18
17
  light: "pierre-light"
@@ -454,7 +453,12 @@ function replaceCustomExtensions(version, map) {
454
453
  //#endregion
455
454
  //#region src/utils/cleanLastNewline.ts
456
455
  function cleanLastNewline(contents) {
457
- return contents.replace(/\n$|\r\n$/, "");
456
+ let end = contents.length;
457
+ if (contents.charCodeAt(end - 1) === 10) {
458
+ end--;
459
+ if (contents.charCodeAt(end - 1) === 13) end--;
460
+ }
461
+ return contents.slice(0, end);
458
462
  }
459
463
 
460
464
  //#endregion
@@ -631,6 +635,15 @@ function createTransformerWithState(useTokenTransformer = false, useCSSClasses =
631
635
  } : null
632
636
  }];
633
637
  if (useCSSClasses) transformers.push(tokenStyleNormalizer, toClass);
638
+ if (useTokenTransformer) transformers.push({ line: (node) => {
639
+ if (node.type === "element" && node.children.length === 0) node.children.push({
640
+ type: "element",
641
+ tagName: "br",
642
+ properties: {},
643
+ children: []
644
+ });
645
+ return node;
646
+ } });
634
647
  return {
635
648
  state,
636
649
  transformers,
@@ -741,39 +754,6 @@ function getExpandedRegion({ isPartial, rangeSize, expandedHunks, hunkIndex, col
741
754
  renderAll
742
755
  };
743
756
  }
744
- function getTrailingContextRangeSize({ fileDiff, errorPrefix }) {
745
- const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];
746
- if (lastHunk == null || fileDiff.isPartial || fileDiff.additionLines.length === 0 || fileDiff.deletionLines.length === 0) return 0;
747
- const additionRemaining = fileDiff.additionLines.length - (lastHunk.additionLineIndex + lastHunk.additionCount);
748
- const deletionRemaining = fileDiff.deletionLines.length - (lastHunk.deletionLineIndex + lastHunk.deletionCount);
749
- if (additionRemaining <= 0 && deletionRemaining <= 0) return 0;
750
- if (additionRemaining !== deletionRemaining) throw new Error(`${errorPrefix}: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${fileDiff.name}`);
751
- return Math.min(additionRemaining, deletionRemaining);
752
- }
753
- function getTrailingExpandedRegion({ fileDiff, hunkIndex, expandedHunks, collapsedContextThreshold, errorPrefix }) {
754
- if (hunkIndex !== fileDiff.hunks.length - 1) return;
755
- const trailingRangeSize = getTrailingContextRangeSize({
756
- fileDiff,
757
- errorPrefix
758
- });
759
- if (trailingRangeSize <= 0) return;
760
- if (expandedHunks === true || trailingRangeSize <= collapsedContextThreshold) return {
761
- fromStart: trailingRangeSize,
762
- fromEnd: 0,
763
- rangeSize: trailingRangeSize,
764
- collapsedLines: 0,
765
- renderAll: true
766
- };
767
- const region = expandedHunks?.get(fileDiff.hunks.length);
768
- const fromStart = Math.min(Math.max(region?.fromStart ?? 0, 0), trailingRangeSize);
769
- return {
770
- fromStart,
771
- fromEnd: 0,
772
- rangeSize: trailingRangeSize,
773
- collapsedLines: trailingRangeSize - fromStart,
774
- renderAll: fromStart >= trailingRangeSize
775
- };
776
- }
777
757
 
778
758
  //#endregion
779
759
  //#region src/utils/iterateOverDiff.ts
@@ -786,12 +766,12 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
786
766
  collapsedContextThreshold
787
767
  });
788
768
  const state = {
769
+ finalHunk: diff.hunks.at(-1),
789
770
  viewportStart: startingLine,
790
771
  viewportEnd: startingLine + totalLines,
791
772
  isWindowedHighlight: startingLine > 0 || totalLines < Infinity,
792
773
  splitCount: iterationStart.splitCount,
793
774
  unifiedCount: iterationStart.unifiedCount,
794
- finalHunkIndex: diff.hunks.length - 1,
795
775
  shouldBreak() {
796
776
  if (!state.isWindowedHighlight) return false;
797
777
  const breakUnified = state.unifiedCount >= startingLine + totalLines;
@@ -844,24 +824,31 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
844
824
  hunkIndex,
845
825
  collapsedContextThreshold
846
826
  });
847
- const trailingRegion = hunkIndex === state.finalHunkIndex ? getTrailingExpandedRegion({
848
- fileDiff: diff,
849
- hunkIndex,
850
- expandedHunks,
851
- collapsedContextThreshold,
852
- errorPrefix: "iterateOverDiff"
853
- }) : void 0;
827
+ const trailingRegion = (() => {
828
+ if (hunk !== state.finalHunk || !hasFinalCollapsedHunk(diff)) return;
829
+ const additionRemaining = diff.additionLines.length - (hunk.additionLineIndex + hunk.additionCount);
830
+ const deletionRemaining = diff.deletionLines.length - (hunk.deletionLineIndex + hunk.deletionCount);
831
+ if (additionRemaining !== deletionRemaining) throw new Error(`iterateOverDiff: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${diff.name}`);
832
+ const trailingRangeSize = Math.min(additionRemaining, deletionRemaining);
833
+ return getExpandedRegion({
834
+ isPartial: diff.isPartial,
835
+ rangeSize: trailingRangeSize,
836
+ expandedHunks,
837
+ hunkIndex: diff.hunks.length,
838
+ collapsedContextThreshold
839
+ });
840
+ })();
854
841
  const expandedLineCount = leadingRegion.fromStart + leadingRegion.fromEnd;
855
842
  function getTrailingCollapsedAfter(unifiedLineIndex$1, splitLineIndex$1) {
856
843
  if (trailingRegion == null || trailingRegion.collapsedLines <= 0 || trailingRegion.fromStart + trailingRegion.fromEnd > 0) return 0;
857
844
  if (diffStyle === "unified") return unifiedLineIndex$1 === hunk.unifiedLineStart + hunk.unifiedLineCount - 1 ? trailingRegion.collapsedLines : 0;
858
845
  return splitLineIndex$1 === hunk.splitLineStart + hunk.splitLineCount - 1 ? trailingRegion.collapsedLines : 0;
859
846
  }
860
- let consumedCollapsed = leadingRegion.collapsedLines === 0;
861
- function consumePendingCollapsed() {
862
- if (consumedCollapsed) return 0;
863
- consumedCollapsed = true;
864
- return leadingRegion.collapsedLines;
847
+ function getPendingCollapsed() {
848
+ if (leadingRegion.collapsedLines === 0) return 0;
849
+ const value = leadingRegion.collapsedLines;
850
+ leadingRegion.collapsedLines = 0;
851
+ return value;
865
852
  }
866
853
  if (!state.shouldSkip(expandedLineCount, expandedLineCount)) {
867
854
  let unifiedLineIndex$1 = hunk.unifiedLineStart - leadingRegion.rangeSize;
@@ -870,63 +857,81 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
870
857
  let additionLineIndex$1 = hunk.additionLineIndex - leadingRegion.rangeSize;
871
858
  let deletionLineNumber$1 = hunk.deletionStart - leadingRegion.rangeSize;
872
859
  let additionLineNumber$1 = hunk.additionStart - leadingRegion.rangeSize;
873
- if (walkContextLines(state, leadingRegion.fromStart, diffStyle, (index) => {
874
- return state.emit({
875
- hunkIndex,
876
- hunk,
877
- collapsedBefore: 0,
878
- collapsedAfter: 0,
879
- type: "context-expanded",
880
- deletionLine: {
881
- lineNumber: deletionLineNumber$1 + index,
882
- lineIndex: deletionLineIndex$1 + index,
883
- noEOFCR: false,
884
- unifiedLineIndex: unifiedLineIndex$1 + index,
885
- splitLineIndex: splitLineIndex$1 + index
886
- },
887
- additionLine: {
888
- unifiedLineIndex: unifiedLineIndex$1 + index,
889
- splitLineIndex: splitLineIndex$1 + index,
890
- lineIndex: additionLineIndex$1 + index,
891
- lineNumber: additionLineNumber$1 + index,
892
- noEOFCR: false
893
- }
894
- });
895
- })) break hunkIterator;
860
+ const [startIndex, endIndex] = getEqualLineIterationRange(state, leadingRegion.fromStart, diffStyle);
861
+ if (startIndex > 0) state.incrementCounts(startIndex, startIndex);
862
+ let index = startIndex;
863
+ while (index < leadingRegion.fromStart) {
864
+ if (index >= endIndex) {
865
+ state.incrementCounts(leadingRegion.fromStart - index, leadingRegion.fromStart - index);
866
+ break;
867
+ }
868
+ if (state.isInWindow(0, 0)) {
869
+ if (state.emit({
870
+ hunkIndex,
871
+ hunk,
872
+ collapsedBefore: 0,
873
+ collapsedAfter: 0,
874
+ type: "context-expanded",
875
+ deletionLine: {
876
+ lineNumber: deletionLineNumber$1 + index,
877
+ lineIndex: deletionLineIndex$1 + index,
878
+ noEOFCR: false,
879
+ unifiedLineIndex: unifiedLineIndex$1 + index,
880
+ splitLineIndex: splitLineIndex$1 + index
881
+ },
882
+ additionLine: {
883
+ unifiedLineIndex: unifiedLineIndex$1 + index,
884
+ splitLineIndex: splitLineIndex$1 + index,
885
+ lineIndex: additionLineIndex$1 + index,
886
+ lineNumber: additionLineNumber$1 + index,
887
+ noEOFCR: false
888
+ }
889
+ })) break hunkIterator;
890
+ } else state.incrementCounts(1, 1);
891
+ index++;
892
+ }
896
893
  unifiedLineIndex$1 = hunk.unifiedLineStart - leadingRegion.fromEnd;
897
894
  splitLineIndex$1 = hunk.splitLineStart - leadingRegion.fromEnd;
898
895
  deletionLineIndex$1 = hunk.deletionLineIndex - leadingRegion.fromEnd;
899
896
  additionLineIndex$1 = hunk.additionLineIndex - leadingRegion.fromEnd;
900
897
  deletionLineNumber$1 = hunk.deletionStart - leadingRegion.fromEnd;
901
898
  additionLineNumber$1 = hunk.additionStart - leadingRegion.fromEnd;
902
- if (walkContextLines(state, leadingRegion.fromEnd, diffStyle, (index) => {
903
- return state.emit({
904
- hunkIndex,
905
- hunk,
906
- collapsedBefore: consumePendingCollapsed(),
907
- collapsedAfter: 0,
908
- type: "context-expanded",
909
- deletionLine: {
910
- lineNumber: deletionLineNumber$1 + index,
911
- lineIndex: deletionLineIndex$1 + index,
912
- noEOFCR: false,
913
- unifiedLineIndex: unifiedLineIndex$1 + index,
914
- splitLineIndex: splitLineIndex$1 + index
915
- },
916
- additionLine: {
917
- unifiedLineIndex: unifiedLineIndex$1 + index,
918
- splitLineIndex: splitLineIndex$1 + index,
919
- lineIndex: additionLineIndex$1 + index,
920
- lineNumber: additionLineNumber$1 + index,
921
- noEOFCR: false
922
- }
923
- });
924
- }, () => {
925
- consumePendingCollapsed();
926
- })) break hunkIterator;
899
+ const [fromEndStartIndex, fromEndEndIndex] = getEqualLineIterationRange(state, leadingRegion.fromEnd, diffStyle);
900
+ if (fromEndStartIndex > 0) state.incrementCounts(fromEndStartIndex, fromEndStartIndex);
901
+ index = fromEndStartIndex;
902
+ while (index < leadingRegion.fromEnd) {
903
+ if (index >= fromEndEndIndex) {
904
+ state.incrementCounts(leadingRegion.fromEnd - index, leadingRegion.fromEnd - index);
905
+ break;
906
+ }
907
+ if (state.isInWindow(0, 0)) {
908
+ if (state.emit({
909
+ hunkIndex,
910
+ hunk,
911
+ collapsedBefore: getPendingCollapsed(),
912
+ collapsedAfter: 0,
913
+ type: "context-expanded",
914
+ deletionLine: {
915
+ lineNumber: deletionLineNumber$1 + index,
916
+ lineIndex: deletionLineIndex$1 + index,
917
+ noEOFCR: false,
918
+ unifiedLineIndex: unifiedLineIndex$1 + index,
919
+ splitLineIndex: splitLineIndex$1 + index
920
+ },
921
+ additionLine: {
922
+ unifiedLineIndex: unifiedLineIndex$1 + index,
923
+ splitLineIndex: splitLineIndex$1 + index,
924
+ lineIndex: additionLineIndex$1 + index,
925
+ lineNumber: additionLineNumber$1 + index,
926
+ noEOFCR: false
927
+ }
928
+ })) break hunkIterator;
929
+ } else state.incrementCounts(1, 1);
930
+ index++;
931
+ }
927
932
  } else {
928
933
  state.incrementCounts(expandedLineCount, expandedLineCount);
929
- consumePendingCollapsed();
934
+ getPendingCollapsed();
930
935
  }
931
936
  let unifiedLineIndex = hunk.unifiedLineStart;
932
937
  let splitLineIndex = hunk.splitLineStart;
@@ -940,37 +945,45 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
940
945
  const isLastContent = content === lastContent;
941
946
  if (content.type === "context") {
942
947
  if (!state.shouldSkip(content.lines, content.lines)) {
943
- if (walkContextLines(state, content.lines, diffStyle, (index) => {
944
- const isLastLine = isLastContent && index === content.lines - 1;
945
- const unifiedRowIndex = unifiedLineIndex + index;
946
- const splitRowIndex = splitLineIndex + index;
947
- return state.emit({
948
- hunkIndex,
949
- hunk,
950
- collapsedBefore: consumePendingCollapsed(),
951
- collapsedAfter: getTrailingCollapsedAfter(unifiedRowIndex, splitRowIndex),
952
- type: "context",
953
- deletionLine: {
954
- lineNumber: deletionLineNumber + index,
955
- lineIndex: deletionLineIndex + index,
956
- noEOFCR: isLastLine && hunk.noEOFCRDeletions,
957
- unifiedLineIndex: unifiedRowIndex,
958
- splitLineIndex: splitRowIndex
959
- },
960
- additionLine: {
961
- unifiedLineIndex: unifiedRowIndex,
962
- splitLineIndex: splitRowIndex,
963
- lineIndex: additionLineIndex + index,
964
- lineNumber: additionLineNumber + index,
965
- noEOFCR: isLastLine && hunk.noEOFCRAdditions
966
- }
967
- });
968
- }, () => {
969
- consumePendingCollapsed();
970
- })) break hunkIterator;
948
+ const [startIndex, endIndex] = getEqualLineIterationRange(state, content.lines, diffStyle);
949
+ if (startIndex > 0) state.incrementCounts(startIndex, startIndex);
950
+ let index = startIndex;
951
+ while (index < content.lines) {
952
+ if (index >= endIndex) {
953
+ state.incrementCounts(content.lines - index, content.lines - index);
954
+ break;
955
+ }
956
+ if (state.isInWindow(0, 0)) {
957
+ const isLastLine = isLastContent && index === content.lines - 1;
958
+ const unifiedRowIndex = unifiedLineIndex + index;
959
+ const splitRowIndex = splitLineIndex + index;
960
+ if (state.emit({
961
+ hunkIndex,
962
+ hunk,
963
+ collapsedBefore: getPendingCollapsed(),
964
+ collapsedAfter: getTrailingCollapsedAfter(unifiedRowIndex, splitRowIndex),
965
+ type: "context",
966
+ deletionLine: {
967
+ lineNumber: deletionLineNumber + index,
968
+ lineIndex: deletionLineIndex + index,
969
+ noEOFCR: isLastLine && hunk.noEOFCRDeletions,
970
+ unifiedLineIndex: unifiedRowIndex,
971
+ splitLineIndex: splitRowIndex
972
+ },
973
+ additionLine: {
974
+ unifiedLineIndex: unifiedRowIndex,
975
+ splitLineIndex: splitRowIndex,
976
+ lineIndex: additionLineIndex + index,
977
+ lineNumber: additionLineNumber + index,
978
+ noEOFCR: isLastLine && hunk.noEOFCRAdditions
979
+ }
980
+ })) break hunkIterator;
981
+ } else state.incrementCounts(1, 1);
982
+ index++;
983
+ }
971
984
  } else {
972
985
  state.incrementCounts(content.lines, content.lines);
973
- consumePendingCollapsed();
986
+ getPendingCollapsed();
974
987
  }
975
988
  unifiedLineIndex += content.lines;
976
989
  splitLineIndex += content.lines;
@@ -983,13 +996,12 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
983
996
  const unifiedCount = content.deletions + content.additions;
984
997
  if (!state.shouldSkip(unifiedCount, splitCount)) {
985
998
  const iterationRanges = getChangeIterationRanges(state, content, diffStyle);
986
- if ((iterationRanges[0]?.[0] ?? 0) > 0) consumePendingCollapsed();
987
999
  for (const [rangeStart, rangeEnd] of iterationRanges) for (let index = rangeStart; index < rangeEnd; index++) {
988
1000
  const collapsedAfter = getTrailingCollapsedAfter(unifiedLineIndex + index, diffStyle === "unified" ? splitLineIndex + (index < content.deletions ? index : index - content.deletions) : splitLineIndex + index);
989
1001
  if (state.emit(getChangeLineData({
990
1002
  hunkIndex,
991
1003
  hunk,
992
- collapsedBefore: consumePendingCollapsed(),
1004
+ collapsedBefore: getPendingCollapsed(),
993
1005
  collapsedAfter,
994
1006
  diffStyle,
995
1007
  index,
@@ -1006,7 +1018,7 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
1006
1018
  }), true)) break hunkIterator;
1007
1019
  }
1008
1020
  }
1009
- consumePendingCollapsed();
1021
+ getPendingCollapsed();
1010
1022
  state.incrementCounts(unifiedCount, splitCount);
1011
1023
  unifiedLineIndex += unifiedCount;
1012
1024
  splitLineIndex += splitCount;
@@ -1019,30 +1031,41 @@ function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infin
1019
1031
  if (trailingRegion != null) {
1020
1032
  const { collapsedLines, fromStart, fromEnd } = trailingRegion;
1021
1033
  const len = fromStart + fromEnd;
1022
- if (walkContextLines(state, len, diffStyle, (index) => {
1023
- const isLastLine = index === len - 1;
1024
- return state.emit({
1025
- hunkIndex: diff.hunks.length,
1026
- hunk: void 0,
1027
- collapsedBefore: 0,
1028
- collapsedAfter: isLastLine ? collapsedLines : 0,
1029
- type: "context-expanded",
1030
- deletionLine: {
1031
- lineNumber: deletionLineNumber + index,
1032
- lineIndex: deletionLineIndex + index,
1033
- noEOFCR: false,
1034
- unifiedLineIndex: unifiedLineIndex + index,
1035
- splitLineIndex: splitLineIndex + index
1036
- },
1037
- additionLine: {
1038
- unifiedLineIndex: unifiedLineIndex + index,
1039
- splitLineIndex: splitLineIndex + index,
1040
- lineIndex: additionLineIndex + index,
1041
- lineNumber: additionLineNumber + index,
1042
- noEOFCR: false
1043
- }
1044
- });
1045
- }, void 0, () => state.shouldBreak())) break hunkIterator;
1034
+ const [startIndex, endIndex] = getEqualLineIterationRange(state, len, diffStyle);
1035
+ if (startIndex > 0) state.incrementCounts(startIndex, startIndex);
1036
+ let index = startIndex;
1037
+ while (index < len) {
1038
+ if (state.shouldBreak()) break hunkIterator;
1039
+ if (index >= endIndex) {
1040
+ state.incrementCounts(len - index, len - index);
1041
+ break;
1042
+ }
1043
+ if (state.isInWindow(0, 0)) {
1044
+ const isLastLine = index === len - 1;
1045
+ if (state.emit({
1046
+ hunkIndex: diff.hunks.length,
1047
+ hunk: void 0,
1048
+ collapsedBefore: 0,
1049
+ collapsedAfter: isLastLine ? collapsedLines : 0,
1050
+ type: "context-expanded",
1051
+ deletionLine: {
1052
+ lineNumber: deletionLineNumber + index,
1053
+ lineIndex: deletionLineIndex + index,
1054
+ noEOFCR: false,
1055
+ unifiedLineIndex: unifiedLineIndex + index,
1056
+ splitLineIndex: splitLineIndex + index
1057
+ },
1058
+ additionLine: {
1059
+ unifiedLineIndex: unifiedLineIndex + index,
1060
+ splitLineIndex: splitLineIndex + index,
1061
+ lineIndex: additionLineIndex + index,
1062
+ lineNumber: additionLineNumber + index,
1063
+ noEOFCR: false
1064
+ }
1065
+ })) break hunkIterator;
1066
+ } else state.incrementCounts(1, 1);
1067
+ index++;
1068
+ }
1046
1069
  }
1047
1070
  }
1048
1071
  }
@@ -1107,14 +1130,15 @@ function getHunkPrefixCounts({ diff, expandedHunks, collapsedContextThreshold })
1107
1130
  const leadingCount = leadingRegion.fromStart + leadingRegion.fromEnd;
1108
1131
  splitCount += leadingCount + hunk.splitLineCount;
1109
1132
  unifiedCount += leadingCount + hunk.unifiedLineCount;
1110
- const trailingRegion = index === finalHunkIndex ? getTrailingExpandedRegion({
1111
- fileDiff: diff,
1112
- hunkIndex: index,
1113
- expandedHunks,
1114
- collapsedContextThreshold,
1115
- errorPrefix: "iterateOverDiff"
1116
- }) : void 0;
1117
- if (trailingRegion != null) {
1133
+ if (index === finalHunkIndex && hasFinalCollapsedHunk(diff)) {
1134
+ const trailingRangeSize = getTrailingRangeSize(diff, hunk);
1135
+ const trailingRegion = getExpandedRegion({
1136
+ isPartial: diff.isPartial,
1137
+ rangeSize: trailingRangeSize,
1138
+ expandedHunks,
1139
+ hunkIndex: diff.hunks.length,
1140
+ collapsedContextThreshold
1141
+ });
1118
1142
  const trailingCount = trailingRegion.fromStart + trailingRegion.fromEnd;
1119
1143
  splitCount += trailingCount;
1120
1144
  unifiedCount += trailingCount;
@@ -1126,7 +1150,7 @@ function getHunkPrefixCounts({ diff, expandedHunks, collapsedContextThreshold })
1126
1150
  }
1127
1151
  return prefixCounts;
1128
1152
  }
1129
- function getContextLineIterationBounds(state, count, diffStyle) {
1153
+ function getEqualLineIterationRange(state, count, diffStyle) {
1130
1154
  if (!state.isWindowedHighlight || count <= 0) return [0, count];
1131
1155
  const ranges = [];
1132
1156
  function pushRange(currentCount) {
@@ -1146,25 +1170,16 @@ function getContextLineIterationBounds(state, count, diffStyle) {
1146
1170
  }
1147
1171
  return [start, end];
1148
1172
  }
1149
- function walkContextLines(state, count, diffStyle, callback, onSkippedStart, shouldBreak) {
1150
- const [startIndex, endIndex] = getContextLineIterationBounds(state, count, diffStyle);
1151
- if (startIndex > 0) {
1152
- state.incrementCounts(startIndex, startIndex);
1153
- onSkippedStart?.();
1154
- }
1155
- let index = startIndex;
1156
- while (index < count) {
1157
- if (shouldBreak?.() === true) return true;
1158
- if (index >= endIndex) {
1159
- state.incrementCounts(count - index, count - index);
1160
- break;
1161
- }
1162
- if (state.isInWindow(0, 0)) {
1163
- if (callback(index) === true) return true;
1164
- } else state.incrementCounts(1, 1);
1165
- index++;
1166
- }
1167
- return false;
1173
+ function getTrailingRangeSize(diff, hunk) {
1174
+ const additionRemaining = diff.additionLines.length - (hunk.additionLineIndex + hunk.additionCount);
1175
+ const deletionRemaining = diff.deletionLines.length - (hunk.deletionLineIndex + hunk.deletionCount);
1176
+ if (additionRemaining !== deletionRemaining) throw new Error(`iterateOverDiff: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${diff.name}`);
1177
+ return Math.min(additionRemaining, deletionRemaining);
1178
+ }
1179
+ function hasFinalCollapsedHunk(diff) {
1180
+ const lastHunk = diff.hunks.at(-1);
1181
+ if (lastHunk == null || diff.isPartial || diff.additionLines.length === 0 || diff.deletionLines.length === 0) return false;
1182
+ return lastHunk.additionLineIndex + lastHunk.additionCount < diff.additionLines.length || lastHunk.deletionLineIndex + lastHunk.deletionCount < diff.deletionLines.length;
1168
1183
  }
1169
1184
  function getChangeIterationRanges(state, content, diffStyle) {
1170
1185
  if (!state.isWindowedHighlight) return [[0, diffStyle === "unified" ? content.deletions + content.additions : Math.max(content.deletions, content.additions)]];
@@ -1540,63 +1555,34 @@ function renderTwoFiles({ deletionFile, additionFile, deletionInfo, additionInfo
1540
1555
  }
1541
1556
 
1542
1557
  //#endregion
1543
- //#region src/utils/iterateOverFile.ts
1558
+ //#region src/utils/computeFileOffsets.ts
1559
+ const LINE_FEED = 10;
1560
+ const CARRIAGE_RETURN = 13;
1544
1561
  /**
1545
- * Iterates over lines in a file with optional windowing support.
1546
- *
1547
- * Similar to `iterateOverDiff` but simplified for linear file content.
1548
- * Supports viewport windowing for virtualization scenarios.
1549
- *
1550
- * @param props - Configuration for iteration
1551
- * @param props.lines - Pre-split array of lines (use splitFileContents() to create from string)
1552
- * @param props.startingLine - Optional starting line index (0-based, default: 0)
1553
- * @param props.totalLines - Optional max lines to iterate (default: Infinity)
1554
- * @param props.callback - Callback invoked for each line in the window.
1555
- * Return `true` to stop iteration early.
1556
- *
1557
- * @example
1558
- * ```typescript
1559
- * const lines = splitFileContents('line1\nline2\nline3');
1560
- * iterateOverFile({
1561
- * lines,
1562
- * startingLine: 0,
1563
- * totalLines: 10,
1564
- * callback: ({ lineIndex, lineNumber, content, isLastLine }) => {
1565
- * console.log(`Line ${lineNumber}: ${content}`);
1566
- * if (content.includes('stop')) return true; // Stop iteration
1567
- * }
1568
- * });
1569
- * ```
1562
+ * Computes line start offsets for a string.
1570
1563
  */
1571
- function iterateOverFile({ lines, startingLine = 0, totalLines = Infinity, callback }) {
1572
- const len = Math.min(startingLine + totalLines, lines.length);
1573
- const lastLineIndex = (() => {
1574
- const lastLine = lines.at(-1);
1575
- if (lastLine === "" || lastLine === "\n" || lastLine === "\r\n" || lastLine === "\r") return Math.max(0, lines.length - 2);
1576
- return lines.length - 1;
1577
- })();
1578
- for (let lineIndex = startingLine; lineIndex < len; lineIndex++) {
1579
- const isLastLine = lineIndex === lastLineIndex;
1580
- if (callback({
1581
- lineIndex,
1582
- lineNumber: lineIndex + 1,
1583
- content: lines[lineIndex],
1584
- isLastLine
1585
- }) === true || isLastLine) break;
1564
+ function computeLineOffsets(contents) {
1565
+ const offsets = [0];
1566
+ for (let i = 0; i < contents.length; i++) {
1567
+ const char = contents.charCodeAt(i);
1568
+ if (char === LINE_FEED || char === CARRIAGE_RETURN) {
1569
+ if (char === CARRIAGE_RETURN && i + 1 < contents.length && contents.charCodeAt(i + 1) === LINE_FEED) i++;
1570
+ offsets.push(i + 1);
1571
+ }
1586
1572
  }
1573
+ return offsets;
1587
1574
  }
1588
-
1589
- //#endregion
1590
- //#region src/utils/splitFileContents.ts
1591
1575
  /**
1592
- * Splits file contents into lines using the same logic as diff parsing.
1593
- * - Preserves trailing newlines on each line
1594
- *
1595
- * @param contents - The raw file contents string
1596
- * @returns Array of lines with newlines preserved
1576
+ * Splits file contents into lines aligned with {@link computeLineOffsets}.
1577
+ * Unlike splitFileContents, a trailing newline produces a final empty line.
1597
1578
  */
1598
- function splitFileContents(contents) {
1599
- return contents !== "" ? contents.split(SPLIT_WITH_NEWLINES) : [];
1579
+ function linesFromFileContents(contents) {
1580
+ const offsets = computeLineOffsets(contents);
1581
+ return Array.from({ length: offsets.length }, (_, i) => {
1582
+ const start = offsets[i];
1583
+ const end = offsets[i + 1] ?? contents.length;
1584
+ return contents.slice(start, end);
1585
+ });
1600
1586
  }
1601
1587
 
1602
1588
  //#endregion
@@ -1641,7 +1627,7 @@ function renderFileWithHighlighter(file, highlighter$1, { theme = DEFAULT_THEMES
1641
1627
  tokenizeMaxLineLength
1642
1628
  };
1643
1629
  })();
1644
- const highlightedLines = getLineNodes(highlighter$1.codeToHast(isWindowedHighlight ? extractWindowedFileContent(lines ?? splitFileContents(file.contents), startingLine, totalLines) : cleanLastNewline(file.contents), hastConfig));
1630
+ const highlightedLines = getLineNodes(highlighter$1.codeToHast(isWindowedHighlight ? extractWindowedFileContent(lines ?? linesFromFileContents(file.contents), startingLine, totalLines) : file.contents, hastConfig));
1645
1631
  const code = isWindowedHighlight ? new Array(startingLine) : highlightedLines;
1646
1632
  if (isWindowedHighlight) code.push(...highlightedLines);
1647
1633
  return {
@@ -1651,16 +1637,9 @@ function renderFileWithHighlighter(file, highlighter$1, { theme = DEFAULT_THEMES
1651
1637
  };
1652
1638
  }
1653
1639
  function extractWindowedFileContent(lines, startingLine, totalLines) {
1654
- let windowContent = "";
1655
- iterateOverFile({
1656
- lines,
1657
- startingLine,
1658
- totalLines,
1659
- callback({ content }) {
1660
- windowContent += content;
1661
- }
1662
- });
1663
- return windowContent;
1640
+ if (lines.length === 0) return "";
1641
+ const endLine = Math.min(startingLine + totalLines, lines.length);
1642
+ return lines.slice(startingLine, endLine).join("");
1664
1643
  }
1665
1644
 
1666
1645
  //#endregion