@pierre/diffs 1.3.0-beta.9 → 1.3.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/components/CodeView.d.ts +12 -13
- package/dist/components/CodeView.d.ts.map +1 -1
- package/dist/components/CodeView.js +50 -58
- package/dist/components/CodeView.js.map +1 -1
- package/dist/components/File.d.ts +10 -3
- package/dist/components/File.d.ts.map +1 -1
- package/dist/components/File.js +41 -10
- package/dist/components/File.js.map +1 -1
- package/dist/components/FileDiff.d.ts +37 -6
- package/dist/components/FileDiff.d.ts.map +1 -1
- package/dist/components/FileDiff.js +202 -19
- package/dist/components/FileDiff.js.map +1 -1
- package/dist/components/UnresolvedFile.d.ts.map +1 -1
- package/dist/components/UnresolvedFile.js +7 -2
- package/dist/components/UnresolvedFile.js.map +1 -1
- package/dist/components/VirtualizedFile.d.ts +2 -1
- package/dist/components/VirtualizedFile.d.ts.map +1 -1
- package/dist/components/VirtualizedFile.js +9 -5
- package/dist/components/VirtualizedFile.js.map +1 -1
- package/dist/components/VirtualizedFileDiff.d.ts +14 -0
- package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
- package/dist/components/VirtualizedFileDiff.js +61 -3
- package/dist/components/VirtualizedFileDiff.js.map +1 -1
- package/dist/components/Virtualizer.d.ts +14 -2
- package/dist/components/Virtualizer.d.ts.map +1 -1
- package/dist/components/Virtualizer.js +27 -2
- package/dist/components/Virtualizer.js.map +1 -1
- package/dist/edit/index.d.ts +5 -0
- package/dist/edit/index.js +3 -0
- package/dist/editor/command.d.ts +1 -1
- package/dist/editor/command.d.ts.map +1 -1
- package/dist/editor/command.js +11 -0
- package/dist/editor/command.js.map +1 -1
- package/dist/editor/editStack.d.ts +6 -2
- package/dist/editor/editStack.d.ts.map +1 -1
- package/dist/editor/editStack.js +53 -12
- package/dist/editor/editStack.js.map +1 -1
- package/dist/editor/editor.d.ts +51 -23
- package/dist/editor/editor.d.ts.map +1 -1
- package/dist/editor/editor.js +977 -241
- package/dist/editor/editor.js.map +1 -1
- package/dist/editor/editor2.js +1 -1
- package/dist/editor/languages.d.ts +26 -0
- package/dist/editor/languages.d.ts.map +1 -0
- package/dist/editor/languages.js +363 -0
- package/dist/editor/languages.js.map +1 -0
- package/dist/editor/lineAnnotations.d.ts.map +1 -1
- package/dist/editor/lineAnnotations.js +25 -26
- package/dist/editor/lineAnnotations.js.map +1 -1
- package/dist/editor/marker.d.ts +5 -3
- package/dist/editor/marker.d.ts.map +1 -1
- package/dist/editor/marker.js +111 -91
- package/dist/editor/marker.js.map +1 -1
- package/dist/editor/matchBrackets.d.ts +3 -2
- package/dist/editor/matchBrackets.d.ts.map +1 -1
- package/dist/editor/matchBrackets.js +2 -10
- package/dist/editor/matchBrackets.js.map +1 -1
- package/dist/editor/pieceTable.d.ts +2 -1
- package/dist/editor/pieceTable.d.ts.map +1 -1
- package/dist/editor/pieceTable.js +103 -39
- package/dist/editor/pieceTable.js.map +1 -1
- package/dist/editor/platform.js +2 -0
- package/dist/editor/platform.js.map +1 -1
- package/dist/editor/popover.d.ts +25 -5
- package/dist/editor/popover.d.ts.map +1 -1
- package/dist/editor/popover.js +60 -14
- package/dist/editor/popover.js.map +1 -1
- package/dist/editor/searchPanel.d.ts.map +1 -1
- package/dist/editor/searchPanel.js +9 -2
- package/dist/editor/searchPanel.js.map +1 -1
- package/dist/editor/selection.d.ts +31 -11
- package/dist/editor/selection.d.ts.map +1 -1
- package/dist/editor/selection.js +198 -70
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/selectionAction.d.ts +4 -3
- package/dist/editor/selectionAction.d.ts.map +1 -1
- package/dist/editor/selectionAction.js +9 -5
- package/dist/editor/selectionAction.js.map +1 -1
- package/dist/editor/stateStorage.d.ts +15 -0
- package/dist/editor/stateStorage.d.ts.map +1 -0
- package/dist/editor/stateStorage.js +65 -0
- package/dist/editor/stateStorage.js.map +1 -0
- package/dist/editor/textDocument.d.ts +12 -77
- package/dist/editor/textDocument.d.ts.map +1 -1
- package/dist/editor/textDocument.js +76 -34
- package/dist/editor/textDocument.js.map +1 -1
- package/dist/editor/textMeasure.d.ts +7 -12
- package/dist/editor/textMeasure.d.ts.map +1 -1
- package/dist/editor/textMeasure.js +40 -32
- package/dist/editor/textMeasure.js.map +1 -1
- package/dist/editor/{tokenzier.d.ts → tokenizer.d.ts} +3 -3
- package/dist/editor/tokenizer.d.ts.map +1 -0
- package/dist/editor/{tokenzier.js → tokenizer.js} +170 -50
- package/dist/editor/tokenizer.js.map +1 -0
- package/dist/editor/utils.js +1 -1
- package/dist/editor/utils.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +6 -5
- package/dist/managers/InteractionManager.d.ts +18 -7
- package/dist/managers/InteractionManager.d.ts.map +1 -1
- package/dist/managers/InteractionManager.js +154 -99
- package/dist/managers/InteractionManager.js.map +1 -1
- package/dist/managers/UniversalRenderingManager.d.ts.map +1 -1
- package/dist/managers/UniversalRenderingManager.js +1 -2
- package/dist/managers/UniversalRenderingManager.js.map +1 -1
- package/dist/react/CodeView.d.ts +13 -13
- package/dist/react/CodeView.d.ts.map +1 -1
- package/dist/react/CodeView.js +37 -28
- package/dist/react/CodeView.js.map +1 -1
- package/dist/react/EditContext.d.ts +20 -0
- package/dist/react/EditContext.d.ts.map +1 -0
- package/dist/react/EditContext.js +20 -0
- package/dist/react/EditContext.js.map +1 -0
- package/dist/react/File.d.ts +2 -1
- package/dist/react/File.d.ts.map +1 -1
- package/dist/react/File.js +3 -2
- package/dist/react/File.js.map +1 -1
- package/dist/react/FileDiff.d.ts +2 -2
- package/dist/react/FileDiff.d.ts.map +1 -1
- package/dist/react/FileDiff.js +3 -2
- package/dist/react/FileDiff.js.map +1 -1
- package/dist/react/MultiFileDiff.d.ts +2 -2
- package/dist/react/MultiFileDiff.d.ts.map +1 -1
- package/dist/react/MultiFileDiff.js +3 -2
- package/dist/react/MultiFileDiff.js.map +1 -1
- package/dist/react/PatchDiff.d.ts +2 -2
- package/dist/react/PatchDiff.d.ts.map +1 -1
- package/dist/react/PatchDiff.js +3 -2
- package/dist/react/PatchDiff.js.map +1 -1
- package/dist/react/UnresolvedFile.d.ts +1 -1
- package/dist/react/UnresolvedFile.js.map +1 -1
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +3 -3
- package/dist/react/types.d.ts +9 -1
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/utils/useFileDiffInstance.d.ts +5 -3
- package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
- package/dist/react/utils/useFileDiffInstance.js +24 -34
- package/dist/react/utils/useFileDiffInstance.js.map +1 -1
- package/dist/react/utils/useFileInstance.d.ts +5 -3
- package/dist/react/utils/useFileInstance.d.ts.map +1 -1
- package/dist/react/utils/useFileInstance.js +23 -33
- package/dist/react/utils/useFileInstance.js.map +1 -1
- package/dist/react/utils/useUnresolvedFileInstance.js +4 -4
- package/dist/react/utils/useUnresolvedFileInstance.js.map +1 -1
- package/dist/renderers/DiffHunksRenderer.d.ts +41 -1
- package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
- package/dist/renderers/DiffHunksRenderer.js +167 -29
- package/dist/renderers/DiffHunksRenderer.js.map +1 -1
- package/dist/renderers/FileRenderer.d.ts +20 -1
- package/dist/renderers/FileRenderer.d.ts.map +1 -1
- package/dist/renderers/FileRenderer.js +119 -29
- package/dist/renderers/FileRenderer.js.map +1 -1
- package/dist/ssr/index.d.ts +2 -2
- package/dist/ssr/preloadDiffs.d.ts.map +1 -1
- package/dist/ssr/preloadDiffs.js +3 -1
- package/dist/ssr/preloadDiffs.js.map +1 -1
- package/dist/ssr/preloadFile.d.ts.map +1 -1
- package/dist/ssr/preloadFile.js +2 -0
- package/dist/ssr/preloadFile.js.map +1 -1
- package/dist/style.js +1 -1
- package/dist/types.d.ts +192 -42
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/annotationHelpers.d.ts +20 -0
- package/dist/utils/annotationHelpers.d.ts.map +1 -0
- package/dist/utils/annotationHelpers.js +29 -0
- package/dist/utils/annotationHelpers.js.map +1 -0
- package/dist/utils/computeFileOffsets.d.ts.map +1 -1
- package/dist/utils/computeFileOffsets.js +34 -5
- package/dist/utils/computeFileOffsets.js.map +1 -1
- package/dist/utils/editSessionHunks.d.ts +66 -0
- package/dist/utils/editSessionHunks.d.ts.map +1 -0
- package/dist/utils/editSessionHunks.js +437 -0
- package/dist/utils/editSessionHunks.js.map +1 -0
- package/dist/utils/getDiffHunksRendererOptions.d.ts.map +1 -1
- package/dist/utils/getDiffHunksRendererOptions.js +2 -1
- package/dist/utils/getDiffHunksRendererOptions.js.map +1 -1
- package/dist/utils/getFileRendererOptions.d.ts.map +1 -1
- package/dist/utils/getFileRendererOptions.js +2 -1
- package/dist/utils/getFileRendererOptions.js.map +1 -1
- package/dist/utils/getHunkSideBoundaries.d.ts +7 -0
- package/dist/utils/getHunkSideBoundaries.d.ts.map +1 -0
- package/dist/utils/getHunkSideBoundaries.js +12 -0
- package/dist/utils/getHunkSideBoundaries.js.map +1 -0
- package/dist/utils/getTotalLineCountFromHunks.d.ts.map +1 -1
- package/dist/utils/getTotalLineCountFromHunks.js +2 -1
- package/dist/utils/getTotalLineCountFromHunks.js.map +1 -1
- package/dist/utils/hydratePartialDiff.d.ts.map +1 -1
- package/dist/utils/hydratePartialDiff.js +4 -3
- package/dist/utils/hydratePartialDiff.js.map +1 -1
- package/dist/utils/isGutterUtilityPath.d.ts +5 -0
- package/dist/utils/isGutterUtilityPath.d.ts.map +1 -0
- package/dist/utils/isGutterUtilityPath.js +12 -0
- package/dist/utils/isGutterUtilityPath.js.map +1 -0
- package/dist/utils/iterateOverDiff.d.ts.map +1 -1
- package/dist/utils/iterateOverDiff.js +17 -12
- package/dist/utils/iterateOverDiff.js.map +1 -1
- package/dist/utils/parseMergeConflictDiffFromFile.d.ts.map +1 -1
- package/dist/utils/parseMergeConflictDiffFromFile.js +9 -6
- package/dist/utils/parseMergeConflictDiffFromFile.js.map +1 -1
- package/dist/utils/parsePatchFiles.d.ts.map +1 -1
- package/dist/utils/parsePatchFiles.js +6 -3
- package/dist/utils/parsePatchFiles.js.map +1 -1
- package/dist/utils/realignChangeContent.d.ts +33 -0
- package/dist/utils/realignChangeContent.d.ts.map +1 -0
- package/dist/utils/realignChangeContent.js +148 -0
- package/dist/utils/realignChangeContent.js.map +1 -0
- package/dist/utils/renderDiffWithHighlighter.js +1 -1
- package/dist/utils/resolveRegion.d.ts.map +1 -1
- package/dist/utils/resolveRegion.js +18 -2
- package/dist/utils/resolveRegion.js.map +1 -1
- package/dist/utils/shouldUseTokenTransformer.d.ts +7 -3
- package/dist/utils/shouldUseTokenTransformer.d.ts.map +1 -1
- package/dist/utils/shouldUseTokenTransformer.js +1 -1
- package/dist/utils/shouldUseTokenTransformer.js.map +1 -1
- package/dist/utils/updateDiffHunks.d.ts +8 -2
- package/dist/utils/updateDiffHunks.d.ts.map +1 -1
- package/dist/utils/updateDiffHunks.js +9 -7
- package/dist/utils/updateDiffHunks.js.map +1 -1
- package/dist/utils/virtualDiffLayout.d.ts +42 -2
- package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
- package/dist/utils/virtualDiffLayout.js +101 -7
- package/dist/utils/virtualDiffLayout.js.map +1 -1
- package/dist/worker/WorkerPoolManager.d.ts +2 -1
- package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
- package/dist/worker/WorkerPoolManager.js +3 -0
- package/dist/worker/WorkerPoolManager.js.map +1 -1
- package/dist/worker/worker-portable.js +60 -20
- package/dist/worker/worker-portable.js.map +1 -1
- package/dist/worker/worker.js +61 -19
- package/dist/worker/worker.js.map +1 -1
- package/package.json +10 -7
- package/dist/editor/index.d.ts +0 -3
- package/dist/editor/index.js +0 -3
- package/dist/editor/tokenzier.d.ts.map +0 -1
- package/dist/editor/tokenzier.js.map +0 -1
- package/dist/react/EditorContext.d.ts +0 -15
- package/dist/react/EditorContext.d.ts.map +0 -1
- package/dist/react/EditorContext.js +0 -23
- package/dist/react/EditorContext.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iterateOverDiff.js","names":[],"sources":["../../src/utils/iterateOverDiff.ts"],"sourcesContent":["import { DEFAULT_COLLAPSED_CONTEXT_THRESHOLD } from '../constants';\nimport type {\n ChangeContent,\n FileDiffMetadata,\n Hunk,\n HunkExpansionRegion,\n} from '../types';\nimport {\n getExpandedRegion,\n getTrailingExpandedRegion,\n} from './virtualDiffLayout';\n\nexport interface DiffLineMetadata {\n unifiedLineIndex: number;\n splitLineIndex: number;\n lineIndex: number;\n lineNumber: number;\n noEOFCR: boolean;\n}\n\nexport interface DiffLineCallbackBase {\n hunkIndex: number;\n hunk: Hunk | undefined; // undefined for trailing expansion region\n collapsedBefore: number; // > 0 means separator before this line, value = hidden lines\n collapsedAfter: number; // > 0 only on final line if trailing collapsed content\n}\n\ninterface DiffLineCallbackContextChange extends DiffLineCallbackBase {\n type: 'change' | 'context' | 'context-expanded';\n deletionLine: DiffLineMetadata;\n additionLine: DiffLineMetadata;\n}\n\ninterface DiffLineCallbackChangeDeletion extends DiffLineCallbackBase {\n type: 'change';\n deletionLine: DiffLineMetadata;\n additionLine?: undefined;\n}\n\ninterface DiffLineCallbackChangeAddition extends DiffLineCallbackBase {\n type: 'change';\n deletionLine?: undefined;\n additionLine: DiffLineMetadata;\n}\n\nexport type DiffLineCallbackProps =\n | DiffLineCallbackContextChange\n | DiffLineCallbackChangeDeletion\n | DiffLineCallbackChangeAddition;\n\ntype DiffStyle = 'unified' | 'split' | 'both';\n\ntype LineIterationBounds = [startIndex: number, endIndex: number];\n\ntype ChangeContentSide = 'deletions' | 'additions';\n\ntype ContextLineCallback = (index: number) => boolean | void;\n\ninterface IterationState {\n isWindowedHighlight: boolean;\n viewportStart: number;\n viewportEnd: number;\n splitCount: number;\n unifiedCount: number;\n finalHunkIndex: number;\n shouldBreak(): boolean;\n shouldSkip(unifiedHeight: number, splitHeight: number): boolean;\n incrementCounts(unifiedValue: number, splitValue: number): void;\n isInWindow(unifiedHeight: number, splitHeight: number): boolean;\n isInUnifiedWindow(height: number): boolean;\n isInSplitWindow(height: number): boolean;\n emit(props: DiffLineCallbackProps, silent?: boolean): boolean;\n}\n\ninterface IterationStartState {\n hunkIndex: number;\n splitCount: number;\n unifiedCount: number;\n}\n\ninterface HunkPrefixCounts {\n splitCount: number;\n unifiedCount: number;\n}\n\ninterface IterationStartStateProps extends Omit<\n IterateOverDiffProps,\n 'callback' | 'totalLines'\n> {\n startingLine: number;\n collapsedContextThreshold: number;\n}\n\ninterface HunkPrefixCountsProps extends Pick<\n IterationStartStateProps,\n 'diff' | 'expandedHunks' | 'collapsedContextThreshold'\n> {}\n\nexport type DiffLineCallback = (props: DiffLineCallbackProps) => boolean | void;\n\nexport interface IterateOverDiffProps {\n diff: FileDiffMetadata;\n diffStyle: DiffStyle;\n startingLine?: number;\n totalLines?: number;\n expandedHunks?: Map<number, HunkExpansionRegion> | true;\n collapsedContextThreshold?: number;\n callback: DiffLineCallback;\n}\n\nexport function iterateOverDiff({\n diff,\n diffStyle,\n startingLine = 0,\n totalLines = Infinity,\n expandedHunks,\n collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD,\n callback,\n}: IterateOverDiffProps): void {\n const iterationStart = getIterationStartState({\n diff,\n diffStyle,\n startingLine,\n expandedHunks,\n collapsedContextThreshold,\n });\n const state: IterationState = {\n viewportStart: startingLine,\n viewportEnd: startingLine + totalLines,\n isWindowedHighlight: startingLine > 0 || totalLines < Infinity,\n splitCount: iterationStart.splitCount,\n unifiedCount: iterationStart.unifiedCount,\n finalHunkIndex: diff.hunks.length - 1,\n shouldBreak() {\n if (!state.isWindowedHighlight) {\n return false;\n }\n\n const breakUnified = state.unifiedCount >= startingLine + totalLines;\n const breakSplit = state.splitCount >= startingLine + totalLines;\n\n if (diffStyle === 'unified') {\n return breakUnified;\n } else if (diffStyle === 'split') {\n return breakSplit;\n } else {\n return breakUnified && breakSplit;\n }\n },\n shouldSkip(unifiedCount: number, splitCount: number) {\n if (!state.isWindowedHighlight) {\n return false;\n }\n\n const skipUnified =\n unifiedCount > 0 && state.unifiedCount + unifiedCount <= startingLine;\n const skipSplit =\n splitCount > 0 && state.splitCount + splitCount <= startingLine;\n\n if (diffStyle === 'unified') {\n return skipUnified;\n } else if (diffStyle === 'split') {\n return skipSplit;\n } else {\n return skipUnified && skipSplit;\n }\n },\n incrementCounts(unifiedValue: number, splitValue: number) {\n if (diffStyle === 'unified' || diffStyle === 'both') {\n state.unifiedCount += unifiedValue;\n }\n if (diffStyle === 'split' || diffStyle === 'both') {\n state.splitCount += splitValue;\n }\n },\n isInWindow(unifiedHeight: number, splitHeight: number) {\n if (!state.isWindowedHighlight) {\n return true;\n }\n\n const unifiedInWindow = state.isInUnifiedWindow(unifiedHeight);\n const splitInWindow = state.isInSplitWindow(splitHeight);\n\n if (diffStyle === 'unified') {\n return unifiedInWindow;\n } else if (diffStyle === 'split') {\n return splitInWindow;\n } else {\n return unifiedInWindow || splitInWindow;\n }\n },\n isInUnifiedWindow(unifiedHeight: number) {\n return (\n !state.isWindowedHighlight ||\n (state.unifiedCount >= startingLine - unifiedHeight &&\n state.unifiedCount < startingLine + totalLines)\n );\n },\n isInSplitWindow(splitHeight: number) {\n return (\n !state.isWindowedHighlight ||\n (state.splitCount >= startingLine - splitHeight &&\n state.splitCount < startingLine + totalLines)\n );\n },\n emit(props: DiffLineCallbackProps, silent = false): boolean {\n if (!silent) {\n if (diffStyle === 'unified') {\n state.incrementCounts(1, 0);\n } else if (diffStyle === 'split') {\n state.incrementCounts(0, 1);\n } else {\n state.incrementCounts(1, 1);\n }\n }\n return callback(props) ?? false;\n },\n };\n\n hunkIterator: for (\n let hunkIndex = iterationStart.hunkIndex;\n hunkIndex < diff.hunks.length;\n hunkIndex++\n ) {\n const hunk = diff.hunks[hunkIndex];\n if (hunk == null) {\n throw new Error('iterateOverDiff: invalid hunk index');\n }\n if (state.shouldBreak()) {\n break;\n }\n\n const leadingRegion = getExpandedRegion({\n isPartial: diff.isPartial,\n rangeSize: hunk.collapsedBefore,\n expandedHunks,\n hunkIndex,\n collapsedContextThreshold,\n });\n const trailingRegion =\n hunkIndex === state.finalHunkIndex\n ? getTrailingExpandedRegion({\n fileDiff: diff,\n hunkIndex,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix: 'iterateOverDiff',\n })\n : undefined;\n const expandedLineCount = leadingRegion.fromStart + leadingRegion.fromEnd;\n\n function getTrailingCollapsedAfter(\n unifiedLineIndex: number,\n splitLineIndex: number\n ) {\n if (\n trailingRegion == null ||\n trailingRegion.collapsedLines <= 0 ||\n trailingRegion.fromStart + trailingRegion.fromEnd > 0\n ) {\n return 0;\n }\n if (diffStyle === 'unified') {\n return unifiedLineIndex ===\n hunk.unifiedLineStart + hunk.unifiedLineCount - 1\n ? trailingRegion.collapsedLines\n : 0;\n }\n return splitLineIndex === hunk.splitLineStart + hunk.splitLineCount - 1\n ? trailingRegion.collapsedLines\n : 0;\n }\n\n let consumedCollapsed = leadingRegion.collapsedLines === 0;\n function consumePendingCollapsed() {\n if (consumedCollapsed) {\n return 0;\n }\n consumedCollapsed = true;\n return leadingRegion.collapsedLines;\n }\n\n // Emit for expanded lines\n if (!state.shouldSkip(expandedLineCount, expandedLineCount)) {\n let unifiedLineIndex = hunk.unifiedLineStart - leadingRegion.rangeSize;\n let splitLineIndex = hunk.splitLineStart - leadingRegion.rangeSize;\n\n let deletionLineIndex = hunk.deletionLineIndex - leadingRegion.rangeSize;\n let additionLineIndex = hunk.additionLineIndex - leadingRegion.rangeSize;\n let deletionLineNumber = hunk.deletionStart - leadingRegion.rangeSize;\n let additionLineNumber = hunk.additionStart - leadingRegion.rangeSize;\n\n if (\n walkContextLines(state, leadingRegion.fromStart, diffStyle, (index) => {\n return state.emit({\n hunkIndex,\n hunk: hunk,\n collapsedBefore: 0,\n collapsedAfter: 0,\n type: 'context-expanded',\n deletionLine: {\n lineNumber: deletionLineNumber + index,\n lineIndex: deletionLineIndex + index,\n noEOFCR: false,\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n },\n additionLine: {\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n lineIndex: additionLineIndex + index,\n lineNumber: additionLineNumber + index,\n noEOFCR: false,\n },\n });\n })\n ) {\n break hunkIterator;\n }\n\n unifiedLineIndex = hunk.unifiedLineStart - leadingRegion.fromEnd;\n splitLineIndex = hunk.splitLineStart - leadingRegion.fromEnd;\n\n deletionLineIndex = hunk.deletionLineIndex - leadingRegion.fromEnd;\n additionLineIndex = hunk.additionLineIndex - leadingRegion.fromEnd;\n deletionLineNumber = hunk.deletionStart - leadingRegion.fromEnd;\n additionLineNumber = hunk.additionStart - leadingRegion.fromEnd;\n if (\n walkContextLines(\n state,\n leadingRegion.fromEnd,\n diffStyle,\n (index) => {\n return state.emit({\n hunkIndex,\n hunk,\n collapsedBefore: consumePendingCollapsed(),\n collapsedAfter: 0,\n type: 'context-expanded',\n deletionLine: {\n lineNumber: deletionLineNumber + index,\n lineIndex: deletionLineIndex + index,\n noEOFCR: false,\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n },\n additionLine: {\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n lineIndex: additionLineIndex + index,\n lineNumber: additionLineNumber + index,\n noEOFCR: false,\n },\n });\n },\n () => {\n // The collapsed separator belongs before this fromEnd slice. If the\n // render window starts inside the slice, consume it with the skipped\n // rows so it is not attached to the first emitted row.\n consumePendingCollapsed();\n }\n )\n ) {\n break hunkIterator;\n }\n } else {\n state.incrementCounts(expandedLineCount, expandedLineCount);\n consumePendingCollapsed();\n }\n\n let unifiedLineIndex = hunk.unifiedLineStart;\n let splitLineIndex = hunk.splitLineStart;\n\n let deletionLineIndex = hunk.deletionLineIndex;\n let additionLineIndex = hunk.additionLineIndex;\n let deletionLineNumber = hunk.deletionStart;\n let additionLineNumber = hunk.additionStart;\n const lastContent = hunk.hunkContent.at(-1);\n\n for (const content of hunk.hunkContent) {\n if (state.shouldBreak()) {\n break hunkIterator;\n }\n\n const isLastContent = content === lastContent;\n\n // Hunk Context Content\n if (content.type === 'context') {\n if (!state.shouldSkip(content.lines, content.lines)) {\n if (\n walkContextLines(\n state,\n content.lines,\n diffStyle,\n (index) => {\n const isLastLine = isLastContent && index === content.lines - 1;\n const unifiedRowIndex = unifiedLineIndex + index;\n const splitRowIndex = splitLineIndex + index;\n return state.emit({\n hunkIndex,\n hunk,\n collapsedBefore: consumePendingCollapsed(),\n collapsedAfter: getTrailingCollapsedAfter(\n unifiedRowIndex,\n splitRowIndex\n ),\n type: 'context',\n deletionLine: {\n lineNumber: deletionLineNumber + index,\n lineIndex: deletionLineIndex + index,\n noEOFCR: isLastLine && hunk.noEOFCRDeletions,\n unifiedLineIndex: unifiedRowIndex,\n splitLineIndex: splitRowIndex,\n },\n additionLine: {\n unifiedLineIndex: unifiedRowIndex,\n splitLineIndex: splitRowIndex,\n lineIndex: additionLineIndex + index,\n lineNumber: additionLineNumber + index,\n noEOFCR: isLastLine && hunk.noEOFCRAdditions,\n },\n });\n },\n () => {\n // When windowing starts inside context content, the leading\n // separator was above the visible range and should not be\n // emitted on the first rendered context line.\n consumePendingCollapsed();\n }\n )\n ) {\n break hunkIterator;\n }\n } else {\n state.incrementCounts(content.lines, content.lines);\n consumePendingCollapsed();\n }\n unifiedLineIndex += content.lines;\n splitLineIndex += content.lines;\n\n deletionLineIndex += content.lines;\n additionLineIndex += content.lines;\n deletionLineNumber += content.lines;\n additionLineNumber += content.lines;\n }\n // Hunk Change Content\n else {\n const splitCount = Math.max(content.deletions, content.additions);\n const unifiedCount = content.deletions + content.additions;\n const shouldSkipChange = state.shouldSkip(unifiedCount, splitCount);\n if (!shouldSkipChange) {\n const iterationRanges = getChangeIterationRanges(\n state,\n content,\n diffStyle\n );\n const firstRangeStart = iterationRanges[0]?.[0] ?? 0;\n if (firstRangeStart > 0) {\n // Change rows can be windowed from the middle of the block too. In\n // that case the leading separator belongs to skipped rows, not to\n // the first visible deletion/addition row.\n consumePendingCollapsed();\n }\n // Change ranges are already clipped to the active window. Counts move\n // once for the whole change block after the selected rows emit.\n for (const [rangeStart, rangeEnd] of iterationRanges) {\n for (let index = rangeStart; index < rangeEnd; index++) {\n const unifiedRowIndex = unifiedLineIndex + index;\n const splitRowIndex =\n diffStyle === 'unified'\n ? splitLineIndex +\n (index < content.deletions\n ? index\n : index - content.deletions)\n : splitLineIndex + index;\n const collapsedAfter = getTrailingCollapsedAfter(\n unifiedRowIndex,\n splitRowIndex\n );\n if (\n state.emit(\n getChangeLineData({\n hunkIndex,\n hunk,\n collapsedBefore: consumePendingCollapsed(),\n collapsedAfter,\n diffStyle,\n index,\n unifiedLineIndex,\n splitLineIndex,\n additionLineIndex,\n deletionLineIndex,\n additionLineNumber,\n deletionLineNumber,\n content,\n isLastContent,\n unifiedCount,\n splitCount,\n }),\n true\n )\n ) {\n break hunkIterator;\n }\n }\n }\n }\n\n consumePendingCollapsed();\n state.incrementCounts(unifiedCount, splitCount);\n unifiedLineIndex += unifiedCount;\n splitLineIndex += splitCount;\n deletionLineIndex += content.deletions;\n additionLineIndex += content.additions;\n deletionLineNumber += content.deletions;\n additionLineNumber += content.additions;\n }\n }\n\n if (trailingRegion != null) {\n const { collapsedLines, fromStart, fromEnd } = trailingRegion;\n const len = fromStart + fromEnd;\n if (\n walkContextLines(\n state,\n len,\n diffStyle,\n (index) => {\n const isLastLine = index === len - 1;\n return state.emit({\n hunkIndex: diff.hunks.length,\n hunk: undefined,\n collapsedBefore: 0,\n collapsedAfter: isLastLine ? collapsedLines : 0,\n type: 'context-expanded',\n deletionLine: {\n lineNumber: deletionLineNumber + index,\n lineIndex: deletionLineIndex + index,\n noEOFCR: false,\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n },\n additionLine: {\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n lineIndex: additionLineIndex + index,\n lineNumber: additionLineNumber + index,\n noEOFCR: false,\n },\n });\n },\n undefined,\n () => state.shouldBreak()\n )\n ) {\n break hunkIterator;\n }\n }\n }\n}\n\n// Seek the iterator to the hunk that contains `startingLine` without changing\n// the public meaning of `startingLine`: it is a dense rendered-row index, not\n// a raw split/unified line index. We first build prefix counts for each hunk\n// under the current expansion/collapse settings, binary-search those counts to\n// find the first hunk whose rendered rows cross `startingLine`, then seed the\n// running split/unified counters as if every prior hunk had already been\n// walked.\nfunction getIterationStartState({\n diff,\n diffStyle,\n startingLine,\n expandedHunks,\n collapsedContextThreshold,\n}: IterationStartStateProps): IterationStartState {\n if (startingLine <= 0 || diffStyle === 'both') {\n return { hunkIndex: 0, splitCount: 0, unifiedCount: 0 };\n }\n\n const prefixCounts = getHunkPrefixCounts({\n diff,\n expandedHunks,\n collapsedContextThreshold,\n });\n\n let low = 0;\n let high = diff.hunks.length - 1;\n let result = diff.hunks.length;\n\n while (low <= high) {\n const mid = (low + high) >> 1;\n const counts = prefixCounts[mid + 1];\n if (counts == null) {\n throw new Error('iterateOverDiff: invalid hunk prefix index');\n }\n const selectedCount =\n diffStyle === 'unified' ? counts.unifiedCount : counts.splitCount;\n\n if (selectedCount > startingLine) {\n result = mid;\n high = mid - 1;\n } else {\n low = mid + 1;\n }\n }\n\n if (result >= diff.hunks.length) {\n const counts = prefixCounts[diff.hunks.length];\n if (counts == null) {\n throw new Error('iterateOverDiff: invalid terminal hunk prefix index');\n }\n return {\n hunkIndex: diff.hunks.length,\n splitCount: counts.splitCount,\n unifiedCount: counts.unifiedCount,\n };\n }\n\n const counts = prefixCounts[result];\n if (counts == null) {\n throw new Error('iterateOverDiff: invalid selected hunk prefix index');\n }\n return {\n hunkIndex: result,\n splitCount: counts.splitCount,\n unifiedCount: counts.unifiedCount,\n };\n}\n\n// Build cumulative rendered-row counts at every hunk boundary for the current\n// expansion state. Entry 0 is always zero rows before the first hunk; entry N\n// is the split/unified row count after hunks [0, N). These counts let\n// getIterationStartState binary-search by dense rendered row without replaying\n// every prior hunk.\nfunction getHunkPrefixCounts({\n diff,\n expandedHunks,\n collapsedContextThreshold,\n}: HunkPrefixCountsProps): HunkPrefixCounts[] {\n let splitCount = 0;\n let unifiedCount = 0;\n const finalHunkIndex = diff.hunks.length - 1;\n const prefixCounts: HunkPrefixCounts[] = [\n {\n splitCount: 0,\n unifiedCount: 0,\n },\n ];\n\n for (let index = 0; index < diff.hunks.length; index++) {\n const hunk = diff.hunks[index];\n if (hunk == null) {\n throw new Error('iterateOverDiff: invalid hunk summary index');\n }\n\n const leadingRegion = getExpandedRegion({\n isPartial: diff.isPartial,\n rangeSize: hunk.collapsedBefore,\n expandedHunks,\n hunkIndex: index,\n collapsedContextThreshold,\n });\n const leadingCount = leadingRegion.fromStart + leadingRegion.fromEnd;\n splitCount += leadingCount + hunk.splitLineCount;\n unifiedCount += leadingCount + hunk.unifiedLineCount;\n\n const trailingRegion =\n index === finalHunkIndex\n ? getTrailingExpandedRegion({\n fileDiff: diff,\n hunkIndex: index,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix: 'iterateOverDiff',\n })\n : undefined;\n if (trailingRegion != null) {\n const trailingCount = trailingRegion.fromStart + trailingRegion.fromEnd;\n splitCount += trailingCount;\n unifiedCount += trailingCount;\n }\n\n prefixCounts.push({ splitCount, unifiedCount });\n }\n\n return prefixCounts;\n}\n\n// Clip a run of context rows to a single bounded hull around the active rendered\n// window. `diffStyle: both` can make split and unified rows visible in disjoint\n// ranges, so these bounds may include interior gaps that still need per-row\n// filtering before emitting.\nfunction getContextLineIterationBounds(\n state: IterationState,\n count: number,\n diffStyle: DiffStyle\n): LineIterationBounds {\n if (!state.isWindowedHighlight || count <= 0) {\n return [0, count];\n }\n\n const ranges: LineIterationBounds[] = [];\n function pushRange(currentCount: number): void {\n const start = Math.max(0, state.viewportStart - currentCount);\n const end = Math.min(count, state.viewportEnd - currentCount);\n if (end > start) {\n ranges.push([start, end]);\n }\n }\n\n if (diffStyle !== 'split') {\n pushRange(state.unifiedCount);\n }\n if (diffStyle !== 'unified') {\n pushRange(state.splitCount);\n }\n\n if (ranges.length === 0) {\n return [0, 0];\n }\n\n let start = ranges[0][0];\n let end = ranges[0][1];\n for (let index = 1; index < ranges.length; index++) {\n const range = ranges[index];\n start = Math.min(start, range[0]);\n end = Math.max(end, range[1]);\n }\n return [start, end];\n}\n\n// Walk context rows through the active window while keeping split and\n// unified counters aligned. The callback only runs after the final per-row\n// window check, which keeps `diffStyle: both` gap rows from being emitted.\nfunction walkContextLines(\n state: IterationState,\n count: number,\n diffStyle: DiffStyle,\n callback: ContextLineCallback,\n onSkippedStart?: () => void,\n shouldBreak?: () => boolean\n): boolean {\n const [startIndex, endIndex] = getContextLineIterationBounds(\n state,\n count,\n diffStyle\n );\n if (startIndex > 0) {\n state.incrementCounts(startIndex, startIndex);\n onSkippedStart?.();\n }\n\n let index = startIndex;\n while (index < count) {\n if (shouldBreak?.() === true) {\n return true;\n }\n if (index >= endIndex) {\n state.incrementCounts(count - index, count - index);\n break;\n }\n if (state.isInWindow(0, 0)) {\n if (callback(index) === true) {\n return true;\n }\n } else {\n state.incrementCounts(1, 1);\n }\n index++;\n }\n\n return false;\n}\n\n// Clip a change block to the rows that can be visible in the active coordinate\n// space. `diffStyle: both` iterates in split row space, but includes the unified\n// ranges too so either view can render the visible change rows it needs.\nfunction getChangeIterationRanges(\n state: IterationState,\n content: ChangeContent,\n diffStyle: DiffStyle\n): LineIterationBounds[] {\n // If not a window highlight, then we should just render the entire range\n if (!state.isWindowedHighlight) {\n return [\n [\n 0,\n diffStyle === 'unified'\n ? content.deletions + content.additions\n : Math.max(content.deletions, content.additions),\n ],\n ];\n }\n const useUnified = diffStyle !== 'split';\n const useSplit = diffStyle !== 'unified';\n const iterationSpace = diffStyle === 'unified' ? 'unified' : 'split';\n const iterationRanges: LineIterationBounds[] = [];\n function getVisibleRange(\n start: number,\n count: number\n ): LineIterationBounds | undefined {\n const end = start + count;\n if (end <= state.viewportStart || start >= state.viewportEnd) {\n return undefined;\n }\n const visibleStart = Math.max(0, state.viewportStart - start);\n const visibleEnd = Math.min(count, state.viewportEnd - start);\n return visibleEnd > visibleStart ? [visibleStart, visibleEnd] : undefined;\n }\n function mapRangeToIteration(\n range: LineIterationBounds,\n kind: ChangeContentSide\n ): LineIterationBounds {\n if (iterationSpace === 'split') {\n // For split iteration, additions/deletions are already in split row space.\n return range;\n }\n return kind === 'additions'\n ? [range[0] + content.deletions, range[1] + content.deletions]\n : range;\n }\n function pushRange(\n range: LineIterationBounds | undefined,\n kind: ChangeContentSide\n ) {\n if (range == null) {\n return;\n }\n const [start, end] = mapRangeToIteration(range, kind);\n if (end > start) {\n iterationRanges.push([start, end]);\n }\n }\n\n if (useUnified) {\n pushRange(\n getVisibleRange(state.unifiedCount, content.deletions),\n 'deletions'\n );\n pushRange(\n getVisibleRange(\n state.unifiedCount + content.deletions,\n content.additions\n ),\n 'additions'\n );\n }\n\n if (useSplit) {\n pushRange(\n getVisibleRange(state.splitCount, content.deletions),\n 'deletions'\n );\n pushRange(\n getVisibleRange(state.splitCount, content.additions),\n 'additions'\n );\n }\n\n if (iterationRanges.length === 0) {\n return iterationRanges;\n }\n\n iterationRanges.sort((a, b) => a[0] - b[0]);\n const merged: LineIterationBounds[] = [iterationRanges[0]];\n for (const [start, end] of iterationRanges.slice(1)) {\n const last = merged[merged.length - 1];\n if (start <= last[1]) {\n last[1] = Math.max(last[1], end);\n } else {\n merged.push([start, end]);\n }\n }\n\n return merged;\n}\n\ninterface GetChangeLineDataProps {\n hunkIndex: number;\n hunk: Hunk;\n collapsedBefore: number;\n collapsedAfter: number;\n diffStyle: DiffStyle;\n index: number;\n unifiedLineIndex: number;\n splitLineIndex: number;\n additionLineIndex: number;\n additionLineNumber: number;\n deletionLineNumber: number;\n deletionLineIndex: number;\n content: ChangeContent;\n isLastContent: boolean;\n unifiedCount: number;\n splitCount: number;\n}\n\n// Build the callback payload for one change row, mapping the selected row index\n// into split/unified coordinates and addition/deletion line metadata.\nfunction getChangeLineData({\n hunkIndex,\n hunk,\n collapsedAfter,\n collapsedBefore,\n diffStyle,\n index,\n unifiedLineIndex,\n splitLineIndex,\n additionLineIndex,\n deletionLineIndex,\n additionLineNumber,\n deletionLineNumber,\n content,\n isLastContent,\n unifiedCount,\n splitCount,\n}: GetChangeLineDataProps): DiffLineCallbackProps {\n const unifiedDeletionLineIndex =\n index < content.deletions ? unifiedLineIndex + index : undefined;\n const unifiedAdditionLineIndex =\n diffStyle === 'unified'\n ? index >= content.deletions\n ? unifiedLineIndex + index\n : undefined\n : index < content.additions\n ? unifiedLineIndex + content.deletions + index\n : undefined;\n\n const resolvedSplitLineIndex =\n diffStyle === 'unified'\n ? splitLineIndex +\n (index < content.deletions ? index : index - content.deletions)\n : splitLineIndex + index;\n\n const deletionLineIndexValue =\n index < content.deletions ? deletionLineIndex + index : undefined;\n const deletionLineNumberValue =\n index < content.deletions ? deletionLineNumber + index : undefined;\n const additionLineIndexValue =\n diffStyle === 'unified'\n ? index >= content.deletions\n ? additionLineIndex + (index - content.deletions)\n : undefined\n : index < content.additions\n ? additionLineIndex + index\n : undefined;\n const additionLineNumberValue =\n diffStyle === 'unified'\n ? index >= content.deletions\n ? additionLineNumber + (index - content.deletions)\n : undefined\n : index < content.additions\n ? additionLineNumber + index\n : undefined;\n\n const noEOFCRDeletion =\n diffStyle === 'unified'\n ? isLastContent &&\n index === content.deletions - 1 &&\n hunk.noEOFCRDeletions\n : isLastContent && index === splitCount - 1 && hunk.noEOFCRDeletions;\n const noEOFCRAddition =\n diffStyle === 'unified'\n ? isLastContent && index === unifiedCount - 1 && hunk.noEOFCRAdditions\n : isLastContent && index === splitCount - 1 && hunk.noEOFCRAdditions;\n\n const deletionLine: DiffLineMetadata | undefined =\n deletionLineIndexValue != null &&\n deletionLineNumberValue != null &&\n unifiedDeletionLineIndex != null\n ? {\n lineNumber: deletionLineNumberValue,\n lineIndex: deletionLineIndexValue,\n noEOFCR: noEOFCRDeletion,\n unifiedLineIndex: unifiedDeletionLineIndex,\n splitLineIndex: resolvedSplitLineIndex,\n }\n : undefined;\n const additionLine: DiffLineMetadata | undefined =\n additionLineIndexValue != null &&\n additionLineNumberValue != null &&\n unifiedAdditionLineIndex != null\n ? {\n unifiedLineIndex: unifiedAdditionLineIndex,\n splitLineIndex: resolvedSplitLineIndex,\n lineIndex: additionLineIndexValue,\n lineNumber: additionLineNumberValue,\n noEOFCR: noEOFCRAddition,\n }\n : undefined;\n\n if (deletionLine == null && additionLine != null) {\n return {\n type: 'change',\n hunkIndex,\n hunk,\n collapsedAfter,\n collapsedBefore,\n deletionLine: undefined,\n additionLine,\n };\n } else if (deletionLine != null && additionLine == null) {\n return {\n type: 'change',\n hunkIndex,\n hunk,\n collapsedAfter,\n collapsedBefore,\n deletionLine,\n additionLine: undefined,\n };\n }\n\n if (deletionLine == null || additionLine == null) {\n throw new Error('iterateOverDiff: missing change line data');\n }\n\n return {\n type: 'change',\n hunkIndex,\n hunk,\n collapsedAfter,\n collapsedBefore,\n deletionLine,\n additionLine,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"iterateOverDiff.js","names":[],"sources":["../../src/utils/iterateOverDiff.ts"],"sourcesContent":["import { DEFAULT_COLLAPSED_CONTEXT_THRESHOLD } from '../constants';\nimport type {\n ChangeContent,\n FileDiffMetadata,\n Hunk,\n HunkExpansionRegion,\n} from '../types';\nimport { getHunkSideStartBoundary } from './getHunkSideBoundaries';\nimport {\n getExpandedRegion,\n getTrailingExpandedRegion,\n} from './virtualDiffLayout';\n\nexport interface DiffLineMetadata {\n unifiedLineIndex: number;\n splitLineIndex: number;\n lineIndex: number;\n lineNumber: number;\n noEOFCR: boolean;\n}\n\nexport interface DiffLineCallbackBase {\n hunkIndex: number;\n hunk: Hunk | undefined; // undefined for trailing expansion region\n collapsedBefore: number; // > 0 means separator before this line, value = hidden lines\n collapsedAfter: number; // > 0 only on final line if trailing collapsed content\n}\n\ninterface DiffLineCallbackContextChange extends DiffLineCallbackBase {\n type: 'change' | 'context' | 'context-expanded';\n deletionLine: DiffLineMetadata;\n additionLine: DiffLineMetadata;\n}\n\ninterface DiffLineCallbackChangeDeletion extends DiffLineCallbackBase {\n type: 'change';\n deletionLine: DiffLineMetadata;\n additionLine?: undefined;\n}\n\ninterface DiffLineCallbackChangeAddition extends DiffLineCallbackBase {\n type: 'change';\n deletionLine?: undefined;\n additionLine: DiffLineMetadata;\n}\n\nexport type DiffLineCallbackProps =\n | DiffLineCallbackContextChange\n | DiffLineCallbackChangeDeletion\n | DiffLineCallbackChangeAddition;\n\ntype DiffStyle = 'unified' | 'split' | 'both';\n\ntype LineIterationBounds = [startIndex: number, endIndex: number];\n\ntype ChangeContentSide = 'deletions' | 'additions';\n\ntype ContextLineCallback = (index: number) => boolean | void;\n\ninterface IterationState {\n isWindowedHighlight: boolean;\n viewportStart: number;\n viewportEnd: number;\n splitCount: number;\n unifiedCount: number;\n finalHunkIndex: number;\n shouldBreak(): boolean;\n shouldSkip(unifiedHeight: number, splitHeight: number): boolean;\n incrementCounts(unifiedValue: number, splitValue: number): void;\n isInWindow(unifiedHeight: number, splitHeight: number): boolean;\n isInUnifiedWindow(height: number): boolean;\n isInSplitWindow(height: number): boolean;\n emit(props: DiffLineCallbackProps, silent?: boolean): boolean;\n}\n\ninterface IterationStartState {\n hunkIndex: number;\n splitCount: number;\n unifiedCount: number;\n}\n\ninterface HunkPrefixCounts {\n splitCount: number;\n unifiedCount: number;\n}\n\ninterface IterationStartStateProps extends Omit<\n IterateOverDiffProps,\n 'callback' | 'totalLines'\n> {\n startingLine: number;\n collapsedContextThreshold: number;\n}\n\ninterface HunkPrefixCountsProps extends Pick<\n IterationStartStateProps,\n 'diff' | 'expandedHunks' | 'collapsedContextThreshold'\n> {}\n\nexport type DiffLineCallback = (props: DiffLineCallbackProps) => boolean | void;\n\nexport interface IterateOverDiffProps {\n diff: FileDiffMetadata;\n diffStyle: DiffStyle;\n startingLine?: number;\n totalLines?: number;\n expandedHunks?: Map<number, HunkExpansionRegion> | true;\n collapsedContextThreshold?: number;\n callback: DiffLineCallback;\n}\n\nexport function iterateOverDiff({\n diff,\n diffStyle,\n startingLine = 0,\n totalLines = Infinity,\n expandedHunks,\n collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD,\n callback,\n}: IterateOverDiffProps): void {\n const iterationStart = getIterationStartState({\n diff,\n diffStyle,\n startingLine,\n expandedHunks,\n collapsedContextThreshold,\n });\n const state: IterationState = {\n viewportStart: startingLine,\n viewportEnd: startingLine + totalLines,\n isWindowedHighlight: startingLine > 0 || totalLines < Infinity,\n splitCount: iterationStart.splitCount,\n unifiedCount: iterationStart.unifiedCount,\n finalHunkIndex: diff.hunks.length - 1,\n shouldBreak() {\n if (!state.isWindowedHighlight) {\n return false;\n }\n\n const breakUnified = state.unifiedCount >= startingLine + totalLines;\n const breakSplit = state.splitCount >= startingLine + totalLines;\n\n if (diffStyle === 'unified') {\n return breakUnified;\n } else if (diffStyle === 'split') {\n return breakSplit;\n } else {\n return breakUnified && breakSplit;\n }\n },\n shouldSkip(unifiedCount: number, splitCount: number) {\n if (!state.isWindowedHighlight) {\n return false;\n }\n\n const skipUnified =\n unifiedCount > 0 && state.unifiedCount + unifiedCount <= startingLine;\n const skipSplit =\n splitCount > 0 && state.splitCount + splitCount <= startingLine;\n\n if (diffStyle === 'unified') {\n return skipUnified;\n } else if (diffStyle === 'split') {\n return skipSplit;\n } else {\n return skipUnified && skipSplit;\n }\n },\n incrementCounts(unifiedValue: number, splitValue: number) {\n if (diffStyle === 'unified' || diffStyle === 'both') {\n state.unifiedCount += unifiedValue;\n }\n if (diffStyle === 'split' || diffStyle === 'both') {\n state.splitCount += splitValue;\n }\n },\n isInWindow(unifiedHeight: number, splitHeight: number) {\n if (!state.isWindowedHighlight) {\n return true;\n }\n\n const unifiedInWindow = state.isInUnifiedWindow(unifiedHeight);\n const splitInWindow = state.isInSplitWindow(splitHeight);\n\n if (diffStyle === 'unified') {\n return unifiedInWindow;\n } else if (diffStyle === 'split') {\n return splitInWindow;\n } else {\n return unifiedInWindow || splitInWindow;\n }\n },\n isInUnifiedWindow(unifiedHeight: number) {\n return (\n !state.isWindowedHighlight ||\n (state.unifiedCount >= startingLine - unifiedHeight &&\n state.unifiedCount < startingLine + totalLines)\n );\n },\n isInSplitWindow(splitHeight: number) {\n return (\n !state.isWindowedHighlight ||\n (state.splitCount >= startingLine - splitHeight &&\n state.splitCount < startingLine + totalLines)\n );\n },\n emit(props: DiffLineCallbackProps, silent = false): boolean {\n if (!silent) {\n if (diffStyle === 'unified') {\n state.incrementCounts(1, 0);\n } else if (diffStyle === 'split') {\n state.incrementCounts(0, 1);\n } else {\n state.incrementCounts(1, 1);\n }\n }\n return callback(props) ?? false;\n },\n };\n\n hunkIterator: for (\n let hunkIndex = iterationStart.hunkIndex;\n hunkIndex < diff.hunks.length;\n hunkIndex++\n ) {\n const hunk = diff.hunks[hunkIndex];\n if (hunk == null) {\n throw new Error('iterateOverDiff: invalid hunk index');\n }\n if (state.shouldBreak()) {\n break;\n }\n\n const deletionBoundary = getHunkSideStartBoundary(\n hunk.deletionStart,\n hunk.deletionCount\n );\n const additionBoundary = getHunkSideStartBoundary(\n hunk.additionStart,\n hunk.additionCount\n );\n const deletionStartIndex =\n !diff.isPartial && hunk.deletionCount === 0\n ? deletionBoundary\n : hunk.deletionLineIndex;\n const additionStartIndex =\n !diff.isPartial && hunk.additionCount === 0\n ? additionBoundary\n : hunk.additionLineIndex;\n\n const leadingRegion = getExpandedRegion({\n isPartial: diff.isPartial,\n rangeSize: hunk.collapsedBefore,\n expandedHunks,\n hunkIndex,\n collapsedContextThreshold,\n });\n const trailingRegion =\n hunkIndex === state.finalHunkIndex\n ? getTrailingExpandedRegion({\n fileDiff: diff,\n hunkIndex,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix: 'iterateOverDiff',\n })\n : undefined;\n const expandedLineCount = leadingRegion.fromStart + leadingRegion.fromEnd;\n\n function getTrailingCollapsedAfter(\n unifiedLineIndex: number,\n splitLineIndex: number\n ) {\n if (\n trailingRegion == null ||\n trailingRegion.collapsedLines <= 0 ||\n trailingRegion.fromStart + trailingRegion.fromEnd > 0\n ) {\n return 0;\n }\n if (diffStyle === 'unified') {\n return unifiedLineIndex ===\n hunk.unifiedLineStart + hunk.unifiedLineCount - 1\n ? trailingRegion.collapsedLines\n : 0;\n }\n return splitLineIndex === hunk.splitLineStart + hunk.splitLineCount - 1\n ? trailingRegion.collapsedLines\n : 0;\n }\n\n let consumedCollapsed = leadingRegion.collapsedLines === 0;\n function consumePendingCollapsed() {\n if (consumedCollapsed) {\n return 0;\n }\n consumedCollapsed = true;\n return leadingRegion.collapsedLines;\n }\n\n // Emit for expanded lines\n if (!state.shouldSkip(expandedLineCount, expandedLineCount)) {\n let unifiedLineIndex = hunk.unifiedLineStart - leadingRegion.rangeSize;\n let splitLineIndex = hunk.splitLineStart - leadingRegion.rangeSize;\n\n let deletionLineIndex = deletionStartIndex - leadingRegion.rangeSize;\n let additionLineIndex = additionStartIndex - leadingRegion.rangeSize;\n let deletionLineNumber = deletionBoundary + 1 - leadingRegion.rangeSize;\n let additionLineNumber = additionBoundary + 1 - leadingRegion.rangeSize;\n\n if (\n walkContextLines(state, leadingRegion.fromStart, diffStyle, (index) => {\n return state.emit({\n hunkIndex,\n hunk: hunk,\n collapsedBefore: 0,\n collapsedAfter: 0,\n type: 'context-expanded',\n deletionLine: {\n lineNumber: deletionLineNumber + index,\n lineIndex: deletionLineIndex + index,\n noEOFCR: false,\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n },\n additionLine: {\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n lineIndex: additionLineIndex + index,\n lineNumber: additionLineNumber + index,\n noEOFCR: false,\n },\n });\n })\n ) {\n break hunkIterator;\n }\n\n unifiedLineIndex = hunk.unifiedLineStart - leadingRegion.fromEnd;\n splitLineIndex = hunk.splitLineStart - leadingRegion.fromEnd;\n\n deletionLineIndex = deletionStartIndex - leadingRegion.fromEnd;\n additionLineIndex = additionStartIndex - leadingRegion.fromEnd;\n deletionLineNumber = deletionBoundary + 1 - leadingRegion.fromEnd;\n additionLineNumber = additionBoundary + 1 - leadingRegion.fromEnd;\n if (\n walkContextLines(\n state,\n leadingRegion.fromEnd,\n diffStyle,\n (index) => {\n return state.emit({\n hunkIndex,\n hunk,\n collapsedBefore: consumePendingCollapsed(),\n collapsedAfter: 0,\n type: 'context-expanded',\n deletionLine: {\n lineNumber: deletionLineNumber + index,\n lineIndex: deletionLineIndex + index,\n noEOFCR: false,\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n },\n additionLine: {\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n lineIndex: additionLineIndex + index,\n lineNumber: additionLineNumber + index,\n noEOFCR: false,\n },\n });\n },\n () => {\n // The collapsed separator belongs before this fromEnd slice. If the\n // render window starts inside the slice, consume it with the skipped\n // rows so it is not attached to the first emitted row.\n consumePendingCollapsed();\n }\n )\n ) {\n break hunkIterator;\n }\n } else {\n state.incrementCounts(expandedLineCount, expandedLineCount);\n consumePendingCollapsed();\n }\n\n let unifiedLineIndex = hunk.unifiedLineStart;\n let splitLineIndex = hunk.splitLineStart;\n\n let deletionLineIndex = deletionStartIndex;\n let additionLineIndex = additionStartIndex;\n let deletionLineNumber = deletionBoundary + 1;\n let additionLineNumber = additionBoundary + 1;\n const lastContent = hunk.hunkContent.at(-1);\n\n for (const content of hunk.hunkContent) {\n if (state.shouldBreak()) {\n break hunkIterator;\n }\n\n const isLastContent = content === lastContent;\n\n // Hunk Context Content\n if (content.type === 'context') {\n if (!state.shouldSkip(content.lines, content.lines)) {\n if (\n walkContextLines(\n state,\n content.lines,\n diffStyle,\n (index) => {\n const isLastLine = isLastContent && index === content.lines - 1;\n const unifiedRowIndex = unifiedLineIndex + index;\n const splitRowIndex = splitLineIndex + index;\n return state.emit({\n hunkIndex,\n hunk,\n collapsedBefore: consumePendingCollapsed(),\n collapsedAfter: getTrailingCollapsedAfter(\n unifiedRowIndex,\n splitRowIndex\n ),\n type: 'context',\n deletionLine: {\n lineNumber: deletionLineNumber + index,\n lineIndex: deletionLineIndex + index,\n noEOFCR: isLastLine && hunk.noEOFCRDeletions,\n unifiedLineIndex: unifiedRowIndex,\n splitLineIndex: splitRowIndex,\n },\n additionLine: {\n unifiedLineIndex: unifiedRowIndex,\n splitLineIndex: splitRowIndex,\n lineIndex: additionLineIndex + index,\n lineNumber: additionLineNumber + index,\n noEOFCR: isLastLine && hunk.noEOFCRAdditions,\n },\n });\n },\n () => {\n // When windowing starts inside context content, the leading\n // separator was above the visible range and should not be\n // emitted on the first rendered context line.\n consumePendingCollapsed();\n }\n )\n ) {\n break hunkIterator;\n }\n } else {\n state.incrementCounts(content.lines, content.lines);\n consumePendingCollapsed();\n }\n unifiedLineIndex += content.lines;\n splitLineIndex += content.lines;\n\n deletionLineIndex += content.lines;\n additionLineIndex += content.lines;\n deletionLineNumber += content.lines;\n additionLineNumber += content.lines;\n }\n // Hunk Change Content\n else {\n const splitCount = Math.max(content.deletions, content.additions);\n const unifiedCount = content.deletions + content.additions;\n const shouldSkipChange = state.shouldSkip(unifiedCount, splitCount);\n if (!shouldSkipChange) {\n const iterationRanges = getChangeIterationRanges(\n state,\n content,\n diffStyle\n );\n const firstRangeStart = iterationRanges[0]?.[0] ?? 0;\n if (firstRangeStart > 0) {\n // Change rows can be windowed from the middle of the block too. In\n // that case the leading separator belongs to skipped rows, not to\n // the first visible deletion/addition row.\n consumePendingCollapsed();\n }\n // Change ranges are already clipped to the active window. Counts move\n // once for the whole change block after the selected rows emit.\n for (const [rangeStart, rangeEnd] of iterationRanges) {\n for (let index = rangeStart; index < rangeEnd; index++) {\n const unifiedRowIndex = unifiedLineIndex + index;\n const splitRowIndex =\n diffStyle === 'unified'\n ? splitLineIndex +\n (index < content.deletions\n ? index\n : index - content.deletions)\n : splitLineIndex + index;\n const collapsedAfter = getTrailingCollapsedAfter(\n unifiedRowIndex,\n splitRowIndex\n );\n if (\n state.emit(\n getChangeLineData({\n hunkIndex,\n hunk,\n collapsedBefore: consumePendingCollapsed(),\n collapsedAfter,\n diffStyle,\n index,\n unifiedLineIndex,\n splitLineIndex,\n additionLineIndex,\n deletionLineIndex,\n additionLineNumber,\n deletionLineNumber,\n content,\n isLastContent,\n unifiedCount,\n splitCount,\n }),\n true\n )\n ) {\n break hunkIterator;\n }\n }\n }\n }\n\n consumePendingCollapsed();\n state.incrementCounts(unifiedCount, splitCount);\n unifiedLineIndex += unifiedCount;\n splitLineIndex += splitCount;\n deletionLineIndex += content.deletions;\n additionLineIndex += content.additions;\n deletionLineNumber += content.deletions;\n additionLineNumber += content.additions;\n }\n }\n\n if (trailingRegion != null) {\n const { collapsedLines, fromStart, fromEnd } = trailingRegion;\n const len = fromStart + fromEnd;\n if (\n walkContextLines(\n state,\n len,\n diffStyle,\n (index) => {\n const isLastLine = index === len - 1;\n return state.emit({\n hunkIndex: diff.hunks.length,\n hunk: undefined,\n collapsedBefore: 0,\n collapsedAfter: isLastLine ? collapsedLines : 0,\n type: 'context-expanded',\n deletionLine: {\n lineNumber: deletionLineNumber + index,\n lineIndex: deletionLineIndex + index,\n noEOFCR: false,\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n },\n additionLine: {\n unifiedLineIndex: unifiedLineIndex + index,\n splitLineIndex: splitLineIndex + index,\n lineIndex: additionLineIndex + index,\n lineNumber: additionLineNumber + index,\n noEOFCR: false,\n },\n });\n },\n undefined,\n () => state.shouldBreak()\n )\n ) {\n break hunkIterator;\n }\n }\n }\n}\n\n// Seek the iterator to the hunk that contains `startingLine` without changing\n// the public meaning of `startingLine`: it is a dense rendered-row index, not\n// a raw split/unified line index. We first build prefix counts for each hunk\n// under the current expansion/collapse settings, binary-search those counts to\n// find the first hunk whose rendered rows cross `startingLine`, then seed the\n// running split/unified counters as if every prior hunk had already been\n// walked.\nfunction getIterationStartState({\n diff,\n diffStyle,\n startingLine,\n expandedHunks,\n collapsedContextThreshold,\n}: IterationStartStateProps): IterationStartState {\n if (startingLine <= 0 || diffStyle === 'both') {\n return { hunkIndex: 0, splitCount: 0, unifiedCount: 0 };\n }\n\n const prefixCounts = getHunkPrefixCounts({\n diff,\n expandedHunks,\n collapsedContextThreshold,\n });\n\n let low = 0;\n let high = diff.hunks.length - 1;\n let result = diff.hunks.length;\n\n while (low <= high) {\n const mid = (low + high) >> 1;\n const counts = prefixCounts[mid + 1];\n if (counts == null) {\n throw new Error('iterateOverDiff: invalid hunk prefix index');\n }\n const selectedCount =\n diffStyle === 'unified' ? counts.unifiedCount : counts.splitCount;\n\n if (selectedCount > startingLine) {\n result = mid;\n high = mid - 1;\n } else {\n low = mid + 1;\n }\n }\n\n if (result >= diff.hunks.length) {\n const counts = prefixCounts[diff.hunks.length];\n if (counts == null) {\n throw new Error('iterateOverDiff: invalid terminal hunk prefix index');\n }\n return {\n hunkIndex: diff.hunks.length,\n splitCount: counts.splitCount,\n unifiedCount: counts.unifiedCount,\n };\n }\n\n const counts = prefixCounts[result];\n if (counts == null) {\n throw new Error('iterateOverDiff: invalid selected hunk prefix index');\n }\n return {\n hunkIndex: result,\n splitCount: counts.splitCount,\n unifiedCount: counts.unifiedCount,\n };\n}\n\n// Build cumulative rendered-row counts at every hunk boundary for the current\n// expansion state. Entry 0 is always zero rows before the first hunk; entry N\n// is the split/unified row count after hunks [0, N). These counts let\n// getIterationStartState binary-search by dense rendered row without replaying\n// every prior hunk.\nfunction getHunkPrefixCounts({\n diff,\n expandedHunks,\n collapsedContextThreshold,\n}: HunkPrefixCountsProps): HunkPrefixCounts[] {\n let splitCount = 0;\n let unifiedCount = 0;\n const finalHunkIndex = diff.hunks.length - 1;\n const prefixCounts: HunkPrefixCounts[] = [\n {\n splitCount: 0,\n unifiedCount: 0,\n },\n ];\n\n for (let index = 0; index < diff.hunks.length; index++) {\n const hunk = diff.hunks[index];\n if (hunk == null) {\n throw new Error('iterateOverDiff: invalid hunk summary index');\n }\n\n const leadingRegion = getExpandedRegion({\n isPartial: diff.isPartial,\n rangeSize: hunk.collapsedBefore,\n expandedHunks,\n hunkIndex: index,\n collapsedContextThreshold,\n });\n const leadingCount = leadingRegion.fromStart + leadingRegion.fromEnd;\n splitCount += leadingCount + hunk.splitLineCount;\n unifiedCount += leadingCount + hunk.unifiedLineCount;\n\n const trailingRegion =\n index === finalHunkIndex\n ? getTrailingExpandedRegion({\n fileDiff: diff,\n hunkIndex: index,\n expandedHunks,\n collapsedContextThreshold,\n errorPrefix: 'iterateOverDiff',\n })\n : undefined;\n if (trailingRegion != null) {\n const trailingCount = trailingRegion.fromStart + trailingRegion.fromEnd;\n splitCount += trailingCount;\n unifiedCount += trailingCount;\n }\n\n prefixCounts.push({ splitCount, unifiedCount });\n }\n\n return prefixCounts;\n}\n\n// Clip a run of context rows to a single bounded hull around the active rendered\n// window. `diffStyle: both` can make split and unified rows visible in disjoint\n// ranges, so these bounds may include interior gaps that still need per-row\n// filtering before emitting.\nfunction getContextLineIterationBounds(\n state: IterationState,\n count: number,\n diffStyle: DiffStyle\n): LineIterationBounds {\n if (!state.isWindowedHighlight || count <= 0) {\n return [0, count];\n }\n\n const ranges: LineIterationBounds[] = [];\n function pushRange(currentCount: number): void {\n const start = Math.max(0, state.viewportStart - currentCount);\n const end = Math.min(count, state.viewportEnd - currentCount);\n if (end > start) {\n ranges.push([start, end]);\n }\n }\n\n if (diffStyle !== 'split') {\n pushRange(state.unifiedCount);\n }\n if (diffStyle !== 'unified') {\n pushRange(state.splitCount);\n }\n\n if (ranges.length === 0) {\n return [0, 0];\n }\n\n let start = ranges[0][0];\n let end = ranges[0][1];\n for (let index = 1; index < ranges.length; index++) {\n const range = ranges[index];\n start = Math.min(start, range[0]);\n end = Math.max(end, range[1]);\n }\n return [start, end];\n}\n\n// Walk context rows through the active window while keeping split and\n// unified counters aligned. The callback only runs after the final per-row\n// window check, which keeps `diffStyle: both` gap rows from being emitted.\nfunction walkContextLines(\n state: IterationState,\n count: number,\n diffStyle: DiffStyle,\n callback: ContextLineCallback,\n onSkippedStart?: () => void,\n shouldBreak?: () => boolean\n): boolean {\n const [startIndex, endIndex] = getContextLineIterationBounds(\n state,\n count,\n diffStyle\n );\n if (startIndex > 0) {\n state.incrementCounts(startIndex, startIndex);\n onSkippedStart?.();\n }\n\n let index = startIndex;\n while (index < count) {\n if (shouldBreak?.() === true) {\n return true;\n }\n if (index >= endIndex) {\n state.incrementCounts(count - index, count - index);\n break;\n }\n if (state.isInWindow(0, 0)) {\n if (callback(index) === true) {\n return true;\n }\n } else {\n state.incrementCounts(1, 1);\n }\n index++;\n }\n\n return false;\n}\n\n// Clip a change block to the rows that can be visible in the active coordinate\n// space. `diffStyle: both` iterates in split row space, but includes the unified\n// ranges too so either view can render the visible change rows it needs.\nfunction getChangeIterationRanges(\n state: IterationState,\n content: ChangeContent,\n diffStyle: DiffStyle\n): LineIterationBounds[] {\n // If not a window highlight, then we should just render the entire range\n if (!state.isWindowedHighlight) {\n return [\n [\n 0,\n diffStyle === 'unified'\n ? content.deletions + content.additions\n : Math.max(content.deletions, content.additions),\n ],\n ];\n }\n const useUnified = diffStyle !== 'split';\n const useSplit = diffStyle !== 'unified';\n const iterationSpace = diffStyle === 'unified' ? 'unified' : 'split';\n const iterationRanges: LineIterationBounds[] = [];\n function getVisibleRange(\n start: number,\n count: number\n ): LineIterationBounds | undefined {\n const end = start + count;\n if (end <= state.viewportStart || start >= state.viewportEnd) {\n return undefined;\n }\n const visibleStart = Math.max(0, state.viewportStart - start);\n const visibleEnd = Math.min(count, state.viewportEnd - start);\n return visibleEnd > visibleStart ? [visibleStart, visibleEnd] : undefined;\n }\n function mapRangeToIteration(\n range: LineIterationBounds,\n kind: ChangeContentSide\n ): LineIterationBounds {\n if (iterationSpace === 'split') {\n // For split iteration, additions/deletions are already in split row space.\n return range;\n }\n return kind === 'additions'\n ? [range[0] + content.deletions, range[1] + content.deletions]\n : range;\n }\n function pushRange(\n range: LineIterationBounds | undefined,\n kind: ChangeContentSide\n ) {\n if (range == null) {\n return;\n }\n const [start, end] = mapRangeToIteration(range, kind);\n if (end > start) {\n iterationRanges.push([start, end]);\n }\n }\n\n if (useUnified) {\n pushRange(\n getVisibleRange(state.unifiedCount, content.deletions),\n 'deletions'\n );\n pushRange(\n getVisibleRange(\n state.unifiedCount + content.deletions,\n content.additions\n ),\n 'additions'\n );\n }\n\n if (useSplit) {\n pushRange(\n getVisibleRange(state.splitCount, content.deletions),\n 'deletions'\n );\n pushRange(\n getVisibleRange(state.splitCount, content.additions),\n 'additions'\n );\n }\n\n if (iterationRanges.length === 0) {\n return iterationRanges;\n }\n\n iterationRanges.sort((a, b) => a[0] - b[0]);\n const merged: LineIterationBounds[] = [iterationRanges[0]];\n for (const [start, end] of iterationRanges.slice(1)) {\n const last = merged[merged.length - 1];\n if (start <= last[1]) {\n last[1] = Math.max(last[1], end);\n } else {\n merged.push([start, end]);\n }\n }\n\n return merged;\n}\n\ninterface GetChangeLineDataProps {\n hunkIndex: number;\n hunk: Hunk;\n collapsedBefore: number;\n collapsedAfter: number;\n diffStyle: DiffStyle;\n index: number;\n unifiedLineIndex: number;\n splitLineIndex: number;\n additionLineIndex: number;\n additionLineNumber: number;\n deletionLineNumber: number;\n deletionLineIndex: number;\n content: ChangeContent;\n isLastContent: boolean;\n unifiedCount: number;\n splitCount: number;\n}\n\n// Build the callback payload for one change row, mapping the selected row index\n// into split/unified coordinates and addition/deletion line metadata.\nfunction getChangeLineData({\n hunkIndex,\n hunk,\n collapsedAfter,\n collapsedBefore,\n diffStyle,\n index,\n unifiedLineIndex,\n splitLineIndex,\n additionLineIndex,\n deletionLineIndex,\n additionLineNumber,\n deletionLineNumber,\n content,\n isLastContent,\n unifiedCount,\n splitCount,\n}: GetChangeLineDataProps): DiffLineCallbackProps {\n const unifiedDeletionLineIndex =\n index < content.deletions ? unifiedLineIndex + index : undefined;\n const unifiedAdditionLineIndex =\n diffStyle === 'unified'\n ? index >= content.deletions\n ? unifiedLineIndex + index\n : undefined\n : index < content.additions\n ? unifiedLineIndex + content.deletions + index\n : undefined;\n\n const resolvedSplitLineIndex =\n diffStyle === 'unified'\n ? splitLineIndex +\n (index < content.deletions ? index : index - content.deletions)\n : splitLineIndex + index;\n\n const deletionLineIndexValue =\n index < content.deletions ? deletionLineIndex + index : undefined;\n const deletionLineNumberValue =\n index < content.deletions ? deletionLineNumber + index : undefined;\n const additionLineIndexValue =\n diffStyle === 'unified'\n ? index >= content.deletions\n ? additionLineIndex + (index - content.deletions)\n : undefined\n : index < content.additions\n ? additionLineIndex + index\n : undefined;\n const additionLineNumberValue =\n diffStyle === 'unified'\n ? index >= content.deletions\n ? additionLineNumber + (index - content.deletions)\n : undefined\n : index < content.additions\n ? additionLineNumber + index\n : undefined;\n\n const noEOFCRDeletion =\n diffStyle === 'unified'\n ? isLastContent &&\n index === content.deletions - 1 &&\n hunk.noEOFCRDeletions\n : isLastContent && index === splitCount - 1 && hunk.noEOFCRDeletions;\n const noEOFCRAddition =\n diffStyle === 'unified'\n ? isLastContent && index === unifiedCount - 1 && hunk.noEOFCRAdditions\n : isLastContent && index === splitCount - 1 && hunk.noEOFCRAdditions;\n\n const deletionLine: DiffLineMetadata | undefined =\n deletionLineIndexValue != null &&\n deletionLineNumberValue != null &&\n unifiedDeletionLineIndex != null\n ? {\n lineNumber: deletionLineNumberValue,\n lineIndex: deletionLineIndexValue,\n noEOFCR: noEOFCRDeletion,\n unifiedLineIndex: unifiedDeletionLineIndex,\n splitLineIndex: resolvedSplitLineIndex,\n }\n : undefined;\n const additionLine: DiffLineMetadata | undefined =\n additionLineIndexValue != null &&\n additionLineNumberValue != null &&\n unifiedAdditionLineIndex != null\n ? {\n unifiedLineIndex: unifiedAdditionLineIndex,\n splitLineIndex: resolvedSplitLineIndex,\n lineIndex: additionLineIndexValue,\n lineNumber: additionLineNumberValue,\n noEOFCR: noEOFCRAddition,\n }\n : undefined;\n\n if (deletionLine == null && additionLine != null) {\n return {\n type: 'change',\n hunkIndex,\n hunk,\n collapsedAfter,\n collapsedBefore,\n deletionLine: undefined,\n additionLine,\n };\n } else if (deletionLine != null && additionLine == null) {\n return {\n type: 'change',\n hunkIndex,\n hunk,\n collapsedAfter,\n collapsedBefore,\n deletionLine,\n additionLine: undefined,\n };\n }\n\n if (deletionLine == null || additionLine == null) {\n throw new Error('iterateOverDiff: missing change line data');\n }\n\n return {\n type: 'change',\n hunkIndex,\n hunk,\n collapsedAfter,\n collapsedBefore,\n deletionLine,\n additionLine,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseMergeConflictDiffFromFile.d.ts","names":[],"sources":["../../src/utils/parseMergeConflictDiffFromFile.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"parseMergeConflictDiffFromFile.d.ts","names":[],"sources":["../../src/utils/parseMergeConflictDiffFromFile.ts"],"mappings":";;;UAsDiB,oCAAA;EACf,QAAA,EAAU,gBAAA;EACV,WAAA,EAAa,YAAA;EACb,YAAA,EAAc,YAAA;EACd,OAAA,GAAU,uBAAA;EACV,UAAA,EAAY,sBAAA;AAAA;AAAA,UAGG,uBAAA,SAAgC,uBAAuB;EAGtE,QAAA,EAAU,mBAAA;EACV,aAAA;EACA,WAAA;IACE,KAAA;IACA,IAAA;IACA,SAAA;IACA,GAAA;EAAA;AAAA;AAAA,UAIM,kCAAA;EACR,SAAA;EACA,SAAS;AAAA;AAAA,iBAqGK,4BAAA,CACd,MAAA,EAAQ,uBAAA,EACR,QAAA,EAAU,gBAAA,GACT,kCAAA;AAAA,iBAoBa,8BAAA,CACd,IAAA,EAAM,YAAA,EACN,eAAA,YACC,oCAAoC;AAAA,iBAg0BvB,4BAAA,CACd,QAAA,EAAU,gBAAA,EACV,OAAA,GAAU,uBAAA,kBACT,sBAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getHunkSideEndBoundary, getHunkSideStartBoundary } from "./getHunkSideBoundaries.js";
|
|
1
2
|
//#region src/utils/parseMergeConflictDiffFromFile.ts
|
|
2
3
|
function getMergeConflictActionAnchor(action, fileDiff) {
|
|
3
4
|
const hunk = fileDiff.hunks[action.hunkIndex];
|
|
@@ -49,7 +50,7 @@ function parseMergeConflictDiffFromFile(file, maxContextLines = 6) {
|
|
|
49
50
|
}
|
|
50
51
|
if (s.hunks.length > 0 && s.additionLines.length > 0 && s.deletionLines.length > 0) {
|
|
51
52
|
const lastHunk = s.hunks[s.hunks.length - 1];
|
|
52
|
-
const collapsedAfter = Math.max(s.additionLines.length - (lastHunk.additionStart
|
|
53
|
+
const collapsedAfter = Math.max(s.additionLines.length - getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount), 0);
|
|
53
54
|
s.splitLineCount += collapsedAfter;
|
|
54
55
|
s.unifiedLineCount += collapsedAfter;
|
|
55
56
|
}
|
|
@@ -217,20 +218,22 @@ function finalizeActiveHunk(s) {
|
|
|
217
218
|
hunkSplitLineCount += Math.max(content.additions, content.deletions);
|
|
218
219
|
hunkUnifiedLineCount += content.additions + content.deletions;
|
|
219
220
|
}
|
|
220
|
-
const
|
|
221
|
+
const additionStart = hunk.additionCount === 0 ? hunk.additionStart - 1 : hunk.additionStart;
|
|
222
|
+
const deletionStart = hunk.deletionCount === 0 ? hunk.deletionStart - 1 : hunk.deletionStart;
|
|
223
|
+
const collapsedBefore = Math.max(getHunkSideStartBoundary(additionStart, hunk.additionCount) - s.lastHunkEnd, 0);
|
|
221
224
|
const finalizedHunk = {
|
|
222
225
|
collapsedBefore,
|
|
223
|
-
additionStart
|
|
226
|
+
additionStart,
|
|
224
227
|
additionCount: hunk.additionCount,
|
|
225
228
|
additionLines: hunk.additionLines,
|
|
226
229
|
additionLineIndex: hunk.additionLineIndex,
|
|
227
|
-
deletionStart
|
|
230
|
+
deletionStart,
|
|
228
231
|
deletionCount: hunk.deletionCount,
|
|
229
232
|
deletionLines: hunk.deletionLines,
|
|
230
233
|
deletionLineIndex: hunk.deletionLineIndex,
|
|
231
234
|
hunkContent: hunk.hunkContent,
|
|
232
235
|
hunkContext: void 0,
|
|
233
|
-
hunkSpecs: `@@ -${formatHunkRange(
|
|
236
|
+
hunkSpecs: `@@ -${formatHunkRange(deletionStart, hunk.deletionCount)} +${formatHunkRange(additionStart, hunk.additionCount)} @@\n`,
|
|
234
237
|
splitLineStart: s.splitLineCount + collapsedBefore,
|
|
235
238
|
splitLineCount: hunkSplitLineCount,
|
|
236
239
|
unifiedLineStart: s.unifiedLineCount + collapsedBefore,
|
|
@@ -241,7 +244,7 @@ function finalizeActiveHunk(s) {
|
|
|
241
244
|
s.hunks.push(finalizedHunk);
|
|
242
245
|
s.splitLineCount += collapsedBefore + hunkSplitLineCount;
|
|
243
246
|
s.unifiedLineCount += collapsedBefore + hunkUnifiedLineCount;
|
|
244
|
-
s.lastHunkEnd =
|
|
247
|
+
s.lastHunkEnd = getHunkSideEndBoundary(additionStart, hunk.additionCount);
|
|
245
248
|
}
|
|
246
249
|
function splitHunkWithBufferedContext(s) {
|
|
247
250
|
if (s.activeHunk == null) 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 33121550):\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';\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 (lastHunk.additionStart + lastHunk.additionCount - 1),\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 const collapsedBefore = Math.max(hunk.additionStart - 1 - s.lastHunkEnd, 0);\n const finalizedHunk: Hunk = {\n collapsedBefore,\n additionStart: hunk.additionStart,\n additionCount: hunk.additionCount,\n additionLines: hunk.additionLines,\n additionLineIndex: hunk.additionLineIndex,\n deletionStart: hunk.deletionStart,\n deletionCount: hunk.deletionCount,\n deletionLines: hunk.deletionLines,\n deletionLineIndex: hunk.deletionLineIndex,\n hunkContent: hunk.hunkContent,\n hunkContext: undefined,\n hunkSpecs: `@@ -${formatHunkRange(hunk.deletionStart, hunk.deletionCount)} +${formatHunkRange(hunk.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 = hunk.additionStart + hunk.additionCount - 1;\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 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsePatchFiles.d.ts","names":[],"sources":["../../src/utils/parsePatchFiles.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"parsePatchFiles.d.ts","names":[],"sources":["../../src/utils/parsePatchFiles.ts"],"mappings":";;;iBAiCgB,YAAA,CACd,IAAA,UACA,cAAA,WACA,YAAA,aACC,WAAW;AAAA,UAqEJ,kBAAA;EACR,QAAA;EACA,SAAA;EACA,OAAA,GAAU,YAAA;EACV,OAAA,GAAU,YAAY;EACtB,YAAA;AAAA;AAAA,iBAGc,WAAA,CACd,cAAA,UACA,OAAA,GAAU,kBAAA,GACT,gBAAgB;;;AAhFL;AAMb;;;;;iBAmjBe,eAAA,CACd,IAAA,UACA,cAAA,WACA,YAAA,aACC,WAAW"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ALTERNATE_FILE_NAMES_GIT, COMMIT_METADATA_SPLIT, FILENAME_HEADER_REGEX, FILENAME_HEADER_REGEX_GIT, GIT_DIFF_FILE_BREAK_REGEX, INDEX_LINE_METADATA } from "../constants.js";
|
|
2
|
+
import { getHunkSideEndBoundary, getHunkSideStartBoundary } from "./getHunkSideBoundaries.js";
|
|
2
3
|
import { cleanLastNewline } from "./cleanLastNewline.js";
|
|
3
4
|
import { detachString, releaseStringDetachBuffer } from "./detachString.js";
|
|
5
|
+
import { realignChangeContentBySimilarity } from "./realignChangeContent.js";
|
|
4
6
|
//#region src/utils/parsePatchFiles.ts
|
|
5
7
|
function processPatch(data, cacheKeyPrefix, throwOnError) {
|
|
6
8
|
try {
|
|
@@ -233,9 +235,9 @@ function _processFile(fileDiffString, { cacheKey, isGitDiff = GIT_DIFF_FILE_BREA
|
|
|
233
235
|
if (throwOnError && (parsedAdditionLines !== hunkData.additionCount || parsedDeletionLines !== hunkData.deletionCount)) throw Error("parsePatchContent: hunk line count mismatch");
|
|
234
236
|
hunkData.additionLines = additionLines;
|
|
235
237
|
hunkData.deletionLines = deletionLines;
|
|
236
|
-
hunkData.collapsedBefore = Math.max(hunkData.additionStart
|
|
238
|
+
hunkData.collapsedBefore = Math.max(getHunkSideStartBoundary(hunkData.additionStart, hunkData.additionCount) - lastHunkEnd, 0);
|
|
237
239
|
currentFile.hunks.push(hunkData);
|
|
238
|
-
lastHunkEnd = hunkData.additionStart
|
|
240
|
+
lastHunkEnd = getHunkSideEndBoundary(hunkData.additionStart, hunkData.additionCount);
|
|
239
241
|
for (const content of hunkData.hunkContent) if (content.type === "context") {
|
|
240
242
|
hunkData.splitLineCount += content.lines;
|
|
241
243
|
hunkData.unifiedLineCount += content.lines;
|
|
@@ -252,7 +254,7 @@ function _processFile(fileDiffString, { cacheKey, isGitDiff = GIT_DIFF_FILE_BREA
|
|
|
252
254
|
if (throwOnError && isPartial && !isGitDiff && currentFile.hunks.length === 0) throw Error("parsePatchContent: unified file has no hunks");
|
|
253
255
|
if (currentFile.hunks.length > 0 && !isPartial && currentFile.additionLines.length > 0 && currentFile.deletionLines.length > 0) {
|
|
254
256
|
const lastHunk = currentFile.hunks[currentFile.hunks.length - 1];
|
|
255
|
-
const lastHunkEnd = lastHunk.additionStart
|
|
257
|
+
const lastHunkEnd = getHunkSideEndBoundary(lastHunk.additionStart, lastHunk.additionCount);
|
|
256
258
|
const totalFileLines = currentFile.additionLines.length;
|
|
257
259
|
const collapsedAfter = Math.max(totalFileLines - lastHunkEnd, 0);
|
|
258
260
|
currentFile.splitLineCount += collapsedAfter;
|
|
@@ -265,6 +267,7 @@ function _processFile(fileDiffString, { cacheKey, isGitDiff = GIT_DIFF_FILE_BREA
|
|
|
265
267
|
else if (oldFile != null && oldFile.contents !== "" && (newFile == null || newFile.contents === "")) currentFile.type = "deleted";
|
|
266
268
|
}
|
|
267
269
|
if (currentFile.type !== "rename-pure" && currentFile.type !== "rename-changed") currentFile.prevName = void 0;
|
|
270
|
+
realignChangeContentBySimilarity(currentFile);
|
|
268
271
|
return currentFile;
|
|
269
272
|
}
|
|
270
273
|
/**
|