UNPKG

@pierre/diffs

Version:

489 lines (487 loc) 20.1 kB
import { DEFAULT_COLLAPSED_CONTEXT_THRESHOLD } from "../constants.js"; import { getExpandedRegion, getTrailingExpandedRegion } from "./virtualDiffLayout.js"; //#region src/utils/iterateOverDiff.ts function iterateOverDiff({ diff, diffStyle, startingLine = 0, totalLines = Infinity, expandedHunks, collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD, callback }) { const iterationStart = getIterationStartState({ diff, diffStyle, startingLine, expandedHunks, collapsedContextThreshold }); const state = { viewportStart: startingLine, viewportEnd: startingLine + totalLines, isWindowedHighlight: startingLine > 0 || totalLines < Infinity, splitCount: iterationStart.splitCount, unifiedCount: iterationStart.unifiedCount, finalHunkIndex: diff.hunks.length - 1, shouldBreak() { if (!state.isWindowedHighlight) return false; const breakUnified = state.unifiedCount >= startingLine + totalLines; const breakSplit = state.splitCount >= startingLine + totalLines; if (diffStyle === "unified") return breakUnified; else if (diffStyle === "split") return breakSplit; else return breakUnified && breakSplit; }, shouldSkip(unifiedHeight, splitHeight) { if (!state.isWindowedHighlight) return false; const skipUnified = state.unifiedCount + unifiedHeight < startingLine; const skipSplit = state.splitCount + splitHeight < startingLine; if (diffStyle === "unified") return skipUnified; else if (diffStyle === "split") return skipSplit; else return skipUnified && skipSplit; }, incrementCounts(unifiedValue, splitValue) { if (diffStyle === "unified" || diffStyle === "both") state.unifiedCount += unifiedValue; if (diffStyle === "split" || diffStyle === "both") state.splitCount += splitValue; }, isInWindow(unifiedHeight, splitHeight) { if (!state.isWindowedHighlight) return true; const unifiedInWindow = state.isInUnifiedWindow(unifiedHeight); const splitInWindow = state.isInSplitWindow(splitHeight); if (diffStyle === "unified") return unifiedInWindow; else if (diffStyle === "split") return splitInWindow; else return unifiedInWindow || splitInWindow; }, isInUnifiedWindow(unifiedHeight) { return !state.isWindowedHighlight || state.unifiedCount >= startingLine - unifiedHeight && state.unifiedCount < startingLine + totalLines; }, isInSplitWindow(splitHeight) { return !state.isWindowedHighlight || state.splitCount >= startingLine - splitHeight && state.splitCount < startingLine + totalLines; }, emit(props, silent = false) { if (!silent) if (diffStyle === "unified") state.incrementCounts(1, 0); else if (diffStyle === "split") state.incrementCounts(0, 1); else state.incrementCounts(1, 1); return callback(props) ?? false; } }; hunkIterator: for (let hunkIndex = iterationStart.hunkIndex; hunkIndex < diff.hunks.length; hunkIndex++) { const hunk = diff.hunks[hunkIndex]; if (hunk == null) throw new Error("iterateOverDiff: invalid hunk index"); if (state.shouldBreak()) break; const leadingRegion = getExpandedRegion({ isPartial: diff.isPartial, rangeSize: hunk.collapsedBefore, expandedHunks, hunkIndex, collapsedContextThreshold }); const trailingRegion = hunkIndex === state.finalHunkIndex ? getTrailingExpandedRegion({ fileDiff: diff, hunkIndex, expandedHunks, collapsedContextThreshold, errorPrefix: "iterateOverDiff" }) : void 0; const expandedLineCount = leadingRegion.fromStart + leadingRegion.fromEnd; function getTrailingCollapsedAfter(unifiedLineIndex$1, splitLineIndex$1) { if (trailingRegion == null || trailingRegion.collapsedLines <= 0 || trailingRegion.fromStart + trailingRegion.fromEnd > 0) return 0; if (diffStyle === "unified") return unifiedLineIndex$1 === hunk.unifiedLineStart + hunk.unifiedLineCount - 1 ? trailingRegion.collapsedLines : 0; return splitLineIndex$1 === hunk.splitLineStart + hunk.splitLineCount - 1 ? trailingRegion.collapsedLines : 0; } let consumedCollapsed = leadingRegion.collapsedLines === 0; function consumePendingCollapsed() { if (consumedCollapsed) return 0; consumedCollapsed = true; return leadingRegion.collapsedLines; } if (!state.shouldSkip(expandedLineCount, expandedLineCount)) { let unifiedLineIndex$1 = hunk.unifiedLineStart - leadingRegion.rangeSize; let splitLineIndex$1 = hunk.splitLineStart - leadingRegion.rangeSize; let deletionLineIndex$1 = hunk.deletionLineIndex - leadingRegion.rangeSize; let additionLineIndex$1 = hunk.additionLineIndex - leadingRegion.rangeSize; let deletionLineNumber$1 = hunk.deletionStart - leadingRegion.rangeSize; let additionLineNumber$1 = hunk.additionStart - leadingRegion.rangeSize; if (walkContextLines(state, leadingRegion.fromStart, diffStyle, (index) => { return state.emit({ hunkIndex, hunk, collapsedBefore: 0, collapsedAfter: 0, type: "context-expanded", deletionLine: { lineNumber: deletionLineNumber$1 + index, lineIndex: deletionLineIndex$1 + index, noEOFCR: false, unifiedLineIndex: unifiedLineIndex$1 + index, splitLineIndex: splitLineIndex$1 + index }, additionLine: { unifiedLineIndex: unifiedLineIndex$1 + index, splitLineIndex: splitLineIndex$1 + index, lineIndex: additionLineIndex$1 + index, lineNumber: additionLineNumber$1 + index, noEOFCR: false } }); })) break hunkIterator; unifiedLineIndex$1 = hunk.unifiedLineStart - leadingRegion.fromEnd; splitLineIndex$1 = hunk.splitLineStart - leadingRegion.fromEnd; deletionLineIndex$1 = hunk.deletionLineIndex - leadingRegion.fromEnd; additionLineIndex$1 = hunk.additionLineIndex - leadingRegion.fromEnd; deletionLineNumber$1 = hunk.deletionStart - leadingRegion.fromEnd; additionLineNumber$1 = hunk.additionStart - leadingRegion.fromEnd; if (walkContextLines(state, leadingRegion.fromEnd, diffStyle, (index) => { return state.emit({ hunkIndex, hunk, collapsedBefore: consumePendingCollapsed(), collapsedAfter: 0, type: "context-expanded", deletionLine: { lineNumber: deletionLineNumber$1 + index, lineIndex: deletionLineIndex$1 + index, noEOFCR: false, unifiedLineIndex: unifiedLineIndex$1 + index, splitLineIndex: splitLineIndex$1 + index }, additionLine: { unifiedLineIndex: unifiedLineIndex$1 + index, splitLineIndex: splitLineIndex$1 + index, lineIndex: additionLineIndex$1 + index, lineNumber: additionLineNumber$1 + index, noEOFCR: false } }); }, () => { consumePendingCollapsed(); })) break hunkIterator; } else { state.incrementCounts(expandedLineCount, expandedLineCount); consumePendingCollapsed(); } let unifiedLineIndex = hunk.unifiedLineStart; let splitLineIndex = hunk.splitLineStart; let deletionLineIndex = hunk.deletionLineIndex; let additionLineIndex = hunk.additionLineIndex; let deletionLineNumber = hunk.deletionStart; let additionLineNumber = hunk.additionStart; const lastContent = hunk.hunkContent.at(-1); for (const content of hunk.hunkContent) { if (state.shouldBreak()) break hunkIterator; const isLastContent = content === lastContent; if (content.type === "context") { if (!state.shouldSkip(content.lines, content.lines)) { if (walkContextLines(state, content.lines, diffStyle, (index) => { const isLastLine = isLastContent && index === content.lines - 1; const unifiedRowIndex = unifiedLineIndex + index; const splitRowIndex = splitLineIndex + index; return state.emit({ hunkIndex, hunk, collapsedBefore: consumePendingCollapsed(), collapsedAfter: getTrailingCollapsedAfter(unifiedRowIndex, splitRowIndex), type: "context", deletionLine: { lineNumber: deletionLineNumber + index, lineIndex: deletionLineIndex + index, noEOFCR: isLastLine && hunk.noEOFCRDeletions, unifiedLineIndex: unifiedRowIndex, splitLineIndex: splitRowIndex }, additionLine: { unifiedLineIndex: unifiedRowIndex, splitLineIndex: splitRowIndex, lineIndex: additionLineIndex + index, lineNumber: additionLineNumber + index, noEOFCR: isLastLine && hunk.noEOFCRAdditions } }); }, () => { consumePendingCollapsed(); })) break hunkIterator; } else { state.incrementCounts(content.lines, content.lines); consumePendingCollapsed(); } unifiedLineIndex += content.lines; splitLineIndex += content.lines; deletionLineIndex += content.lines; additionLineIndex += content.lines; deletionLineNumber += content.lines; additionLineNumber += content.lines; } else { const splitCount = Math.max(content.deletions, content.additions); const unifiedCount = content.deletions + content.additions; if (!state.shouldSkip(unifiedCount, splitCount)) { const iterationRanges = getChangeIterationRanges(state, content, diffStyle); if ((iterationRanges[0]?.[0] ?? 0) > 0) consumePendingCollapsed(); for (const [rangeStart, rangeEnd] of iterationRanges) for (let index = rangeStart; index < rangeEnd; index++) { const collapsedAfter = getTrailingCollapsedAfter(unifiedLineIndex + index, diffStyle === "unified" ? splitLineIndex + (index < content.deletions ? index : index - content.deletions) : splitLineIndex + index); if (state.emit(getChangeLineData({ hunkIndex, hunk, collapsedBefore: consumePendingCollapsed(), collapsedAfter, diffStyle, index, unifiedLineIndex, splitLineIndex, additionLineIndex, deletionLineIndex, additionLineNumber, deletionLineNumber, content, isLastContent, unifiedCount, splitCount }), true)) break hunkIterator; } } consumePendingCollapsed(); state.incrementCounts(unifiedCount, splitCount); unifiedLineIndex += unifiedCount; splitLineIndex += splitCount; deletionLineIndex += content.deletions; additionLineIndex += content.additions; deletionLineNumber += content.deletions; additionLineNumber += content.additions; } } if (trailingRegion != null) { const { collapsedLines, fromStart, fromEnd } = trailingRegion; const len = fromStart + fromEnd; if (walkContextLines(state, len, diffStyle, (index) => { const isLastLine = index === len - 1; return state.emit({ hunkIndex: diff.hunks.length, hunk: void 0, collapsedBefore: 0, collapsedAfter: isLastLine ? collapsedLines : 0, type: "context-expanded", deletionLine: { lineNumber: deletionLineNumber + index, lineIndex: deletionLineIndex + index, noEOFCR: false, unifiedLineIndex: unifiedLineIndex + index, splitLineIndex: splitLineIndex + index }, additionLine: { unifiedLineIndex: unifiedLineIndex + index, splitLineIndex: splitLineIndex + index, lineIndex: additionLineIndex + index, lineNumber: additionLineNumber + index, noEOFCR: false } }); }, void 0, () => state.shouldBreak())) break hunkIterator; } } } function getIterationStartState({ diff, diffStyle, startingLine, expandedHunks, collapsedContextThreshold }) { if (startingLine <= 0 || diffStyle === "both") return { hunkIndex: 0, splitCount: 0, unifiedCount: 0 }; const prefixCounts = getHunkPrefixCounts({ diff, expandedHunks, collapsedContextThreshold }); let low = 0; let high = diff.hunks.length - 1; let result = diff.hunks.length; while (low <= high) { const mid = low + high >> 1; const counts$1 = prefixCounts[mid + 1]; if (counts$1 == null) throw new Error("iterateOverDiff: invalid hunk prefix index"); if ((diffStyle === "unified" ? counts$1.unifiedCount : counts$1.splitCount) > startingLine) { result = mid; high = mid - 1; } else low = mid + 1; } if (result >= diff.hunks.length) { const counts$1 = prefixCounts[diff.hunks.length]; if (counts$1 == null) throw new Error("iterateOverDiff: invalid terminal hunk prefix index"); return { hunkIndex: diff.hunks.length, splitCount: counts$1.splitCount, unifiedCount: counts$1.unifiedCount }; } const counts = prefixCounts[result]; if (counts == null) throw new Error("iterateOverDiff: invalid selected hunk prefix index"); return { hunkIndex: result, splitCount: counts.splitCount, unifiedCount: counts.unifiedCount }; } function getHunkPrefixCounts({ diff, expandedHunks, collapsedContextThreshold }) { let splitCount = 0; let unifiedCount = 0; const finalHunkIndex = diff.hunks.length - 1; const prefixCounts = [{ splitCount: 0, unifiedCount: 0 }]; for (let index = 0; index < diff.hunks.length; index++) { const hunk = diff.hunks[index]; if (hunk == null) throw new Error("iterateOverDiff: invalid hunk summary index"); const leadingRegion = getExpandedRegion({ isPartial: diff.isPartial, rangeSize: hunk.collapsedBefore, expandedHunks, hunkIndex: index, collapsedContextThreshold }); const leadingCount = leadingRegion.fromStart + leadingRegion.fromEnd; splitCount += leadingCount + hunk.splitLineCount; unifiedCount += leadingCount + hunk.unifiedLineCount; const trailingRegion = index === finalHunkIndex ? getTrailingExpandedRegion({ fileDiff: diff, hunkIndex: index, expandedHunks, collapsedContextThreshold, errorPrefix: "iterateOverDiff" }) : void 0; if (trailingRegion != null) { const trailingCount = trailingRegion.fromStart + trailingRegion.fromEnd; splitCount += trailingCount; unifiedCount += trailingCount; } prefixCounts.push({ splitCount, unifiedCount }); } return prefixCounts; } function getContextLineIterationBounds(state, count, diffStyle) { if (!state.isWindowedHighlight || count <= 0) return [0, count]; const ranges = []; function pushRange(currentCount) { const start$1 = Math.max(0, state.viewportStart - currentCount); const end$1 = Math.min(count, state.viewportEnd - currentCount); if (end$1 > start$1) ranges.push([start$1, end$1]); } if (diffStyle !== "split") pushRange(state.unifiedCount); if (diffStyle !== "unified") pushRange(state.splitCount); if (ranges.length === 0) return [0, 0]; let start = ranges[0][0]; let end = ranges[0][1]; for (let index = 1; index < ranges.length; index++) { const range = ranges[index]; start = Math.min(start, range[0]); end = Math.max(end, range[1]); } return [start, end]; } function walkContextLines(state, count, diffStyle, callback, onSkippedStart, shouldBreak) { const [startIndex, endIndex] = getContextLineIterationBounds(state, count, diffStyle); if (startIndex > 0) { state.incrementCounts(startIndex, startIndex); onSkippedStart?.(); } let index = startIndex; while (index < count) { if (shouldBreak?.() === true) return true; if (index >= endIndex) { state.incrementCounts(count - index, count - index); break; } if (state.isInWindow(0, 0)) { if (callback(index) === true) return true; } else state.incrementCounts(1, 1); index++; } return false; } function getChangeIterationRanges(state, content, diffStyle) { if (!state.isWindowedHighlight) return [[0, diffStyle === "unified" ? content.deletions + content.additions : Math.max(content.deletions, content.additions)]]; const useUnified = diffStyle !== "split"; const useSplit = diffStyle !== "unified"; const iterationSpace = diffStyle === "unified" ? "unified" : "split"; const iterationRanges = []; function getVisibleRange(start, count) { if (start + count <= state.viewportStart || start >= state.viewportEnd) return; const visibleStart = Math.max(0, state.viewportStart - start); const visibleEnd = Math.min(count, state.viewportEnd - start); return visibleEnd > visibleStart ? [visibleStart, visibleEnd] : void 0; } function mapRangeToIteration(range, kind) { if (iterationSpace === "split") return range; return kind === "additions" ? [range[0] + content.deletions, range[1] + content.deletions] : range; } function pushRange(range, kind) { if (range == null) return; const [start, end] = mapRangeToIteration(range, kind); if (end > start) iterationRanges.push([start, end]); } if (useUnified) { pushRange(getVisibleRange(state.unifiedCount, content.deletions), "deletions"); pushRange(getVisibleRange(state.unifiedCount + content.deletions, content.additions), "additions"); } if (useSplit) { pushRange(getVisibleRange(state.splitCount, content.deletions), "deletions"); pushRange(getVisibleRange(state.splitCount, content.additions), "additions"); } if (iterationRanges.length === 0) return iterationRanges; iterationRanges.sort((a, b) => a[0] - b[0]); const merged = [iterationRanges[0]]; for (const [start, end] of iterationRanges.slice(1)) { const last = merged[merged.length - 1]; if (start <= last[1]) last[1] = Math.max(last[1], end); else merged.push([start, end]); } return merged; } function getChangeLineData({ hunkIndex, hunk, collapsedAfter, collapsedBefore, diffStyle, index, unifiedLineIndex, splitLineIndex, additionLineIndex, deletionLineIndex, additionLineNumber, deletionLineNumber, content, isLastContent, unifiedCount, splitCount }) { const unifiedDeletionLineIndex = index < content.deletions ? unifiedLineIndex + index : void 0; const unifiedAdditionLineIndex = diffStyle === "unified" ? index >= content.deletions ? unifiedLineIndex + index : void 0 : index < content.additions ? unifiedLineIndex + content.deletions + index : void 0; const resolvedSplitLineIndex = diffStyle === "unified" ? splitLineIndex + (index < content.deletions ? index : index - content.deletions) : splitLineIndex + index; const deletionLineIndexValue = index < content.deletions ? deletionLineIndex + index : void 0; const deletionLineNumberValue = index < content.deletions ? deletionLineNumber + index : void 0; const additionLineIndexValue = diffStyle === "unified" ? index >= content.deletions ? additionLineIndex + (index - content.deletions) : void 0 : index < content.additions ? additionLineIndex + index : void 0; const additionLineNumberValue = diffStyle === "unified" ? index >= content.deletions ? additionLineNumber + (index - content.deletions) : void 0 : index < content.additions ? additionLineNumber + index : void 0; const noEOFCRDeletion = diffStyle === "unified" ? isLastContent && index === content.deletions - 1 && hunk.noEOFCRDeletions : isLastContent && index === splitCount - 1 && hunk.noEOFCRDeletions; const noEOFCRAddition = diffStyle === "unified" ? isLastContent && index === unifiedCount - 1 && hunk.noEOFCRAdditions : isLastContent && index === splitCount - 1 && hunk.noEOFCRAdditions; const deletionLine = deletionLineIndexValue != null && deletionLineNumberValue != null && unifiedDeletionLineIndex != null ? { lineNumber: deletionLineNumberValue, lineIndex: deletionLineIndexValue, noEOFCR: noEOFCRDeletion, unifiedLineIndex: unifiedDeletionLineIndex, splitLineIndex: resolvedSplitLineIndex } : void 0; const additionLine = additionLineIndexValue != null && additionLineNumberValue != null && unifiedAdditionLineIndex != null ? { unifiedLineIndex: unifiedAdditionLineIndex, splitLineIndex: resolvedSplitLineIndex, lineIndex: additionLineIndexValue, lineNumber: additionLineNumberValue, noEOFCR: noEOFCRAddition } : void 0; if (deletionLine == null && additionLine != null) return { type: "change", hunkIndex, hunk, collapsedAfter, collapsedBefore, deletionLine: void 0, additionLine }; else if (deletionLine != null && additionLine == null) return { type: "change", hunkIndex, hunk, collapsedAfter, collapsedBefore, deletionLine, additionLine: void 0 }; if (deletionLine == null || additionLine == null) throw new Error("iterateOverDiff: missing change line data"); return { type: "change", hunkIndex, hunk, collapsedAfter, collapsedBefore, deletionLine, additionLine }; } //#endregion export { iterateOverDiff }; //# sourceMappingURL=iterateOverDiff.js.map