UNPKG

@pierre/diffs

Version:

1,142 lines 52.4 kB
import "../constants.js";
import { areObjectsEqual } from "../utils/areObjectsEqual.js";
import { areOptionsEqual } from "../utils/areOptionsEqual.js";
import { computeVirtualFileMetrics, getVirtualFileHeaderRegion, getVirtualFilePaddingBottom } from "../utils/computeVirtualFileMetrics.js";
import { FILE_ANNOTATION_DOM_KEY, includesFileAnnotations, shouldRenderFileAnnotations } from "../utils/includesFileAnnotations.js";
import { areDiffTargetsEqual } from "../utils/areDiffTargetsEqual.js";
import { areFilesEqual } from "../utils/areFilesEqual.js";
import { awaitWithTimeout } from "../utils/awaitWithTimeout.js";
import { getExpandedRegion, getLeadingHunkSeparatorLayout, getTrailingExpandedRegion, getTrailingHunkSeparatorLayout, isAdditionLineRenderable } from "../utils/virtualDiffLayout.js";
import { computeEstimatedDiffHeights } from "../utils/computeEstimatedDiffHeights.js";
import { getDiffFileInput } from "../utils/getDiffFileInput.js";
import { hydratePartialDiff } from "../utils/hydratePartialDiff.js";
import { iterateOverDiff } from "../utils/iterateOverDiff.js";
import { parseDiffFromFile } from "../utils/parseDiffFromFile.js";
import { FileDiff } from "./FileDiff.js";
//#region src/components/VirtualizedFileDiff.ts
const VIRTUALIZED_FILE_DIFF_LAYOUT_CHECKPOINT_INTERVAL = 3e3;
const NO_GHOST_TEXT_ROWS = /* @__PURE__ */ new Map();
let instanceId = -1;
var VirtualizedFileDiff = class extends FileDiff {
	__id = `little-virtualized-file-diff:${++instanceId}`;
	renderType = "virtualized";
	top;
	height = 0;
	metrics;
	cache = {
		heightDeltas: /* @__PURE__ */ new Map(),
		measuredHeightDeltaTotal: 0,
		estimatedSplitHeight: void 0,
		estimatedUnifiedHeight: void 0,
		checkpoints: [],
		totalLines: 0,
		fileAnnotationHeight: 0,
		ghostTextRows: NO_GHOST_TEXT_ROWS,
		ghostTextRowsByIndex: /* @__PURE__ */ new Map()
	};
	isVisible = false;
	isSetup = false;
	virtualizer;
	layoutDirty = true;
	forceRenderOverride;
	currentCollapsed;
	pendingHydratedDiff;
	pendingExpansions;
	pendingRender;
	constructor(options, virtualizer, metrics, workerManager, isContainerManaged = false) {
		super(options, workerManager, isContainerManaged);
		this.virtualizer = virtualizer;
		this.metrics = computeVirtualFileMetrics(metrics);
	}
	setMetrics(metrics, force = false) {
		const nextMetrics = computeVirtualFileMetrics(metrics);
		if (!force && areObjectsEqual(this.metrics, nextMetrics)) return;
		this.metrics = nextMetrics;
		this.resetLayoutCache({ includeEstimatedHeights: true });
	}
	setLineAnnotations(lineAnnotations) {
		if (this.syncLineAnnotations(lineAnnotations)) this.resetLayoutCache({ includeEstimatedHeights: false });
	}
	syncLineAnnotations(lineAnnotations) {
		if (lineAnnotations == null || !this.isNewAnnotations(lineAnnotations) || lineAnnotations.length === 0 && this.getLatestAnnotations().length === 0) return false;
		super.setLineAnnotations(lineAnnotations);
		return true;
	}
	syncEditSessionAnnotationsFromEditor(lineAnnotations) {
		if (super.syncEditSessionAnnotationsFromEditor(lineAnnotations)) {
			this.resetLayoutCache({ includeEstimatedHeights: false });
			return true;
		}
		return false;
	}
	setFileAnnotationHeight(nextHeight) {
		const previousHeight = this.cache.fileAnnotationHeight;
		if (nextHeight === previousHeight) return false;
		this.cache.fileAnnotationHeight = nextHeight;
		this.cache.measuredHeightDeltaTotal += nextHeight - previousHeight;
		return true;
	}
	hasFileAnnotations(fileDiff) {
		const lineAnnotations = this.getLatestAnnotations();
		if (!includesFileAnnotations(lineAnnotations)) return false;
		return lineAnnotations.some((annotation) => {
			if (annotation.lineNumber !== 0) return false;
			if (fileDiff.type === "new") return annotation.side === "additions";
			if (fileDiff.type === "deleted") return annotation.side === "deletions";
			return true;
		});
	}
	getLineHeight(lineIndex, hasMetadataLine = false) {
		return this.getEstimatedLineHeight(hasMetadataLine) + (this.cache.heightDeltas.get(lineIndex) ?? 0);
	}
	getEstimatedLineHeight(hasMetadataLine = false) {
		const multiplier = hasMetadataLine ? 2 : 1;
		return this.metrics.lineHeight * multiplier;
	}
	setOptions(options) {
		if (this.isAdvancedMode()) throw new Error("VirtualizedFileDiff.setOptions cannot be used inside CodeView. Update CodeView options instead.");
		if (options == null) return;
		const { options: previousOptions } = this;
		const optionsChanged = !areOptionsEqual(previousOptions, options);
		const layoutChanged = optionsChanged && hasDiffLayoutOptionChanged(previousOptions, options);
		super.setOptions(options);
		if (layoutChanged) this.resetLayoutCache({
			forceSimpleRecompute: true,
			includeEstimatedHeights: hasDiffEstimateOptionChanged(previousOptions, options)
		});
		if (optionsChanged) this.forceRenderOverride = true;
		if (optionsChanged && this.isSimpleMode()) this.virtualizer.instanceChanged(this, layoutChanged);
	}
	setThemeType(themeType) {
		if (this.isAdvancedMode()) throw new Error("VirtualizedFileDiff.setThemeType cannot be used inside CodeView. Update CodeView options instead.");
		super.setThemeType(themeType);
	}
	resetLayoutCache({ forceSimpleRecompute = false, includeEstimatedHeights = false, resetRenderRange = true } = {}) {
		this.layoutDirty = true;
		this.cache.fileAnnotationHeight = 0;
		this.cache.heightDeltas.clear();
		this.cache.measuredHeightDeltaTotal = 0;
		this.cache.ghostTextRows = NO_GHOST_TEXT_ROWS;
		if (this.cache.ghostTextRowsByIndex.size > 0) this.cache.ghostTextRowsByIndex.clear();
		this.invalidateDerivedLayoutCache(includeEstimatedHeights, resetRenderRange);
		if (forceSimpleRecompute && this.isSimpleMode()) this.computeApproximateSize();
	}
	invalidateDerivedLayoutCache(includeEstimatedHeights, resetRenderRange = true) {
		this.layoutDirty = true;
		if (this.cache.checkpoints.length > 0) this.cache.checkpoints.length = 0;
		if (this.cache.totalLines !== 0) this.cache.totalLines = 0;
		if (includeEstimatedHeights) {
			this.cache.estimatedSplitHeight = void 0;
			this.cache.estimatedUnifiedHeight = void 0;
		}
		if (this.renderRange != null && resetRenderRange) this.renderRange = void 0;
	}
	applyGhostTextRows(fileDiff, ghostTextRows) {
		const { cache: { heightDeltas, ghostTextRows: folded, ghostTextRowsByIndex: previous }, metrics: { lineHeight } } = this;
		if (folded === ghostTextRows) return false;
		const diffStyle = this.getDiffStyle();
		const next = /* @__PURE__ */ new Map();
		for (const [line, rows] of ghostTextRows) {
			const indexes = this.getLineIndexForDiff(fileDiff, line + 1, "additions");
			if (indexes == null) continue;
			next.set(diffStyle === "split" ? indexes[1] : indexes[0], rows);
		}
		let changed = false;
		for (const rowIndex of /* @__PURE__ */ new Set([...previous.keys(), ...next.keys()])) {
			const delta = ((next.get(rowIndex) ?? 0) - (previous.get(rowIndex) ?? 0)) * lineHeight;
			if (delta === 0) continue;
			const nextDelta = (heightDeltas.get(rowIndex) ?? 0) + delta;
			if (nextDelta === 0) heightDeltas.delete(rowIndex);
			else heightDeltas.set(rowIndex, nextDelta);
			this.cache.measuredHeightDeltaTotal += delta;
			changed = true;
		}
		this.cache.ghostTextRows = ghostTextRows;
		this.cache.ghostTextRowsByIndex = next;
		return changed;
	}
	reconcileHeights() {
		let hasHeightChange = false;
		const { options: { overflow = "scroll" }, cache: { ghostTextRowsByIndex }, metrics: { lineHeight } } = this;
		const fileDiff = this.getRenderedDiff();
		if (this.fileContainer == null || fileDiff == null) {
			if (this.height !== 0) hasHeightChange = true;
			this.height = 0;
			return hasHeightChange;
		}
		this.top = this.getVirtualizedTop();
		hasHeightChange = this.applyGhostTextRows(fileDiff, this.editor?.__getGhostTextRows() ?? NO_GHOST_TEXT_ROWS);
		const lineAnnotations = this.getLatestAnnotations();
		if (overflow === "scroll" && lineAnnotations.length === 0 && !this.isResizeDebuggingEnabled()) {
			if (hasHeightChange) this.computeApproximateSize(true);
			return hasHeightChange;
		}
		const diffStyle = this.getDiffStyle();
		const codeGroups = diffStyle === "split" ? [this.codeDeletions, this.codeAdditions] : [this.codeUnified];
		const hasFileAnnotations = this.hasFileAnnotations(fileDiff);
		if (this.renderRange != null && hasFileAnnotations && shouldRenderFileAnnotations(this.renderRange)) {
			const nextFileAnnotationHeight = measureFileAnnotationHeight(codeGroups) ?? 0;
			if (this.setFileAnnotationHeight(nextFileAnnotationHeight)) hasHeightChange = true;
		} else if (!hasFileAnnotations && this.setFileAnnotationHeight(0)) hasHeightChange = true;
		for (const codeGroup of codeGroups) {
			if (codeGroup == null) continue;
			const content = codeGroup.children[1];
			if (!(content instanceof HTMLElement)) continue;
			const skipsGhostRows = diffStyle === "split" && codeGroup === this.codeDeletions;
			for (const line of content.children) {
				if (!(line instanceof HTMLElement)) continue;
				const lineIndexAttr = line.dataset.lineIndex;
				if (lineIndexAttr == null) continue;
				const lineIndex = parseLineIndex(lineIndexAttr, diffStyle);
				const ghostRows = ghostTextRowsByIndex.get(lineIndex) ?? 0;
				if (skipsGhostRows && ghostRows > 0) continue;
				let measuredHeight = line.getBoundingClientRect().height + ghostRows * lineHeight;
				let hasMetadata = false;
				if (line.nextElementSibling instanceof HTMLElement && ("lineAnnotation" in line.nextElementSibling.dataset || "noNewline" in line.nextElementSibling.dataset)) {
					if ("noNewline" in line.nextElementSibling.dataset) hasMetadata = true;
					measuredHeight += line.nextElementSibling.getBoundingClientRect().height;
				}
				const estimatedHeight = this.getEstimatedLineHeight(hasMetadata);
				const previousDelta = this.cache.heightDeltas.get(lineIndex) ?? 0;
				const nextDelta = measuredHeight - estimatedHeight;
				if (nextDelta === previousDelta) continue;
				hasHeightChange = true;
				this.cache.measuredHeightDeltaTotal += nextDelta - previousDelta;
				if (nextDelta === 0) this.cache.heightDeltas.delete(lineIndex);
				else this.cache.heightDeltas.set(lineIndex, nextDelta);
			}
		}
		if (hasHeightChange || this.isResizeDebuggingEnabled()) this.computeApproximateSize(true);
		return hasHeightChange;
	}
	onRender = (dirty) => {
		if (this.fileContainer == null) return false;
		if (dirty) this.top = this.getVirtualizedTop();
		return this.render();
	};
	flushManagers() {
		super.flushManagers();
		if (this.lineStateRefreshPending) this.flushDeferredLineState();
	}
	updateCodeViewLayout(fileDiff, top, reset, lineAnnotations) {
		let resetLayoutCache = false;
		let resetEstimatedHeights = false;
		const { pendingExpansions, pendingHydratedDiff, options: { collapsed = false } } = this;
		if (pendingHydratedDiff != null) {
			this.pendingHydratedDiff = void 0;
			if (pendingHydratedDiff.expectedDiff === fileDiff) {
				Object.assign(fileDiff, pendingHydratedDiff.nextDiff);
				this.setHydratedState(pendingHydratedDiff.files);
				this.startHydratedEditSession(fileDiff);
				this.forceRenderOverride = true;
				resetLayoutCache = true;
				resetEstimatedHeights = true;
			}
		}
		if (pendingExpansions != null) {
			this.pendingExpansions = void 0;
			for (const { hunkIndex, direction, expansionLineCountOverride } of pendingExpansions) {
				this.hunksRenderer.expandHunk(hunkIndex, direction, expansionLineCountOverride);
				this.forceRenderOverride = true;
				resetEstimatedHeights = true;
			}
		}
		const diffChanged = this.updateExternalDiff(fileDiff, lineAnnotations);
		const { pendingRenderDiff, layoutDiffChanged, renderedDiffChanged, annotationsChanged } = this.updatePendingRender(fileDiff, lineAnnotations);
		if (!this.forceRenderOverride && (diffChanged || renderedDiffChanged || annotationsChanged)) this.forceRenderOverride = true;
		if (reset?.resetDiffLayoutCache === true || layoutDiffChanged || annotationsChanged) resetLayoutCache = true;
		if (layoutDiffChanged || reset?.resetDiffLayoutCache === true && reset.includeEstimatedDiffHeights) resetEstimatedHeights = true;
		if (reset?.metrics != null) {
			this.metrics = computeVirtualFileMetrics(reset.metrics);
			resetLayoutCache = true;
			resetEstimatedHeights = true;
		}
		if (this.currentCollapsed !== collapsed) {
			this.currentCollapsed = collapsed;
			resetLayoutCache = true;
		}
		if (resetLayoutCache) this.resetLayoutCache({ includeEstimatedHeights: resetEstimatedHeights });
		else if (resetEstimatedHeights) this.invalidateDerivedLayoutCache(true);
		this.top = top;
		this.computeApproximateSize(false, pendingRenderDiff);
		return this.height;
	}
	getLinePosition(lineNumber, side = "additions") {
		const fileDiff = this.getLayoutDiff();
		if (fileDiff == null || lineNumber < 1) return;
		const targetLineIndexes = this.getLineIndexForDiff(fileDiff, lineNumber, side);
		if (targetLineIndexes == null) return;
		const { disableFileHeader = false, expandUnchanged = false, collapsed = false, collapsedContextThreshold = 1 } = this.options;
		const diffStyle = this.getDiffStyle();
		const hunkSeparators = this.getHunkSeparatorType();
		const targetLineIndex = diffStyle === "split" ? targetLineIndexes[1] : targetLineIndexes[0];
		this.approximateLayoutCheckpoints(fileDiff);
		const headerRegion = getVirtualFileHeaderRegion(this.metrics, disableFileHeader);
		const checkpoint = this.getLayoutCheckpointBeforeLineIndex(targetLineIndex);
		let top = checkpoint?.top ?? headerRegion + this.cache.fileAnnotationHeight;
		if (collapsed) return {
			top: headerRegion,
			height: 0
		};
		let position;
		iterateOverDiff({
			diff: fileDiff,
			diffStyle,
			startingLine: checkpoint?.renderedLineIndex ?? 0,
			expandedHunks: expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap(),
			collapsedContextThreshold,
			callback: ({ hunkIndex, hunk, collapsedBefore, collapsedAfter, deletionLine, additionLine }) => {
				const lineIndex = diffStyle === "split" ? additionLine?.splitLineIndex ?? deletionLine?.splitLineIndex : additionLine?.unifiedLineIndex ?? deletionLine?.unifiedLineIndex;
				if (lineIndex == null) throw new Error("VirtualizedFileDiff.getLinePosition: missing line index data");
				if (collapsedBefore > 0) {
					const separator = getLeadingHunkSeparatorLayout({
						type: hunkSeparators,
						metrics: this.metrics,
						hunkIndex,
						hunkSpecs: hunk?.hunkSpecs
					});
					if (separator != null) {
						top += separator.gapBefore;
						if (targetLineIndex >= lineIndex - collapsedBefore && targetLineIndex < lineIndex) {
							position = {
								top,
								height: separator.height
							};
							return true;
						}
						top += separator.height + separator.gapAfter;
					}
				}
				const lineHeight = this.getLineHeight(lineIndex, (additionLine?.noEOFCR ?? false) || (deletionLine?.noEOFCR ?? false));
				if (lineIndex === targetLineIndex) {
					position = {
						top,
						height: lineHeight
					};
					return true;
				}
				top += lineHeight;
				if (collapsedAfter > 0) {
					const separator = getTrailingHunkSeparatorLayout({
						type: hunkSeparators,
						metrics: this.metrics
					});
					if (separator != null) {
						if (targetLineIndex > lineIndex && targetLineIndex <= lineIndex + collapsedAfter) {
							position = {
								top: top + separator.gapBefore,
								height: separator.height
							};
							return true;
						}
						top += separator.totalHeight;
					}
				}
				return false;
			}
		});
		return position;
	}
	getEditorViewport() {
		return this.virtualizer.type === "simple" ? this.virtualizer.getRoot() : this.virtualizer.getContainerElement();
	}
	getNumericScrollAnchor(localViewportTop) {
		const fileDiff = this.getLayoutDiff();
		if (fileDiff == null) return;
		const { disableFileHeader = false, expandUnchanged = false, collapsed = false, collapsedContextThreshold = 1 } = this.options;
		if (collapsed) return;
		const diffStyle = this.getDiffStyle();
		const hunkSeparators = this.getHunkSeparatorType();
		this.approximateLayoutCheckpoints(fileDiff);
		const checkpoint = this.getLayoutCheckpointBeforeTop(localViewportTop);
		let top = checkpoint?.top ?? getVirtualFileHeaderRegion(this.metrics, disableFileHeader) + this.cache.fileAnnotationHeight;
		let anchor;
		iterateOverDiff({
			diff: fileDiff,
			diffStyle,
			startingLine: checkpoint?.renderedLineIndex ?? 0,
			expandedHunks: expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap(),
			collapsedContextThreshold,
			callback: ({ hunkIndex, hunk, collapsedBefore, collapsedAfter, deletionLine, additionLine }) => {
				const lineIndex = diffStyle === "split" ? additionLine?.splitLineIndex ?? deletionLine?.splitLineIndex : additionLine?.unifiedLineIndex ?? deletionLine?.unifiedLineIndex;
				if (lineIndex == null) throw new Error("VirtualizedFileDiff.getNumericScrollAnchor: missing line index data");
				if (collapsedBefore > 0) {
					const separator = getLeadingHunkSeparatorLayout({
						type: hunkSeparators,
						metrics: this.metrics,
						hunkIndex,
						hunkSpecs: hunk?.hunkSpecs
					});
					if (separator != null) top += separator.totalHeight;
				}
				if (top >= localViewportTop) {
					if (deletionLine != null) anchor = {
						lineNumber: deletionLine.lineNumber,
						side: "deletions",
						top
					};
					else if (additionLine != null) anchor = {
						lineNumber: additionLine.lineNumber,
						side: "additions",
						top
					};
					if (anchor != null) return true;
				}
				const lineHeight = this.getLineHeight(lineIndex, (additionLine?.noEOFCR ?? false) || (deletionLine?.noEOFCR ?? false));
				top += lineHeight;
				if (collapsedAfter > 0) {
					const separator = getTrailingHunkSeparatorLayout({
						type: hunkSeparators,
						metrics: this.metrics
					});
					if (separator != null) top += separator.totalHeight;
				}
				return false;
			}
		});
		return anchor;
	}
	getVirtualizedHeight() {
		return this.height;
	}
	getAdvancedStickySpecs(windowSpecs) {
		const fileDiff = this.getLayoutDiff();
		if (this.top == null || fileDiff == null) return;
		if (this.options.collapsed === true) return {
			topOffset: this.top,
			height: this.height
		};
		const renderRange = windowSpecs != null ? this.computeRenderRangeFromWindow(fileDiff, this.top, windowSpecs) : this.renderRange;
		if (renderRange == null) return;
		const { bufferBefore, bufferAfter, totalLines } = renderRange;
		let headerOnlyOffset = 0;
		if (totalLines === 0) {
			const activeWindow = windowSpecs ?? this.virtualizer.getWindowSpecs();
			if (this.top < activeWindow.top) headerOnlyOffset = bufferAfter;
		}
		return {
			topOffset: this.top + bufferBefore + headerOnlyOffset,
			height: this.height - (bufferBefore + bufferAfter)
		};
	}
	cleanUp(recycle = false) {
		const hadGhostTextRows = this.cache.ghostTextRows.size > 0;
		if (hadGhostTextRows) this.layoutDirty = true;
		const shouldRecomputeLayout = recycle && this.isAdvancedMode() && this.fileContainer != null && (hadGhostTextRows || !areDiffTargetsEqual(this.getRenderedDiff(), this.getLatestDiff()));
		if (this.fileContainer != null && this.isSimpleMode()) this.getSimpleVirtualizer()?.disconnect(this.fileContainer);
		if (!recycle) {
			this.resetLayoutCache({ includeEstimatedHeights: true });
			this.pendingExpansions = void 0;
			this.pendingHydratedDiff = void 0;
		}
		this.pendingRender = void 0;
		this.isSetup = false;
		super.cleanUp(recycle);
		if (shouldRecomputeLayout) this.virtualizer.instanceChanged(this, true);
	}
	expandHunk = (hunkIndex, direction, expansionLineCountOverride) => {
		if (this.fileDiff == null) return;
		if (this.isAdvancedMode()) {
			this.pendingExpansions ??= [];
			this.pendingExpansions.push({
				hunkIndex,
				direction,
				expansionLineCountOverride
			});
		} else {
			this.hunksRenderer.expandHunk(hunkIndex, direction, expansionLineCountOverride);
			this.resetLayoutCache({ includeEstimatedHeights: true });
			this.computeApproximateSize();
		}
		this.loadFilesIfNecessary();
		this.forceRenderOverride = true;
		this.virtualizer.instanceChanged(this, true);
	};
	async handleFilesLoaded(expectedDiff, files) {
		if (this.fileDiff !== expectedDiff || !expectedDiff.isPartial) return;
		if (this.isAdvancedMode()) {
			const nextDiff = hydratePartialDiff("clone", expectedDiff, files);
			await awaitWithTimeout(() => this.primeHighlightCache(nextDiff));
			if (!this.enabled || this.fileDiff !== expectedDiff) return;
			this.pendingHydratedDiff = {
				expectedDiff,
				nextDiff,
				files
			};
		} else {
			hydratePartialDiff("merge", expectedDiff, files);
			this.setHydratedState(files);
			if (!this.startHydratedEditSession(expectedDiff)) {
				await awaitWithTimeout(() => this.primeHighlightCache(expectedDiff));
				if (!this.enabled || this.fileDiff !== expectedDiff) return;
			}
			this.resetLayoutCache({ includeEstimatedHeights: true });
			this.computeApproximateSize();
		}
		this.forceRenderOverride = true;
		this.virtualizer.instanceChanged(this, true);
	}
	loadFilesIfNecessary() {
		if (this.pendingHydratedDiff != null) {
			if (this.pendingHydratedDiff.expectedDiff === this.fileDiff) return;
			this.pendingHydratedDiff = void 0;
		}
		super.loadFilesIfNecessary();
	}
	isLineRenderable(lineNumber) {
		if (super.isLineRenderable(lineNumber)) return true;
		const { pendingExpansions } = this;
		const fileDiff = this.getRenderedDiff();
		if (pendingExpansions == null || pendingExpansions.length === 0 || fileDiff == null) return false;
		const { expansionLineCount = 100, collapsedContextThreshold = 1 } = this.options;
		const staged = new Map(this.hunksRenderer.getExpandedHunksMap());
		for (const expansion of pendingExpansions) {
			const region = { ...staged.get(expansion.hunkIndex) ?? {
				fromStart: 0,
				fromEnd: 0
			} };
			const count = expansion.expansionLineCountOverride ?? expansionLineCount;
			if (expansion.direction === "up" || expansion.direction === "both") region.fromStart += count;
			if (expansion.direction === "down" || expansion.direction === "both") region.fromEnd += count;
			staged.set(expansion.hunkIndex, region);
		}
		return isAdditionLineRenderable({
			fileDiff,
			lineNumber,
			expandedHunks: staged,
			collapsedContextThreshold
		});
	}
	/**
	* Invalidate layout after an edit session changed the rendered row set
	* without a line-count change (a mid-session region change or the exit
	* recompute): estimated heights bake the hunk shapes in, and nothing else
	* invalidates them now that editing does not flip expandUnchanged. Public
	* so CodeView can run it when reaping a session whose instance was already
	* released.
	*/
	invalidateEditSessionLayout() {
		this.getSimpleVirtualizer()?.markDOMDirty();
		this.resetLayoutCache({
			forceSimpleRecompute: this.isSimpleMode(),
			includeEstimatedHeights: true,
			resetRenderRange: false
		});
		if (!this.isSimpleMode()) this.computeApproximateSize(true);
		this.getSimpleVirtualizer()?.requestHeightReconcile(this);
	}
	escalateEditSessionRender() {
		this.invalidateEditSessionLayout();
		if (!this.enabled || this.fileDiff == null) return;
		this.forceRenderOverride = true;
		this.virtualizer.instanceChanged(this, true);
	}
	shouldSelfHealEditSession() {
		return !this.isAdvancedMode() && super.shouldSelfHealEditSession();
	}
	setVisibility(visible) {
		if (this.isAdvancedMode() || this.fileContainer == null) return;
		this.renderRange = void 0;
		if (visible && !this.isVisible) {
			this.top = this.getVirtualizedTop();
			this.isVisible = true;
		} else if (!visible && this.isVisible) {
			this.isVisible = false;
			this.rerender();
		}
	}
	rerender() {
		if (!this.enabled || this.fileDiff == null && this.additionFile == null && this.deletionFile == null) return;
		const latestDiff = this.getLatestDiff();
		const nextRenderDiff = latestDiff == null ? void 0 : this.hunksRenderer.getDiffForNextRender(latestDiff);
		if (this.isSimpleMode()) this.pendingRender = void 0;
		this.forceRenderOverride = true;
		this.virtualizer.instanceChanged(this, !areDiffTargetsEqual(this.getRenderedDiff(), nextRenderDiff));
	}
	syncGhostTextRows() {
		const codeView = this.getAdvancedVirtualizer();
		if (codeView != null) {
			codeView.capturePendingLayoutAnchor();
			this.layoutDirty = true;
			codeView.instanceChanged(this, true);
		} else this.getSimpleVirtualizer()?.requestHeightReconcile(this);
	}
	applyDocumentChange(textDocument, newLineAnnotations, shouldUpdateBuffer = false) {
		const { renderRange: previousRenderRange } = this;
		this.getAdvancedVirtualizer()?.capturePendingLayoutAnchor();
		super.applyDocumentChange(textDocument, newLineAnnotations);
		this.getSimpleVirtualizer()?.markDOMDirty();
		this.resetLayoutCache({
			forceSimpleRecompute: this.isSimpleMode(),
			includeEstimatedHeights: true,
			resetRenderRange: false
		});
		const fileDiff = this.getRenderedDiff();
		if (!this.isSimpleMode()) this.computeApproximateSize(true);
		else if (shouldUpdateBuffer && previousRenderRange != null && fileDiff != null) {
			const windowSpecs = this.virtualizer.getWindowSpecs();
			const renderRange = this.computeRenderRangeFromWindow(fileDiff, this.top ?? 0, windowSpecs);
			if (renderRange.bufferAfter !== previousRenderRange.bufferAfter) this.updateBuffers(renderRange);
		}
		this.forceRenderOverride = true;
		this.virtualizer.instanceChanged(this, true);
	}
	computeApproximateSize(force = false, fileDiff = this.getLayoutDiff()) {
		const shouldValidateSize = this.isResizeDebuggingEnabled();
		if (!force && !this.layoutDirty && !shouldValidateSize) return;
		const isFirstCompute = this.height === 0;
		this.height = 0;
		this.cache.checkpoints = [];
		this.cache.totalLines = 0;
		if (fileDiff == null) {
			this.layoutDirty = false;
			return;
		}
		this.applyGhostTextRows(fileDiff, this.editor?.__getGhostTextRows() ?? NO_GHOST_TEXT_ROWS);
		const { disableFileHeader = false, collapsed = false } = this.options;
		const headerRegion = getVirtualFileHeaderRegion(this.metrics, disableFileHeader);
		this.height += headerRegion;
		if (collapsed) {
			this.layoutDirty = false;
			return;
		}
		this.height = this.getActiveEstimatedHeight(fileDiff) + this.cache.measuredHeightDeltaTotal;
		if (shouldValidateSize && !isFirstCompute) this.validateComputedHeight(fileDiff);
		this.layoutDirty = false;
	}
	getLayoutDiff() {
		return this.pendingRender?.diff ?? this.getRenderedDiff();
	}
	getActiveEstimatedHeight(fileDiff = this.getLayoutDiff()) {
		this.ensureEstimatedDiffHeights(fileDiff);
		const estimatedHeight = this.getDiffStyle() === "split" ? this.cache.estimatedSplitHeight : this.cache.estimatedUnifiedHeight;
		if (estimatedHeight == null) throw new Error("VirtualizedFileDiff.getActiveEstimatedHeight: missing estimated height");
		return estimatedHeight;
	}
	ensureEstimatedDiffHeights(fileDiff = this.getLayoutDiff()) {
		if (fileDiff == null) {
			this.cache.estimatedSplitHeight = void 0;
			this.cache.estimatedUnifiedHeight = void 0;
			return;
		}
		if (this.cache.estimatedSplitHeight != null && this.cache.estimatedUnifiedHeight != null) return;
		const { disableFileHeader = false, expandUnchanged = false, collapsedContextThreshold = 1 } = this.options;
		const { splitHeight, unifiedHeight } = computeEstimatedDiffHeights({
			fileDiff,
			metrics: this.metrics,
			disableFileHeader,
			hunkSeparators: this.getHunkSeparatorType(),
			expandUnchanged,
			expandedHunks: this.hunksRenderer.getExpandedHunksMap(),
			collapsedContextThreshold,
			canHydratePartialDiff: canHydrateCollapsedContext(fileDiff, this.options.loadDiffFiles != null)
		});
		this.cache.estimatedSplitHeight = splitHeight;
		this.cache.estimatedUnifiedHeight = unifiedHeight;
	}
	validateComputedHeight(fileDiff = this.getLayoutDiff()) {
		if (this.fileContainer == null || fileDiff == null) return;
		const rect = this.fileContainer.getBoundingClientRect();
		if (rect.height !== this.height) console.log("VirtualizedFileDiff.computeApproximateSize: computed height doesnt match", {
			name: fileDiff.name,
			elementHeight: rect.height,
			computedHeight: this.height
		});
		else console.log("VirtualizedFileDiff.computeApproximateSize: computed height IS CORRECT");
	}
	render({ fileContainer, fileDiff, forceRender = false, lineAnnotations, ...fileInputProps } = {}) {
		const fileInput = getDiffFileInput(fileInputProps, "VirtualizedFileDiff.render");
		const hasFileInput = fileInput != null;
		const oldFile = fileInput?.oldFile;
		const newFile = fileInput?.newFile;
		const filesDidChange = hasFileInput && (!areOptionalFilesEqual(oldFile, this.deletionFile) || !areOptionalFilesEqual(newFile, this.additionFile));
		let nextFileDiff = fileDiff ?? this.fileDiff;
		if (fileDiff == null && hasFileInput && (filesDidChange || this.fileDiff == null)) nextFileDiff = parseDiffFromFile(fileInput.oldFile, fileInput.newFile, this.options.parseDiffOptions);
		const { forceRenderOverride, isSetup } = this;
		this.forceRenderOverride = void 0;
		const targetChanged = nextFileDiff != null && !areDiffTargetsEqual(this.fileDiff, nextFileDiff);
		const dataChanged = targetChanged || filesDidChange;
		fileContainer = this.getOrCreateFileContainer(fileContainer);
		if (nextFileDiff == null) {
			console.error("VirtualizedFileDiff.render: attempting to virtually render when we dont have the correct data");
			return false;
		}
		if (targetChanged) this.updateExternalDiff(nextFileDiff, lineAnnotations);
		const { pendingRenderDiff, layoutDiffChanged, renderedDiffChanged, annotationsChanged } = (() => {
			if (this.pendingRender != null && this.pendingRender.latestDiff === (this.getLatestDiff(nextFileDiff) ?? nextFileDiff)) return {
				pendingRenderDiff: this.pendingRender.diff,
				layoutDiffChanged: false,
				renderedDiffChanged: false,
				annotationsChanged: false
			};
			return this.updatePendingRender(nextFileDiff, lineAnnotations);
		})();
		if (annotationsChanged || layoutDiffChanged) this.resetLayoutCache({ includeEstimatedHeights: layoutDiffChanged });
		if (!isSetup) {
			this.computeApproximateSize(false, pendingRenderDiff);
			const virtualizer = this.getSimpleVirtualizer();
			this.top ??= this.getVirtualizedTop();
			if (this.isAdvancedMode()) this.isVisible = true;
			else {
				if (virtualizer == null) throw new Error("VirtualizedFileDiff.render: simple virtualizer is not available");
				virtualizer.connect(fileContainer, this);
				this.isVisible = virtualizer.isInstanceVisible(this.top ?? 0, this.height);
			}
			this.isSetup = true;
		} else {
			this.top ??= this.getVirtualizedTop();
			if (layoutDiffChanged) {
				this.getSimpleVirtualizer()?.markDOMDirty();
				this.computeApproximateSize(false, pendingRenderDiff);
			}
		}
		if (!this.isVisible && this.isSimpleMode() && (!dataChanged || !isSetup)) {
			if (fileInput != null) {
				this.deletionFile = oldFile;
				this.additionFile = newFile;
			}
			if (targetChanged) this.clearReusableHeader();
			this.pendingRender = void 0;
			return this.renderPlaceholder(this.height);
		}
		const windowSpecs = this.virtualizer.getWindowSpecs();
		const fileTop = this.top ?? 0;
		const renderRange = this.computeRenderRangeFromWindow(pendingRenderDiff, fileTop, windowSpecs);
		return super.render({
			fileDiff: nextFileDiff,
			fileContainer,
			renderRange,
			lineAnnotations,
			forceRender: (forceRenderOverride ?? forceRender) || annotationsChanged || renderedDiffChanged || targetChanged,
			...fileInput,
			...fileInputProps
		});
	}
	finalizeRender() {
		if (this.getRenderedDiff() !== this.pendingRender?.diff) throw new Error("VirtualizedFileDiff.render: rendered a different diff than its prepared layout");
		this.pendingRender = void 0;
		if (this.isSimpleMode()) this.getSimpleVirtualizer()?.requestHeightReconcile(this);
	}
	updatePendingRender(nextFileDiff, lineAnnotations) {
		const latestDiff = this.getLatestDiff(nextFileDiff) ?? nextFileDiff;
		const previousRenderedDiff = this.getRenderedDiff();
		const { pendingRender } = this;
		const previousLayoutDiff = pendingRender?.diff ?? previousRenderedDiff;
		const pendingRenderDiff = this.hunksRenderer.getDiffForNextRender(latestDiff);
		this.pendingRender = {
			latestDiff,
			diff: pendingRenderDiff
		};
		return {
			pendingRenderDiff,
			annotationsChanged: this.syncLineAnnotations(lineAnnotations),
			layoutDiffChanged: !areDiffTargetsEqual(previousLayoutDiff, pendingRenderDiff),
			renderedDiffChanged: !areDiffTargetsEqual(previousRenderedDiff, pendingRenderDiff)
		};
	}
	syncVirtualizedTop() {
		this.top = this.getVirtualizedTop();
	}
	shouldDisableVirtualizationBuffers() {
		return this.isAdvancedMode() || super.shouldDisableVirtualizationBuffers();
	}
	shouldGuardRebuildScroll() {
		return false;
	}
	isSimpleMode() {
		return this.virtualizer.type === "simple";
	}
	isAdvancedMode() {
		return this.virtualizer.type === "advanced";
	}
	getVirtualizedTop() {
		if (this.virtualizer.type === "advanced") return this.virtualizer.getLocalTopForInstance(this);
		return this.fileContainer != null ? this.virtualizer.getOffsetInScrollContainer(this.fileContainer) : 0;
	}
	getSimpleVirtualizer() {
		return this.virtualizer.type === "simple" ? this.virtualizer : void 0;
	}
	getAdvancedVirtualizer() {
		return this.virtualizer.type === "advanced" ? this.virtualizer : void 0;
	}
	isResizeDebuggingEnabled() {
		return this.getSimpleVirtualizer()?.config.resizeDebugging ?? false;
	}
	getDiffStyle() {
		return this.options.diffStyle ?? "split";
	}
	getHunkSeparatorType() {
		return getOptionHunkSeparatorType(this.options.hunkSeparators);
	}
	approximateLayoutCheckpoints(fileDiff) {
		if (!this.layoutDirty && this.cache.checkpoints.length > 0 || fileDiff.hunks.length === 0 || this.options.collapsed === true) return;
		const { disableFileHeader = false, expandUnchanged = false, collapsedContextThreshold = 1 } = this.options;
		const finalHunkIndex = fileDiff.hunks.length - 1;
		const canHydratePartialDiff = canHydrateCollapsedContext(fileDiff, this.options.loadDiffFiles != null);
		const diffStyle = this.getDiffStyle();
		const hunkSeparators = this.getHunkSeparatorType();
		const expandedHunks = expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap();
		const heightDeltaPrefix = createHeightDeltaPrefix(this.cache.heightDeltas);
		let top = getVirtualFileHeaderRegion(this.metrics, disableFileHeader) + this.cache.fileAnnotationHeight;
		let renderedLineIndex = 0;
		const processRows = ({ rowCount, startLineIndex, preSeparatorHeight = 0, postSeparatorHeight = 0, metadataOffsets = [] }) => {
			if (rowCount <= 0) return;
			const blockStart = renderedLineIndex;
			const blockEnd = renderedLineIndex + rowCount;
			let nextCheckpoint = getNextCheckpointIndex(blockStart);
			while (nextCheckpoint < blockEnd) {
				const offset = nextCheckpoint - blockStart;
				const checkpointTop = top + (offset > 0 ? preSeparatorHeight : 0) + offset * this.metrics.lineHeight + countMetadataOffsetsBefore(metadataOffsets, offset) * this.metrics.lineHeight + sumHeightDeltas(heightDeltaPrefix, startLineIndex, startLineIndex + offset);
				this.cache.checkpoints.push({
					renderedLineIndex: nextCheckpoint,
					lineIndex: startLineIndex + offset,
					top: checkpointTop
				});
				nextCheckpoint += VIRTUALIZED_FILE_DIFF_LAYOUT_CHECKPOINT_INTERVAL;
			}
			top += preSeparatorHeight + rowCount * this.metrics.lineHeight + metadataOffsets.length * this.metrics.lineHeight + sumHeightDeltas(heightDeltaPrefix, startLineIndex, startLineIndex + rowCount) + postSeparatorHeight;
			renderedLineIndex = blockEnd;
		};
		for (let hunkIndex = 0; hunkIndex < fileDiff.hunks.length; hunkIndex++) {
			const hunk = fileDiff.hunks[hunkIndex];
			if (hunk == null) throw new Error("VirtualizedFileDiff.approximateLayoutCheckpoints: invalid hunk index");
			const leadingRegion = getExpandedRegion({
				isPartial: fileDiff.isPartial,
				rangeSize: hunk.collapsedBefore,
				expandedHunks,
				hunkIndex,
				collapsedContextThreshold
			});
			const leadingSeparatorHeight = leadingRegion.collapsedLines > 0 ? getLeadingHunkSeparatorLayout({
				type: hunkSeparators,
				metrics: this.metrics,
				hunkIndex,
				hunkSpecs: hunk.hunkSpecs
			})?.totalHeight ?? 0 : 0;
			processRows({
				rowCount: leadingRegion.fromStart,
				startLineIndex: (diffStyle === "split" ? hunk.splitLineStart : hunk.unifiedLineStart) - leadingRegion.rangeSize
			});
			let pendingLeadingSeparatorHeight = leadingSeparatorHeight;
			processRows({
				rowCount: leadingRegion.fromEnd,
				startLineIndex: (diffStyle === "split" ? hunk.splitLineStart : hunk.unifiedLineStart) - leadingRegion.fromEnd,
				preSeparatorHeight: pendingLeadingSeparatorHeight
			});
			if (leadingRegion.fromEnd > 0) pendingLeadingSeparatorHeight = 0;
			const trailingRegion = hunkIndex === finalHunkIndex ? getTrailingExpandedRegion({
				fileDiff,
				hunkIndex,
				expandedHunks,
				collapsedContextThreshold,
				errorPrefix: "VirtualizedFileDiff"
			}) : void 0;
			const trailingSeparatorHeight = trailingRegion != null && trailingRegion.collapsedLines > 0 ? getTrailingHunkSeparatorLayout({
				type: hunkSeparators,
				metrics: this.metrics
			})?.totalHeight ?? 0 : hunkIndex === finalHunkIndex && canHydratePartialDiff ? getTrailingHunkSeparatorLayout({
				type: hunkSeparators,
				metrics: this.metrics
			})?.totalHeight ?? 0 : 0;
			const trailingExpandedCount = trailingRegion != null ? trailingRegion.fromStart + trailingRegion.fromEnd : 0;
			const hunkBodyRowCount = diffStyle === "split" ? hunk.splitLineCount : hunk.unifiedLineCount;
			const hunkBodyStartLineIndex = diffStyle === "split" ? hunk.splitLineStart : hunk.unifiedLineStart;
			processRows({
				rowCount: hunkBodyRowCount,
				startLineIndex: hunkBodyStartLineIndex,
				preSeparatorHeight: pendingLeadingSeparatorHeight,
				postSeparatorHeight: trailingExpandedCount === 0 ? trailingSeparatorHeight : 0,
				metadataOffsets: getHunkMetadataOffsets({
					diffStyle,
					hunk,
					rowCount: hunkBodyRowCount
				})
			});
			if (trailingRegion != null && trailingExpandedCount > 0) processRows({
				rowCount: trailingExpandedCount,
				startLineIndex: hunkBodyStartLineIndex + hunkBodyRowCount,
				postSeparatorHeight: trailingSeparatorHeight
			});
		}
		this.cache.totalLines = renderedLineIndex;
	}
	getLayoutCheckpointBeforeLineIndex(lineIndex) {
		if (lineIndex <= 0 || this.cache.checkpoints.length === 0) return;
		let low = 0;
		let high = this.cache.checkpoints.length - 1;
		let result;
		while (low <= high) {
			const mid = low + high >> 1;
			const checkpoint = this.cache.checkpoints[mid];
			if (checkpoint == null) throw new Error("VirtualizedFileDiff: invalid checkpoint index");
			if (checkpoint.lineIndex <= lineIndex) {
				result = checkpoint;
				low = mid + 1;
			} else high = mid - 1;
		}
		return result;
	}
	getLayoutCheckpointBeforeTop(top, hunkLineCount) {
		let low = 0;
		let high = this.cache.checkpoints.length - 1;
		let resultIndex = -1;
		while (low <= high) {
			const mid = low + high >> 1;
			const checkpoint = this.cache.checkpoints[mid];
			if (checkpoint == null) throw new Error("VirtualizedFileDiff: invalid checkpoint index");
			if (checkpoint.top <= top) {
				resultIndex = mid;
				low = mid + 1;
			} else high = mid - 1;
		}
		if (hunkLineCount == null) return resultIndex >= 0 ? this.cache.checkpoints[resultIndex] : void 0;
		for (let index = resultIndex; index >= 0; index--) {
			const checkpoint = this.cache.checkpoints[index];
			if (checkpoint == null) throw new Error("VirtualizedFileDiff: invalid checkpoint index");
			if (checkpoint.renderedLineIndex % hunkLineCount === 0) return checkpoint;
		}
	}
	getExpandedLineCount(fileDiff, diffStyle) {
		let count = 0;
		if (fileDiff.isPartial) {
			for (const hunk of fileDiff.hunks) count += diffStyle === "split" ? hunk.splitLineCount : hunk.unifiedLineCount;
			return count;
		}
		const { expandUnchanged = false, collapsedContextThreshold = 1 } = this.options;
		const expandedHunks = expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap();
		for (const [hunkIndex, hunk] of fileDiff.hunks.entries()) {
			const hunkCount = diffStyle === "split" ? hunk.splitLineCount : hunk.unifiedLineCount;
			count += hunkCount;
			const collapsedBefore = Math.max(hunk.collapsedBefore, 0);
			const { fromStart, fromEnd, renderAll } = getExpandedRegion({
				isPartial: fileDiff.isPartial,
				rangeSize: collapsedBefore,
				expandedHunks,
				hunkIndex,
				collapsedContextThreshold
			});
			if (collapsedBefore > 0) count += renderAll ? collapsedBefore : fromStart + fromEnd;
		}
		const trailingRegion = getTrailingExpandedRegion({
			fileDiff,
			hunkIndex: fileDiff.hunks.length - 1,
			expandedHunks,
			collapsedContextThreshold,
			errorPrefix: "VirtualizedFileDiff"
		});
		if (trailingRegion != null) count += trailingRegion.fromStart + trailingRegion.fromEnd;
		return count;
	}
	getLayoutLineCount(fileDiff, diffStyle) {
		const expandedLineCount = this.getExpandedLineCount(fileDiff, diffStyle);
		const metadataLineCount = diffStyle === "split" ? fileDiff.splitLineCount : fileDiff.unifiedLineCount;
		return Math.max(expandedLineCount, metadataLineCount, fileDiff.additionLines.length, fileDiff.deletionLines.length, this.cache.totalLines);
	}
	computeRenderRangeFromWindow(fileDiff, fileTop, { top, bottom }) {
		const { disableFileHeader = false, expandUnchanged = false, collapsedContextThreshold = 1 } = this.options;
		const { hunkLineCount, lineHeight } = this.metrics;
		const diffStyle = this.getDiffStyle();
		const hunkSeparators = this.getHunkSeparatorType();
		const canHydratePartialDiff = canHydrateCollapsedContext(fileDiff, this.options.loadDiffFiles != null);
		const fileHeight = this.height;
		let lineCount = this.getLayoutLineCount(fileDiff, diffStyle);
		const headerRegion = getVirtualFileHeaderRegion(this.metrics, disableFileHeader);
		const paddingBottom = fileDiff.hunks.length > 0 ? getVirtualFilePaddingBottom(this.metrics) : 0;
		const { fileAnnotationHeight } = this.cache;
		const codeRegionTop = headerRegion + fileAnnotationHeight;
		const codeHeight = Math.max(0, fileHeight - headerRegion - fileAnnotationHeight - paddingBottom);
		const hasFileAnnotations = this.hasFileAnnotations(fileDiff);
		const fileAnnotationTop = fileTop + headerRegion;
		const measuredFileAnnotationVisible = fileAnnotationHeight > 0 && hasFileAnnotations && fileAnnotationTop < bottom && fileAnnotationTop + fileAnnotationHeight > top;
		if (fileTop < top - fileHeight || fileTop > bottom) return {
			startingLine: 0,
			totalLines: 0,
			bufferBefore: 0,
			bufferAfter: fileHeight - headerRegion - paddingBottom
		};
		if (lineCount <= hunkLineCount || fileDiff.hunks.length === 0) return {
			startingLine: 0,
			totalLines: hunkLineCount,
			bufferBefore: 0,
			bufferAfter: 0
		};
		this.approximateLayoutCheckpoints(fileDiff);
		lineCount = this.getLayoutLineCount(fileDiff, diffStyle);
		const estimatedTargetLines = Math.ceil(Math.max(bottom - top, 0) / lineHeight);
		const totalLines = Math.ceil(estimatedTargetLines / hunkLineCount) * hunkLineCount + hunkLineCount;
		const totalHunks = totalLines / hunkLineCount;
		const overflowHunks = totalHunks;
		const hunkOffsets = [];
		const viewportCenter = (top + bottom) / 2;
		const checkpoint = this.getLayoutCheckpointBeforeTop(Math.max(0, top - fileTop - totalLines * lineHeight * 2), hunkLineCount);
		let absoluteLineTop = fileTop + (checkpoint?.top ?? codeRegionTop);
		let currentLine = checkpoint?.renderedLineIndex ?? 0;
		let firstVisibleHunk;
		let centerHunk;
		let overflowCounter;
		iterateOverDiff({
			diff: fileDiff,
			diffStyle,
			startingLine: checkpoint?.renderedLineIndex ?? 0,
			expandedHunks: expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap(),
			collapsedContextThreshold,
			callback: ({ hunkIndex, hunk, collapsedBefore, collapsedAfter, deletionLine, additionLine }) => {
				const splitLineIndex = additionLine != null ? additionLine.splitLineIndex : deletionLine.splitLineIndex;
				const unifiedLineIndex = additionLine != null ? additionLine.unifiedLineIndex : deletionLine.unifiedLineIndex;
				const hasMetadata = (additionLine?.noEOFCR ?? false) || (deletionLine?.noEOFCR ?? false);
				const isFinalHunkRow = hunkIndex === fileDiff.hunks.length - 1 && hunk != null && (diffStyle === "split" ? splitLineIndex === hunk.splitLineStart + hunk.splitLineCount - 1 : unifiedLineIndex === hunk.unifiedLineStart + hunk.unifiedLineCount - 1);
				const gapAdjustment = (collapsedBefore > 0 ? getLeadingHunkSeparatorLayout({
					type: hunkSeparators,
					metrics: this.metrics,
					hunkIndex,
					hunkSpecs: hunk?.hunkSpecs
				}) : void 0)?.totalHeight ?? 0;
				absoluteLineTop += gapAdjustment;
				const isAtHunkBoundary = currentLine % hunkLineCount === 0;
				const currentHunk = Math.floor(currentLine / hunkLineCount);
				if (isAtHunkBoundary) {
					hunkOffsets[currentHunk] = absoluteLineTop - (fileTop + codeRegionTop + gapAdjustment);
					if (overflowCounter != null) {
						if (overflowCounter <= 0) return true;
						overflowCounter--;
					}
				}
				const lineHeight = this.getLineHeight(diffStyle === "split" ? splitLineIndex : unifiedLineIndex, hasMetadata);
				if (absoluteLineTop > top - lineHeight && absoluteLineTop < bottom) firstVisibleHunk ??= currentHunk;
				if (centerHunk == null && absoluteLineTop + lineHeight > viewportCenter) centerHunk = currentHunk;
				if (overflowCounter == null && absoluteLineTop >= bottom && isAtHunkBoundary) overflowCounter = overflowHunks;
				currentLine++;
				absoluteLineTop += lineHeight;
				if (collapsedAfter > 0 || isFinalHunkRow && canHydratePartialDiff) {
					const trailingSeparator = getTrailingHunkSeparatorLayout({
						type: hunkSeparators,
						metrics: this.metrics
					});
					if (trailingSeparator != null) {
						if (absoluteLineTop < bottom && absoluteLineTop + trailingSeparator.totalHeight > top) firstVisibleHunk ??= currentHunk;
						if (centerHunk == null && absoluteLineTop + trailingSeparator.totalHeight > viewportCenter) centerHunk = currentHunk;
						absoluteLineTop += trailingSeparator.totalHeight;
					}
				}
				return false;
			}
		});
		if (firstVisibleHunk == null) if (measuredFileAnnotationVisible) {
			firstVisibleHunk = 0;
			centerHunk = 0;
		} else return {
			startingLine: 0,
			totalLines: 0,
			bufferBefore: 0,
			bufferAfter: fileHeight - headerRegion - paddingBottom
		};
		centerHunk ??= firstVisibleHunk;
		const idealStartHunk = Math.round(centerHunk - totalHunks / 2);
		const maxStartHunk = Math.max(0, Math.ceil(lineCount / hunkLineCount) - totalHunks);
		const startHunk = Math.max(0, Math.min(idealStartHunk, maxStartHunk));
		const startingLine = startHunk * hunkLineCount;
		const clampedTotalLines = idealStartHunk < 0 ? totalLines + idealStartHunk * hunkLineCount : totalLines;
		const codeBufferBefore = hunkOffsets[startHunk] ?? 0;
		const bufferBefore = startingLine === 0 ? 0 : fileAnnotationHeight + codeBufferBefore;
		const finalHunkIndex = startHunk + clampedTotalLines / hunkLineCount;
		const bufferAfter = finalHunkIndex < hunkOffsets.length ? codeHeight - hunkOffsets[finalHunkIndex] : codeHeight - (absoluteLineTop - fileTop - codeRegionTop);
		return {
			startingLine,
			totalLines: clampedTotalLines,
			bufferBefore,
			bufferAfter: Math.max(0, bufferAfter)
		};
	}
};
function measureFileAnnotationHeight(codeGroups) {
	let height;
	for (const codeGroup of codeGroups) {
		if (codeGroup == null) continue;
		const content = codeGroup.children[1];
		if (!(content instanceof HTMLElement)) continue;
		for (const child of content.children) {
			if (!(child instanceof HTMLElement)) continue;
			if (child.dataset.lineAnnotation !== FILE_ANNOTATION_DOM_KEY) continue;
			height = Math.max(height ?? 0, child.getBoundingClientRect().height);
		}
	}
	return height;
}
function createHeightDeltaPrefix(heightDeltas) {
	const entries = Array.from(heightDeltas).sort((a, b) => a[0] - b[0]);
	const lineIndexes = [];
	const prefixTotals = [0];
	let total = 0;
	for (const [lineIndex, delta] of entries) {
		lineIndexes.push(lineIndex);
		total += delta;
		prefixTotals.push(total);
	}
	return {
		lineIndexes,
		prefixTotals
	};
}
function sumHeightDeltas({ lineIndexes, prefixTotals }, startLineIndex, endLineIndex) {
	if (startLineIndex >= endLineIndex || lineIndexes.length === 0) return 0;
	const start = lowerBound(lineIndexes, startLineIndex);
	return (prefixTotals[lowerBound(lineIndexes, endLineIndex)] ?? 0) - (prefixTotals[start] ?? 0);
}
function lowerBound(values, target) {
	let low = 0;
	let high = values.length;
	while (low < high) {
		const mid = low + high >> 1;
		const value = values[mid];
		if (value == null) throw new Error("VirtualizedFileDiff: invalid prefix index");
		if (value < target) low = mid + 1;
		else high = mid;
	}
	return low;
}
function getNextCheckpointIndex(renderedLineIndex) {
	return Math.ceil(renderedLineIndex / VIRTUALIZED_FILE_DIFF_LAYOUT_CHECKPOINT_INTERVAL) * VIRTUALIZED_FILE_DIFF_LAYOUT_CHECKPOINT_INTERVAL;
}
function countMetadataOffsetsBefore(metadataOffsets, offset) {
	let count = 0;
	for (const metadataOffset of metadataOffsets) if (metadataOffset < offset) count++;
	return count;
}
function getHunkMetadataOffsets({ diffStyle, hunk, rowCount }) {
	if (rowCount <= 0 || !hunk.noEOFCRAdditions && !hunk.noEOFCRDeletions) return [];
	const lastContent = hunk.hunkContent.at(-1);
	if (lastContent == null) return [];
	if (lastContent.type === "context") return [rowCount - 1];
	const splitCount = Math.max(lastContent.deletions, lastContent.additions);
	const unifiedCount = lastContent.deletions + lastContent.additions;
	if (diffStyle === "split") return splitCount > 0 && (hunk.noEOFCRAdditions || hunk.noEOFCRDeletions) ? [rowCount - 1] : [];
	const offsets = [];
	const contentStartOffset = rowCount - unifiedCount;
	if (lastContent.deletions > 0 && hunk.noEOFCRDeletions) offsets.push(contentStartOffset + lastContent.deletions - 1);
	if (lastContent.additions > 0 && hunk.noEOFCRAdditions) offsets.push(rowCount - 1);
	return offsets;
}
function hasDiffLayoutOptionChanged(previousOptions, nextOptions) {
	return (previousOptions.diffStyle ?? "split") !== (nextOptions.diffStyle ?? "split") || (previousOptions.overflow ?? "scroll") !== (nextOptions.overflow ?? "scroll") || (previousOptions.collapsed ?? false) !== (nextOptions.collapsed ?? false) || (previousOptions.disableLineNumbers ?? false) !== (nextOptions.disableLineNumbers ?? false) || (previousOptions.disableFileHeader ?? false) !== (nextOptions.disableFileHeader ?? false) || (previousOptions.diffIndicators ?? "bars") !== (nextOptions.diffIndicators ?? "bars") || (previousOptions.hunkSeparators ?? "line-info") !== (nextOptions.hunkSeparators ?? "line-info") || Boolean(previousOptions.loadDiffFiles) !== Boolean(nextOptions.loadDiffFiles) || (previousOptions.expandUnchanged ?? false) !== (nextOptions.expandUnchanged ?? false) || (previousOptions.collapsedContextThreshold ?? 1) !== (nextOptions.collapsedContextThreshold ?? 1) || previousOptions.unsafeCSS !== nextOptions.unsafeCSS;
}
function hasDiffEstimateOptionChanged(previousOptions, nextOptions) {
	return (previousOptions.disableFileHeader ?? false) !== (nextOptions.disableFileHeader ?? false) || (previousOptions.hunkSeparators ?? "line-info") !== (nextOptions.hunkSeparators ?? "line-info") || Boolean(previousOptions.loadDiffFiles) !== Boolean(nextOptions.loadDiffFiles) || (previousOptions.expandUnchanged ?? false) !== (nextOptions.expandUnchanged ?? false) || (previousOptions.collapsedContextThreshold ?? 1) !== (nextOptions.collapsedContextThreshold ?? 1);
}
function canHydrateCollapsedContext(fileDiff, hasFileLoader) {
	return fileDiff.isPartial && hasFileLoader && (fileDiff.type === "change" || fileDiff.type === "rename-changed");
}
function getOptionHunkSeparatorType(hunkSeparators) {
	return typeof hunkSeparators === "function" ? "custom" : hunkSeparators ?? "line-info";
}
function areOptionalFilesEqual(fileA, fileB) {
	if (fileA == null || fileB == null) return fileA == null && fileB == null;
	return areFilesEqual(fileA, fileB);
}
function parseLineIndex(lineIndexAttr, diffStyle) {
	const [unifiedIndex, splitIndex] = lineIndexAttr.split(",").map(Number);
	return diffStyle === "split" ? splitIndex : unifiedIndex;
}
//#endregion
export { VIRTUALIZED_FILE_DIFF_LAYOUT_CHECKPOINT_INTERVAL, VirtualizedFileDiff };

//# sourceMappingURL=VirtualizedFileDiff.js.map