UNPKG

@pierre/diffs

Version:

1,982 lines 85.5 kB
import { CUSTOM_HEADER_SLOT_ID, DEFAULT_THEMES, EMPTY_RENDER_RANGE, HEADER_FILENAME_SUFFIX_SLOT_ID, HEADER_METADATA_SLOT_ID, HEADER_PREFIX_SLOT_ID } from "../constants.js";
import { getThemes } from "../utils/getThemes.js";
import { getHighlighterIfLoaded, getSharedHighlighter } from "../highlighter/shared_highlighter.js";
import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
import { areThemesEqual } from "../utils/areThemesEqual.js";
import { isStyleNode } from "../utils/isStyleNode.js";
import { InteractionManager, pluckInteractionOptions } from "../managers/InteractionManager.js";
import { ResizeManager } from "../managers/ResizeManager.js";
import { areRenderRangesEqual } from "../utils/areRenderRangesEqual.js";
import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
import { getLineAnnotationName } from "../utils/getLineAnnotationName.js";
import { isDefaultRenderRange } from "../utils/isDefaultRenderRange.js";
import { SVGSpriteSheet } from "../sprite.js";
import { arePrePropertiesEqual } from "../utils/arePrePropertiesEqual.js";
import { createAnnotationWrapperNode } from "../utils/createAnnotationWrapperNode.js";
import { createGutterUtilityContentNode } from "../utils/createGutterUtilityContentNode.js";
import { createUnsafeCSSStyleNode } from "../utils/createUnsafeCSSStyleNode.js";
import { getMeasuredScrollbarGutter } from "../utils/scrollbarGutter.js";
import { patchScrollbarGutterSize, wrapThemeCSS, wrapUnsafeCSS } from "../utils/cssWrappers.js";
import { adoptEditSessionAnnotations, resolveEditSessionSlotName, writeEditSessionAnnotations } from "../utils/editSessionAnnotations.js";
import { getOrCreateCodeNode } from "../utils/getOrCreateCodeNode.js";
import { guardWebKitScrollDuringRebuild } from "../utils/guardWebKitScrollDuringRebuild.js";
import { upsertHostThemeStyle } from "../utils/hostTheme.js";
import { isSafari } from "../utils/platform.js";
import { prerenderHTMLIfNecessary } from "../utils/prerenderHTMLIfNecessary.js";
import { setPreNodeProperties } from "../utils/setWrapperNodeProps.js";
import "./web-components.js";
import { areDiffTargetsEqual } from "../utils/areDiffTargetsEqual.js";
import { areFilesEqual } from "../utils/areFilesEqual.js";
import { awaitWithTimeout } from "../utils/awaitWithTimeout.js";
import { getHunkSideStartBoundary } from "../utils/getHunkSideBoundaries.js";
import { getExpandedRegion, getHunkAdditionLineRange, getNearestRenderableAdditionLine, getTrailingExpandedRegion, isAdditionLineRenderable } from "../utils/virtualDiffLayout.js";
import { getDiffFileInput } from "../utils/getDiffFileInput.js";
import { cloneFileDiffMetadata, cloneHunks } from "../utils/cloneFileDiffMetadata.js";
import { splitFileContents } from "../utils/splitFileContents.js";
import { hydratePartialDiff } from "../utils/hydratePartialDiff.js";
import { iterateOverDiff } from "../utils/iterateOverDiff.js";
import { parseDiffFromFile } from "../utils/parseDiffFromFile.js";
import { ScrollSyncManager } from "../managers/ScrollSyncManager.js";
import { recomputeDiffRenderLineCounts } from "../utils/updateDiffHunks.js";
import { captureExpansionAnchors, finishEditSessionForDiff, rebuildExpansionFromAnchors } from "../utils/editSessionHunks.js";
import { isDiffPlainText } from "../utils/isDiffPlainText.js";
import { DiffHunksRenderer } from "../renderers/DiffHunksRenderer.js";
import { areDiffLineAnnotationsEqual } from "../utils/areDiffLineAnnotationsEqual.js";
import { areHunkDataEqual } from "../utils/areHunkDataEqual.js";
import { getDiffHunksRendererOptions } from "../utils/getDiffHunksRendererOptions.js";
import { toHtml } from "hast-util-to-html";
//#region src/components/FileDiff.ts
function canHydrateDiff(fileDiff) {
	return fileDiff.isPartial && (fileDiff.type === "change" || fileDiff.type === "rename-changed" || fileDiff.type === "rename-pure");
}
function createEditSessionDiff(fileDiff) {
	const editSessionDiff = { ...fileDiff };
	delete editSessionDiff.cacheKey;
	return editSessionDiff;
}
function shouldResetUndoState(prevDiff, nextDiff) {
	if (prevDiff.isPartial || nextDiff.isPartial) throw new Error("FileDiff.shouldResetEditorForExternalDiff: diffs must be fully hydrated");
	const prevLanguage = prevDiff.lang ?? getFiletypeFromFileName(prevDiff.name);
	const nextLanguage = nextDiff.lang ?? getFiletypeFromFileName(nextDiff.name);
	const prevHasOldFile = prevDiff.type !== "new";
	const nextHasOldFile = nextDiff.type !== "new";
	if (prevDiff.name !== nextDiff.name || prevLanguage !== nextLanguage || prevHasOldFile !== nextHasOldFile || prevDiff.deletionLines.length !== nextDiff.deletionLines.length) return true;
	return prevDiff.deletionLines.some((line, index) => line !== nextDiff.deletionLines[index]);
}
function areLinesEqual(first, second) {
	return first.length === second.length && first.every((line, index) => line === second[index]);
}
function canRestoreDiffSession(snapshot, externalDiff) {
	const { oldFile } = snapshot;
	if (oldFile == null) return externalDiff.type === "new";
	return externalDiff.type !== "new" && oldFile.name === (externalDiff.prevName ?? externalDiff.name) && areLinesEqual(oldFile.lines, externalDiff.deletionLines);
}
let instanceId = -1;
var FileDiff = class {
	options;
	workerManager;
	isContainerManaged;
	static LoadedCustomComponent = true;
	__id = `file-diff:${++instanceId}`;
	type = "file-diff";
	fileContainer;
	spriteSVG;
	pre;
	codeUnified;
	codeDeletions;
	codeAdditions;
	bufferBefore;
	bufferAfter;
	themeCSSStyle;
	appliedThemeCSS;
	hasAdoptedThemeCSS = false;
	unsafeCSSStyle;
	appliedUnsafeCSS;
	gutterUtilityContent;
	headerElement;
	headerPrefix;
	headerFilenameSuffix;
	headerMetadata;
	headerCustom;
	separatorCache = /* @__PURE__ */ new Map();
	errorWrapper;
	placeHolder;
	hunksRenderer;
	resizeManager;
	scrollSyncManager;
	interactionManager;
	annotationCache = /* @__PURE__ */ new Map();
	lineAnnotations = [];
	managersDirty = false;
	deletionFile;
	additionFile;
	fileDiff;
	editSession;
	renderedDiff;
	renderRange;
	pendingFiles;
	appliedPreAttributes;
	headerCache = {
		lastRenderedHTML: void 0,
		html: void 0,
		fileDiff: void 0
	};
	lastRowCount;
	mounted = false;
	enabled = true;
	editor;
	refreshViewTimeout;
	lineStateRefreshPending = false;
	deferredSelectedLines;
	deferredEditorActiveLine;
	constructor(options = { theme: DEFAULT_THEMES }, workerManager, isContainerManaged = false) {
		this.options = options;
		this.workerManager = workerManager;
		this.isContainerManaged = isContainerManaged;
		this.hunksRenderer = this.createHunksRenderer(options);
		this.resizeManager = new ResizeManager();
		this.scrollSyncManager = new ScrollSyncManager();
		this.interactionManager = new InteractionManager("diff", pluckInteractionOptions(options, typeof options.hunkSeparators === "function" || (options.hunkSeparators ?? "line-info") === "line-info" || options.hunkSeparators === "line-info-basic" ? this.handleExpandHunk : void 0, this.getLineIndex));
		this.workerManager?.subscribeToThemeChanges(this);
		this.enabled = true;
	}
	handleHighlightRender = () => {
		this.rerender();
	};
	getTheme() {
		return this.workerManager?.getDiffRenderOptions().theme ?? this.options.theme ?? DEFAULT_THEMES;
	}
	getHunksRendererOptions(options) {
		return getDiffHunksRendererOptions(options);
	}
	createHunksRenderer(options) {
		return new DiffHunksRenderer(this.getHunksRendererOptions(options), this.getAnnotationSlotName, this.handleHighlightRender, this.workerManager);
	}
	getLineIndex = (lineNumber, side = "additions") => {
		return this.getLineIndexForDiff(this.getDiffForLineIndex(), lineNumber, side);
	};
	getDiffForLineIndex() {
		return this.getRenderedDiff();
	}
	getLineIndexForDiff(fileDiff, lineNumber, side) {
		if (fileDiff == null) return;
		const lastHunk = fileDiff.hunks.at(-1);
		let targetUnifiedIndex;
		let targetSplitIndex;
		hunkIterator: for (const hunk of fileDiff.hunks) {
			const hunkStart = side === "deletions" ? hunk.deletionStart : hunk.additionStart;
			const hunkCount = side === "deletions" ? hunk.deletionCount : hunk.additionCount;
			let currentLineNumber = getHunkSideStartBoundary(hunkStart, hunkCount) + 1;
			let splitIndex = hunk.splitLineStart;
			let unifiedIndex = hunk.unifiedLineStart;
			if (lineNumber < currentLineNumber) {
				const difference = currentLineNumber - lineNumber;
				targetUnifiedIndex = Math.max(unifiedIndex - difference, 0);
				targetSplitIndex = Math.max(splitIndex - difference, 0);
				break hunkIterator;
			}
			if (lineNumber >= currentLineNumber + hunkCount) {
				if (hunk === lastHunk) {
					const difference = lineNumber - (currentLineNumber + hunkCount);
					targetUnifiedIndex = unifiedIndex + hunk.unifiedLineCount + difference;
					targetSplitIndex = splitIndex + hunk.splitLineCount + difference;
					break hunkIterator;
				}
				continue;
			}
			for (const content of hunk.hunkContent) if (content.type === "context") if (lineNumber < currentLineNumber + content.lines) {
				const difference = lineNumber - currentLineNumber;
				targetSplitIndex = splitIndex + difference;
				targetUnifiedIndex = unifiedIndex + difference;
				break hunkIterator;
			} else {
				currentLineNumber += content.lines;
				splitIndex += content.lines;
				unifiedIndex += content.lines;
			}
			else {
				const sideCount = side === "deletions" ? content.deletions : content.additions;
				if (lineNumber < currentLineNumber + sideCount) {
					const indexDifference = lineNumber - currentLineNumber;
					targetUnifiedIndex = unifiedIndex + (side === "additions" ? content.deletions : 0) + indexDifference;
					targetSplitIndex = splitIndex + indexDifference;
					break hunkIterator;
				} else {
					currentLineNumber += sideCount;
					splitIndex += Math.max(content.deletions, content.additions);
					unifiedIndex += content.deletions + content.additions;
				}
			}
			break hunkIterator;
		}
		if (targetUnifiedIndex == null || targetSplitIndex == null) return;
		return [targetUnifiedIndex, targetSplitIndex];
	}
	setOptions(options) {
		if (options == null) return;
		this.options = options;
		this.clearReusableHeader();
		this.hunksRenderer.setOptions(this.getHunksRendererOptions(options));
		this.syncInteractionOptions();
	}
	syncInteractionOptions() {
		this.interactionManager.setOptions(pluckInteractionOptions(this.options, typeof this.options.hunkSeparators === "function" || (this.options.hunkSeparators ?? "line-info") === "line-info" || this.options.hunkSeparators === "line-info-basic" ? this.handleExpandHunk : void 0, this.getLineIndex));
	}
	mergeOptions(options) {
		this.options = {
			...this.options,
			...options
		};
	}
	setThemeType(themeType) {
		if ((this.options.themeType ?? "system") === themeType) return;
		this.mergeOptions({ themeType });
		this.applyCachedThemeState(themeType);
	}
	applyCachedThemeState(themeType) {
		if (typeof this.options.theme === "string" || this.fileContainer == null || this.appliedThemeCSS == null) return false;
		const effectiveThemeType = this.appliedThemeCSS.baseThemeType ?? themeType;
		if (this.appliedThemeCSS.themeType === effectiveThemeType) return false;
		this.applyThemeState(this.fileContainer, this.appliedThemeCSS.themeStyles, themeType, this.appliedThemeCSS.baseThemeType);
		return true;
	}
	hasThemeChanged() {
		return this.appliedThemeCSS != null && !areThemesEqual(this.appliedThemeCSS.theme, this.getTheme());
	}
	getHoveredLine = () => {
		return this.interactionManager.getHoveredLine();
	};
	getAnnotationSlotName = (annotation) => {
		return resolveEditSessionSlotName(this.editSession?.annotations, annotation, getLineAnnotationName);
	};
	getLatestAnnotations() {
		return this.editSession?.annotations?.current ?? this.lineAnnotations;
	}
	isNewAnnotations(lineAnnotations) {
		const session = this.editSession?.annotations;
		if (lineAnnotations === this.lineAnnotations) return false;
		return session == null || lineAnnotations !== session.provided && lineAnnotations !== session.current;
	}
	setLineAnnotations(lineAnnotations) {
		const sessionAnnotations = this.editSession?.annotations;
		if (sessionAnnotations == null) {
			this.lineAnnotations = lineAnnotations;
			return;
		}
		if (!this.isNewAnnotations(lineAnnotations)) return;
		this.lineAnnotations = lineAnnotations;
		writeEditSessionAnnotations(sessionAnnotations, lineAnnotations, getLineAnnotationName);
	}
	syncEditSessionAnnotationsFromEditor(lineAnnotations) {
		const session = this.editSession?.annotations;
		if (session == null || lineAnnotations === session.current) return false;
		session.current = lineAnnotations;
		this.hunksRenderer.setLineAnnotations(lineAnnotations);
		this.renderAnnotations();
		return true;
	}
	canPartiallyRender(forceRender, annotationsChanged, didContentChange) {
		if (forceRender || annotationsChanged || didContentChange || typeof this.options.hunkSeparators === "function") return false;
		return true;
	}
	setSelectedLines(range, options) {
		if (this.lineStateRefreshPending) this.deferredSelectedLines = [range, options];
		else this.interactionManager.setSelection(range, options);
	}
	setEditorActiveLine(lineNumber, options) {
		if (this.lineStateRefreshPending) this.deferredEditorActiveLine = [lineNumber, options];
		else this.interactionManager.setEditorActiveLine(lineNumber, {
			lineNumberOnly: options?.lineNumberOnly,
			side: options?.side ?? "additions"
		});
	}
	flushDeferredLineState() {
		const { deferredEditorActiveLine: editorActiveLine, deferredSelectedLines: selectedLines } = this;
		this.lineStateRefreshPending = false;
		this.deferredEditorActiveLine = void 0;
		this.deferredSelectedLines = void 0;
		if (editorActiveLine != null) this.setEditorActiveLine(...editorActiveLine);
		if (selectedLines != null) this.interactionManager.setSelection(...selectedLines);
	}
	flushManagers() {
		if (!this.managersDirty || this.pre == null) {
			this.managersDirty = false;
			return;
		}
		const { diffStyle = "split", overflow = "scroll" } = this.options;
		this.interactionManager.setup(this.pre);
		this.resizeManager.setup(this.pre, {
			disableAnnotations: overflow === "wrap",
			columnVariables: this.shouldApplyColumnVariables(overflow) ? "apply" : "measure"
		});
		if (overflow === "scroll" && diffStyle === "split") this.scrollSyncManager.setup(this.pre, this.codeDeletions, this.codeAdditions);
		else this.scrollSyncManager.cleanUp();
		this.managersDirty = false;
	}
	shouldApplyColumnVariables(overflow) {
		if (typeof this.options.hunkSeparators === "function") return true;
		return overflow === "scroll" && (this.getLatestAnnotations().length > 0 || this.pre?.hasAttribute("data-has-merge-conflict") === true);
	}
	getCodeScrollLeft() {
		return Math.max(this.codeUnified?.scrollLeft ?? 0, this.codeDeletions?.scrollLeft ?? 0, this.codeAdditions?.scrollLeft ?? 0);
	}
	setCodeScrollLeft(position) {
		if (this.codeUnified != null) this.codeUnified.scrollLeft = position;
		if (this.codeAdditions != null) this.codeAdditions.scrollLeft = position;
		if (this.codeDeletions != null) this.codeDeletions.scrollLeft = position;
	}
	__getEffectiveCodeOptions() {
		return {
			...this.options,
			...this.hunksRenderer.getEffectiveCodeOptions()
		};
	}
	cleanUp(recycle = false) {
		const { editor } = this;
		dequeueRender(this.handleEditSessionRender);
		this.emitPostRender(true);
		editor?.cleanUp(recycle ? "recycle" : "discard");
		if (!recycle) this.editor = void 0;
		this.resizeManager.cleanUp();
		this.interactionManager.cleanUp();
		this.scrollSyncManager.cleanUp();
		this.managersDirty = false;
		this.workerManager?.unsubscribeToThemeChanges(this);
		this.renderRange = void 0;
		if (!recycle) this.pendingFiles = void 0;
		if (!this.isContainerManaged) this.fileContainer?.remove();
		this.fileContainer = void 0;
		this.mounted = false;
		if (!recycle) this.lineAnnotations = [];
		this.clearAuxiliaryNodes();
		this.annotationCache.clear();
		this.pre = void 0;
		this.codeUnified = void 0;
		this.codeDeletions = void 0;
		this.codeAdditions = void 0;
		this.bufferBefore?.remove();
		this.bufferBefore = void 0;
		this.bufferAfter?.remove();
		this.bufferAfter = void 0;
		this.appliedPreAttributes = void 0;
		this.headerElement = void 0;
		this.headerPrefix = void 0;
		this.headerFilenameSuffix = void 0;
		this.headerMetadata = void 0;
		this.headerCustom = void 0;
		this.placeHolder?.remove();
		this.placeHolder = void 0;
		this.headerCache.lastRenderedHTML = void 0;
		if (!recycle) this.clearReusableHeader();
		this.errorWrapper?.remove();
		this.errorWrapper = void 0;
		this.spriteSVG = void 0;
		this.lastRowCount = void 0;
		this.themeCSSStyle = void 0;
		this.appliedThemeCSS = void 0;
		this.hasAdoptedThemeCSS = false;
		this.unsafeCSSStyle = void 0;
		this.appliedUnsafeCSS = void 0;
		if (recycle) this.hunksRenderer.recycle();
		else {
			this.hunksRenderer.cleanUp();
			this.workerManager = void 0;
			this.fileDiff = void 0;
			this.editSession = void 0;
			this.renderedDiff = void 0;
			this.deletionFile = void 0;
			this.additionFile = void 0;
		}
		if (this.refreshViewTimeout != null) {
			clearTimeout(this.refreshViewTimeout);
			this.refreshViewTimeout = void 0;
		}
		this.lineStateRefreshPending = false;
		this.deferredEditorActiveLine = void 0;
		this.deferredSelectedLines = void 0;
		this.enabled = false;
	}
	virtualizedSetup() {
		this.enabled = true;
		this.workerManager?.subscribeToThemeChanges(this);
	}
	hydrate({ fileContainer, prerenderedHTML, preventEmit = false, lineAnnotations, fileDiff, ...fileInputProps }) {
		if (!this.enabled) throw new Error("FileDiff.hydrate: attempting to call hydrate after cleaned up");
		if (this.fileContainer != null) throw new Error("FileDiff.hydrate: hydrate can only be called before the instance has rendered or hydrated");
		const fileInput = getDiffFileInput(fileInputProps, "FileDiff.hydrate");
		const oldFile = fileInput?.oldFile;
		const newFile = fileInput?.newFile;
		this.hydrateElements(fileContainer, prerenderedHTML);
		const forceEditorRender = this.editor != null;
		if (forceEditorRender || shouldRenderCode(this.pre, hasDiffContent({
			fileDiff,
			oldFile,
			newFile
		}), this.options.collapsed) || shouldRenderHeader(this.headerElement, hasDiffHeaderContent({
			fileDiff,
			oldFile,
			newFile
		}), this.options.disableFileHeader)) this.render({
			...fileInputProps,
			fileContainer,
			lineAnnotations,
			fileDiff,
			forceRender: forceEditorRender || fileInputProps.forceRender,
			preventEmit: true
		});
		else this.hydrationSetup({
			fileDiff,
			lineAnnotations,
			...fileInput
		});
		if (!preventEmit) this.emitPostRender();
	}
	hydrateElements(fileContainer, prerenderedHTML) {
		if (this.fileContainer !== fileContainer) this.emitPostRender(true);
		prerenderHTMLIfNecessary(fileContainer, prerenderedHTML);
		for (const element of fileContainer.shadowRoot?.children ?? []) {
			if (element instanceof SVGElement) {
				this.spriteSVG = element;
				continue;
			}
			if (!(element instanceof HTMLElement)) continue;
			if (element instanceof HTMLPreElement) {
				this.pre = element;
				for (const code of element.children) {
					if (!(code instanceof HTMLElement) || code.tagName.toLowerCase() !== "code") continue;
					if ("deletions" in code.dataset) this.codeDeletions = code;
					if ("additions" in code.dataset) this.codeAdditions = code;
					if ("unified" in code.dataset) this.codeUnified = code;
				}
				continue;
			}
			if ("diffsHeader" in element.dataset) {
				this.headerElement = element;
				continue;
			}
			if (element instanceof HTMLStyleElement && element.hasAttribute("data-theme-css")) {
				this.themeCSSStyle = element;
				continue;
			}
			if (element instanceof HTMLStyleElement && element.hasAttribute("data-unsafe-css")) {
				this.unsafeCSSStyle = element;
				this.appliedUnsafeCSS = element.textContent;
				continue;
			}
		}
		if (this.pre != null) {
			this.syncCodeNodesFromPre(this.pre);
			this.pre.removeAttribute("data-dehydrated");
		}
		this.fileContainer = fileContainer;
		this.hydrateMeasuredScrollbar();
	}
	hydrationSetup({ fileDiff, oldFile, newFile, lineAnnotations }) {
		this.lineAnnotations = lineAnnotations ?? this.lineAnnotations;
		this.additionFile = newFile;
		this.deletionFile = oldFile;
		this.fileDiff = fileDiff ?? (oldFile !== void 0 && newFile !== void 0 ? parseDiffFromFile(oldFile, newFile, this.options.parseDiffOptions) : void 0);
		if (this.pre == null) return;
		this.syncInteractionOptions();
		this.hunksRenderer.hydrate(this.fileDiff);
		this.renderedDiff = this.fileDiff;
		this.renderAnnotations();
		this.renderGutterUtility();
		this.injectUnsafeCSS();
		this.managersDirty = true;
		this.flushManagers();
	}
	rerender() {
		if (!this.enabled || this.fileDiff == null && this.additionFile == null && this.deletionFile == null) return;
		this.render({
			forceRender: true,
			renderRange: this.renderRange
		});
	}
	onThemeChange() {
		this.hunksRenderer.clearRenderCache();
		this.rerender();
	}
	handleExpandHunk = (hunkIndex, direction, expansionLineCountOverride) => {
		this.expandHunk(hunkIndex, direction, expansionLineCountOverride);
	};
	expandHunk = (hunkIndex, direction, expansionLineCountOverride) => {
		this.hunksRenderer.expandHunk(hunkIndex, direction, expansionLineCountOverride);
		this.loadFilesIfNecessary();
		this.rerender();
	};
	loadFilesIfNecessary() {
		const { fileDiff, options: { loadDiffFiles } } = this;
		if (fileDiff == null || loadDiffFiles == null || !canHydrateDiff(fileDiff) || this.pendingFiles?.fileDiff === fileDiff) return;
		const promise = this.loadFilesForDiff(fileDiff, loadDiffFiles);
		const pendingFiles = this.pendingFiles = {
			fileDiff,
			promise
		};
		const clearPendingFiles = () => {
			if (this.pendingFiles === pendingFiles) this.pendingFiles = void 0;
		};
		pendingFiles.promise = promise.finally(clearPendingFiles);
	}
	async loadFilesForDiff(fileDiff, loadDiffFiles) {
		try {
			const files = await loadDiffFiles(fileDiff);
			if (!this.enabled || this.fileDiff !== fileDiff) return;
			await this.handleFilesLoaded(fileDiff, files);
		} catch (error) {
			if (this.options.disableErrorHandling === true) throw error;
			console.error(error);
		}
	}
	async handleFilesLoaded(expectedDiff, files) {
		if (this.fileDiff !== expectedDiff || !expectedDiff.isPartial) return;
		hydratePartialDiff("merge", expectedDiff, files);
		this.setHydratedState(files);
		if (this.startHydratedEditSession(expectedDiff)) {
			this.rerender();
			return;
		}
		await awaitWithTimeout(() => this.primeHighlightCache(expectedDiff));
		if (!this.enabled || this.fileDiff !== expectedDiff) return;
		this.rerender();
	}
	startHydratedEditSession(expectedDiff) {
		if (expectedDiff.isPartial) throw new Error("FileDiff.startHydratedEditSession: diffs cannot be partial for editing");
		if (this.fileDiff !== expectedDiff) return false;
		const { editor } = this;
		if (this.editSession?.outgoingDiff != null) {
			this.installEditSession(expectedDiff);
			return true;
		}
		if (editor == null || this.editSession != null) return false;
		this.installEditSession(expectedDiff, editor.__getDocumentContents(getAdditionFile(expectedDiff)), editor.__getDocumentSessionState());
		return true;
	}
	updateExternalDiff(incomingExternalDiff, lineAnnotations) {
		if (areDiffTargetsEqual(this.fileDiff, incomingExternalDiff)) return false;
		const outgoingDiff = this.editSession?.outgoingDiff ?? this.editSession?.diff;
		this.fileDiff = incomingExternalDiff;
		if (outgoingDiff != null) {
			if (incomingExternalDiff.isPartial) this.loadFilesIfNecessary();
			else this.installEditSession(incomingExternalDiff);
			if (this.editSession != null) this.editSession.outgoingDiff = outgoingDiff;
		} else if (this.editor != null) if (incomingExternalDiff.isPartial) this.loadFilesIfNecessary();
		else this.installEditSession(incomingExternalDiff, this.editor.__getDocumentContents(getAdditionFile(incomingExternalDiff)), this.editor.__getDocumentSessionState());
		if (this.editSession?.annotations != null && lineAnnotations != null) {
			this.lineAnnotations = lineAnnotations;
			this.editSession.annotations = adoptEditSessionAnnotations(lineAnnotations, getLineAnnotationName, this.editSession.annotations);
		}
		return true;
	}
	installEditSession(externalDiff, retainedDocument, retainedSession) {
		const externalContents = externalDiff.additionLines.join("");
		const retainedLines = retainedDocument != null ? splitFileContents(retainedDocument.contents) : void 0;
		const restoreRetainedSession = retainedSession != null && canRestoreDiffSession(retainedSession, externalDiff);
		if (retainedSession != null && !restoreRetainedSession) throw new Error("FileDiff: retained session cannot resume against a different old file");
		if (retainedDocument != null && retainedDocument.contents !== externalContents && !restoreRetainedSession) throw new Error("FileDiff: retained edits are missing their diff session state");
		const usesExternalDocument = retainedDocument == null || retainedDocument.name === externalDiff.name && retainedDocument.lang === externalDiff.lang && retainedDocument.contents === externalContents;
		const sessionDiff = createEditSessionDiff(externalDiff);
		if (retainedDocument != null && retainedLines != null && !usesExternalDocument) {
			sessionDiff.name = retainedDocument.name;
			sessionDiff.lang = retainedDocument.lang;
		}
		if (restoreRetainedSession && retainedSession != null && retainedLines != null) {
			sessionDiff.additionLines = retainedLines;
			sessionDiff.type = retainedSession.type;
			sessionDiff.hunks = retainedSession.hunks;
			sessionDiff.editSessionDirty = true;
			recomputeDiffRenderLineCounts(sessionDiff);
		}
		this.editSession = {
			diff: sessionDiff,
			annotations: this.editSession?.annotations ?? adoptEditSessionAnnotations(this.lineAnnotations, getLineAnnotationName),
			outgoingDiff: this.editSession?.outgoingDiff
		};
		this.hunksRenderer.beginEditSession(sessionDiff, usesExternalDocument && !restoreRetainedSession ? externalDiff : void 0);
	}
	setHydratedState(files) {
		this.deletionFile = files.oldFile;
		this.additionFile = files.newFile;
		this.workerManager?.cleanUpTasks(this.hunksRenderer);
		this.hunksRenderer.clearRenderCache();
	}
	render({ fileDiff, deferManagers = false, forceRender = false, preventEmit = false, lineAnnotations, fileContainer, containerWrapper, renderRange, ...fileInputProps }) {
		if (!this.enabled) throw new Error("FileDiff.render: attempting to call render after cleaned up");
		const fileInput = getDiffFileInput(fileInputProps, "FileDiff.render");
		const oldFile = fileInput?.oldFile;
		const newFile = fileInput?.newFile;
		this.editor?.__postponeBgTokenizeToNextFrame();
		const { collapsed = false, themeType = "system", expandUnchanged = false } = this.options;
		const nextRenderRange = collapsed ? void 0 : renderRange;
		const themeChanged = this.hasThemeChanged();
		const hasFileInput = fileInput != null;
		const filesDidChange = hasFileInput && (!areOptionalFilesEqual(oldFile, this.deletionFile) || !areOptionalFilesEqual(newFile, this.additionFile));
		let diffDidChange = fileDiff != null && !areDiffTargetsEqual(fileDiff, this.fileDiff);
		const annotationsChanged = lineAnnotations != null && (lineAnnotations.length > 0 || this.getLatestAnnotations().length > 0) ? this.isNewAnnotations(lineAnnotations) : false;
		if (!collapsed && areRenderRangesEqual(nextRenderRange, this.renderRange) && !forceRender && !annotationsChanged && !themeChanged && (fileDiff != null && !diffDidChange || fileDiff == null && !filesDidChange)) {
			const rendered = this.applyCachedThemeState(themeType);
			if (rendered) this.finalizeRender();
			return rendered;
		}
		let nextParsedFileDiff;
		if (fileDiff == null && hasFileInput && (filesDidChange || this.fileDiff == null)) nextParsedFileDiff = parseDiffFromFile(fileInput.oldFile, fileInput.newFile, this.options.parseDiffOptions);
		const { renderRange: previousRenderRange } = this;
		this.renderRange = nextRenderRange;
		if (hasFileInput) {
			this.deletionFile = oldFile;
			this.additionFile = newFile;
		} else if (fileDiff != null) {
			this.deletionFile = void 0;
			this.additionFile = void 0;
		}
		if (fileDiff != null && diffDidChange) this.updateExternalDiff(fileDiff, lineAnnotations);
		else if (nextParsedFileDiff != null) {
			diffDidChange = true;
			this.updateExternalDiff(nextParsedFileDiff, lineAnnotations);
		}
		if (diffDidChange) this.clearReusableHeader();
		if (lineAnnotations != null) this.setLineAnnotations(lineAnnotations);
		const latestDiff = this.getLatestDiff();
		if (latestDiff == null) return false;
		if (latestDiff.editSessionDirty === true && this.shouldSelfHealEditSession()) {
			finishEditSessionForDiff(latestDiff, this.options.parseDiffOptions);
			this.hunksRenderer.refreshHighlightedResult();
		}
		if (expandUnchanged) this.loadFilesIfNecessary();
		this.hunksRenderer.setOptions(this.getHunksRendererOptions(this.options));
		this.syncInteractionOptions();
		this.hunksRenderer.setLineAnnotations(this.getLatestAnnotations());
		const { disableErrorHandling = false, disableFileHeader = false } = this.options;
		if (disableFileHeader) {
			if (this.headerElement != null) {
				this.headerElement.remove();
				this.headerElement = void 0;
				this.headerCache.lastRenderedHTML = void 0;
			}
			this.clearHeaderSlots();
		}
		fileContainer = this.getOrCreateFileContainer(fileContainer, containerWrapper);
		this.applyCachedThemeState(themeType);
		if (collapsed) {
			this.removeRenderedCode();
			this.clearAuxiliaryNodes();
			try {
				const hunksResult = this.hunksRenderer.renderDiff(latestDiff, EMPTY_RENDER_RANGE);
				if (hunksResult != null) this.applyThemeState(fileContainer, hunksResult.themeStyles, themeType, hunksResult.baseThemeType);
				if (hunksResult?.headerElement != null) this.applyHeaderToDOM(hunksResult.headerElement, fileContainer, hunksResult.fileDiff);
				this.renderSeparators([]);
				this.renderedDiff = hunksResult?.fileDiff ?? latestDiff;
				this.injectUnsafeCSS();
			} catch (error) {
				if (disableErrorHandling) throw error;
				console.error(error);
				if (error instanceof Error) this.applyErrorToDOM(error, fileContainer);
			}
			this.finalizeRender();
			if (!preventEmit) this.emitPostRender();
			return true;
		}
		try {
			const pre = this.getOrCreatePreNode(fileContainer);
			if (!(this.canPartiallyRender(forceRender, annotationsChanged, filesDidChange || diffDidChange || themeChanged || !areDiffTargetsEqual(this.renderedDiff, latestDiff)) && this.applyPartialRender({
				fileDiff: latestDiff,
				previousRenderRange,
				renderRange: nextRenderRange
			}))) {
				const hunksResult = this.hunksRenderer.renderDiff(latestDiff, nextRenderRange);
				if (hunksResult == null) {
					if (this.workerManager?.isInitialized() === false && this.workerManager.isWorkingPool()) this.workerManager.initialize().catch(() => {}).then(() => this.rerender());
					return false;
				}
				this.applyThemeState(fileContainer, hunksResult.themeStyles, themeType, hunksResult.baseThemeType);
				if (hunksResult.headerElement != null) this.applyHeaderToDOM(hunksResult.headerElement, fileContainer, hunksResult.fileDiff);
				if (hunksResult.additionsContentAST != null || hunksResult.deletionsContentAST != null || hunksResult.unifiedContentAST != null) this.applyHunksToDOM(pre, hunksResult);
				else if (this.pre != null) {
					this.pre.remove();
					this.pre = void 0;
				}
				this.renderSeparators(hunksResult.hunkData);
				this.renderedDiff = hunksResult.fileDiff;
			}
			this.applyBuffers(pre, nextRenderRange);
			this.injectUnsafeCSS();
			this.renderAnnotations();
			this.renderGutterUtility();
			this.managersDirty = true;
			if (!deferManagers) this.flushManagers();
			this.finalizeRender();
			if (this.editor != null) this.syncRenderViewToEditor();
		} catch (error) {
			if (disableErrorHandling) throw error;
			console.error(error);
			if (error instanceof Error) this.applyErrorToDOM(error, fileContainer);
		}
		if (!preventEmit) this.emitPostRender();
		return true;
	}
	finalizeRender() {}
	emitPostRender(unmount = false) {
		const { fileContainer, options: { onPostRender } } = this;
		if (unmount) {
			if (!this.mounted) return;
			this.mounted = false;
			if (fileContainer == null) return;
			this.options.onPostRender?.(fileContainer, this, "unmount");
			return;
		}
		if (fileContainer == null) return;
		const phase = this.mounted ? "update" : "mount";
		this.mounted = true;
		onPostRender?.(fileContainer, this, phase);
	}
	getLatestDiff(fileDiff = this.fileDiff) {
		return this.editSession?.diff ?? fileDiff;
	}
	getRenderedDiff() {
		return this.renderedDiff;
	}
	syncRenderViewToEditor() {
		const { editor, fileContainer } = this;
		const lineAnnotations = this.getLatestAnnotations();
		const renderRange = this.computeEditorRenderRange(this.renderRange);
		const fileDiff = this.getLatestDiff();
		if (editor == null || fileContainer == null || fileDiff == null || fileDiff.isPartial) return;
		const sync = (highlighter) => {
			if (!this.enabled || this.editor !== editor || this.fileContainer !== fileContainer || this.getLatestDiff() !== fileDiff) return;
			const replacement = this.editSession?.outgoingDiff;
			const externalDiff = this.fileDiff;
			const externalDocument = replacement != null && externalDiff != null && replacement !== fileDiff;
			const resetHistory = externalDocument ? shouldResetUndoState(replacement, externalDiff) : false;
			editor.__syncRenderView({
				highlighter,
				fileContainer,
				fileDiff,
				lineAnnotations,
				renderRange,
				externalDocument,
				resetHistory
			});
		};
		const theme = this.getTheme();
		const lang = fileDiff.lang ?? getFiletypeFromFileName(fileDiff.name);
		const highlighter = getHighlighterIfLoaded({
			theme,
			lang
		});
		if (highlighter != null) sync(highlighter);
		else getSharedHighlighter({
			themes: getThemes(theme),
			langs: ["text", lang],
			preferredHighlighter: this.workerManager?.getPreferredHighlighter() ?? this.options.preferredHighlighter
		}).then(sync);
	}
	computeEditorRenderRange(renderRange) {
		const fileDiff = this.getLatestDiff();
		if (renderRange == null || fileDiff == null || isDefaultRenderRange(renderRange)) return renderRange;
		const { diffStyle = "split", expandUnchanged = false, collapsedContextThreshold = 1 } = this.options;
		let firstLineNumber;
		let lastLineNumber;
		iterateOverDiff({
			diff: fileDiff,
			diffStyle,
			startingLine: renderRange.startingLine,
			totalLines: renderRange.totalLines,
			expandedHunks: expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap(),
			collapsedContextThreshold,
			callback: ({ additionLine }) => {
				if (additionLine != null) {
					firstLineNumber ??= additionLine.lineNumber;
					lastLineNumber = additionLine.lineNumber;
				}
			}
		});
		if (firstLineNumber == null || lastLineNumber == null) return {
			...renderRange,
			startingLine: 0,
			totalLines: 0
		};
		return {
			...renderRange,
			startingLine: firstLineNumber - 1,
			totalLines: lastLineNumber - firstLineNumber + 1
		};
	}
	/** @internal The editor applied or edited past the pending external replacement. */
	__acknowledgeDocumentUpdate() {
		if (this.editSession != null) this.editSession.outgoingDiff = void 0;
	}
	/** @internal Settle annotations locally. */
	__acceptEditorChange(event) {
		const { lineAnnotations } = event;
		if (lineAnnotations != null) this.syncEditSessionAnnotationsFromEditor(lineAnnotations);
	}
	emitEditChange(event) {
		const { onEditChange } = this.options;
		onEditChange?.(event);
	}
	/**
	* @internal Capture the current diff session, or return `undefined` when no
	* complete compatible session exists.
	*
	* When `clone` is true, the returned lines and hunks are copied.
	*/
	__captureDocumentSessionState(clone = true) {
		let { fileDiff, editSession: { diff: sessionDiff, outgoingDiff } = {} } = this;
		if (outgoingDiff != null && fileDiff != null) {
			if (!fileDiff.isPartial && shouldResetUndoState(outgoingDiff, fileDiff)) return;
			sessionDiff = outgoingDiff;
		}
		if (sessionDiff == null || sessionDiff.isPartial) return;
		return {
			diffSession: {
				oldFile: sessionDiff.type !== "new" ? {
					name: sessionDiff.prevName ?? sessionDiff.name,
					lines: clone ? [...sessionDiff.deletionLines] : sessionDiff.deletionLines
				} : null,
				type: sessionDiff.type,
				hunks: clone ? cloneHunks(sessionDiff.hunks) : sessionDiff.hunks
			},
			hasChanges: sessionDiff.editSessionDirty === true
		};
	}
	/** @internal Associate this component with its editor for a render lifecycle. */
	__attachEditor(editor) {
		if (this.type !== "file-diff") throw new Error(`FileDiff.__attachEditor: cannot attach an editor to a "${this.type}" diff`);
		if (this.editor != null) throw new Error("FileDiff.__attachEditor: an editor is already attached");
		const detach = () => {
			this.editor = void 0;
			this.finishEditSession();
		};
		try {
			this.resumeEditorRendering(editor);
			return detach;
		} catch (error) {
			detach();
			throw error;
		}
	}
	/** @internal Resume rendering for the editor already associated with this component. */
	__resumeEditor(editor) {
		if (this.editor !== editor) throw new Error("FileDiff.__resumeEditor: editor association changed");
		this.resumeEditorRendering(editor);
	}
	resumeEditorRendering(editor) {
		const { fileDiff: externalDiff } = this;
		const pendingReplacement = this.editSession?.outgoingDiff;
		if (pendingReplacement != null && externalDiff != null && !externalDiff.isPartial && this.editSession?.diff === pendingReplacement) this.installEditSession(externalDiff);
		const initialExternalDiff = this.editSession == null && externalDiff != null && !externalDiff.isPartial ? externalDiff : void 0;
		if (initialExternalDiff != null) this.installEditSession(initialExternalDiff, editor.__getDocumentContents(getAdditionFile(initialExternalDiff)), editor.__getDocumentSessionState());
		else if (this.editSession != null) this.hunksRenderer.beginEditSession(this.editSession.diff);
		this.editor = editor;
		if (this.fileDiff?.isPartial === true) this.loadFilesIfNecessary();
		const editSessionDiff = this.editSession?.diff;
		if (this.hunksRenderer.editorRenderReady()) {
			if (editSessionDiff != null && this.hunksRenderer.diffCache === editSessionDiff) this.renderedDiff = editSessionDiff;
			this.syncRenderViewToEditor();
		} else this.rerender();
	}
	finishEditSession() {
		this.hunksRenderer.endEditSession();
		this.finalizeEditSessionHunks();
	}
	/**
	* @internal
	*
	* Ends the edit session and settles which diff this component renders.
	* Requires the editor to be detached first. Does nothing when no session
	* exists, so callers can invoke it again safely after it has settled.
	*
	* `onEditComplete` receives the completed diff, current external diff,
	* complete file pair, and both annotation collections even when the final
	* text is unchanged. In `install` mode, accepting installs the completed diff
	* and its annotations; rejecting or having no handler restores the external
	* values. `discard` mode always restores the external values. An accepted
	* diff cannot reuse the replaced diff's `cacheKey`.
	*/
	__completeEditSession(editor, mode) {
		this.settleEditSession(mode === "install", editor);
	}
	settleEditSession(installResult, editor) {
		const { editSession, fileDiff: externalDiff, lineAnnotations: externalAnnotations } = this;
		if (editSession == null || externalDiff == null) return;
		const { diff: editSessionDiff, annotations: editSessionAnnotations } = editSession;
		if (this.editor != null) throw new Error("FileDiff.__completeEditSession: detach the editor before completing the session");
		this.hunksRenderer.endEditSession();
		this.finalizeEditSessionHunks();
		const sessionAnnotationsCurrent = editSessionAnnotations?.current;
		let acceptedDiff;
		let acceptedOldFile = null;
		let acceptedNewFile = null;
		let failed = false;
		let failure;
		if (editor == null) throw new Error("FileDiff.__completeEditSession: editor is required for completion");
		const completedDiff = cloneFileDiffMetadata(editSessionDiff);
		const newFile = {
			name: completedDiff.name,
			contents: completedDiff.additionLines.join("")
		};
		if (completedDiff.lang != null) newFile.lang = completedDiff.lang;
		const event = {
			fileDiff: completedDiff,
			editor,
			originalFileDiff: externalDiff,
			oldFile: completedDiff.type === "new" ? null : {
				name: completedDiff.prevName ?? completedDiff.name,
				contents: completedDiff.deletionLines.join("")
			},
			newFile,
			lineAnnotations: sessionAnnotationsCurrent,
			originalLineAnnotations: externalAnnotations
		};
		Object.freeze(event);
		try {
			editor.__emitEditComplete(event);
			if (this.options.onEditComplete?.(event) === "accept") {
				if (completedDiff.cacheKey != null && completedDiff.cacheKey === externalDiff.cacheKey) throw new Error("FileDiff.__completeEditSession: an accepted diff must not reuse the replaced diff cacheKey");
				acceptedDiff = completedDiff;
				acceptedOldFile = event.oldFile;
				acceptedNewFile = event.newFile;
			}
		} catch (error) {
			failed = true;
			failure = error;
		}
		if (installResult && acceptedDiff != null) {
			this.fileDiff = acceptedDiff;
			if (this.additionFile != null) {
				this.deletionFile = acceptedOldFile;
				this.additionFile = acceptedNewFile;
			}
			if (sessionAnnotationsCurrent != null) this.lineAnnotations = sessionAnnotationsCurrent;
		}
		this.editSession = void 0;
		if (installResult && this.fileContainer != null) this.rerender();
		if (failed) throw failure;
	}
	/**
	* Run the session-end recompute: restore recompute-shaped hunks (a
	* context-only region collapses away, boundaries re-derive), preserve
	* expansion state best-effort via old-side anchors, and repaint through
	* the session render path — which also invalidates virtualized layout,
	* since nothing else does at exit now that editing does not flip
	* expandUnchanged. Marker-guarded and idempotent; CodeView also calls this
	* when ending a session whose detach closure was consumed by a recycle.
	* Safe on a cleaned-up instance: the recompute is pure metadata work and
	* the deferred rerender is enabled-guarded. Returns true when a recompute
	* ran.
	*/
	finalizeEditSessionHunks() {
		const fileDiff = this.getLatestDiff();
		if (fileDiff == null || fileDiff.editSessionDirty !== true) return false;
		const { collapsedContextThreshold = 1 } = this.options;
		const anchors = captureExpansionAnchors(fileDiff, this.hunksRenderer.getExpandedHunksMap(), collapsedContextThreshold);
		finishEditSessionForDiff(fileDiff, this.options.parseDiffOptions);
		this.hunksRenderer.setExpandedHunksMap(rebuildExpansionFromAnchors(fileDiff, anchors));
		this.hunksRenderer.refreshHighlightedResult();
		this.escalateEditSessionRender();
		return true;
	}
	applyDocumentChange(textDocument, newLineAnnotations) {
		const editSessionDiff = this.editSession?.diff;
		if (editSessionDiff == null) throw new Error("FileDiff.applyDocumentChange: requires an active edit session");
		this.detachAdditionLines();
		this.hunksRenderer.beginEditSession(editSessionDiff);
		this.hunksRenderer.applyDocumentChange(textDocument);
		if (newLineAnnotations != null) this.syncEditSessionAnnotationsFromEditor(newLineAnnotations);
		this.rerender();
		this.interactionManager.setSelectionDirty();
	}
	updateRenderCache(dirtyLines, themeType, options = {}) {
		const editSessionDiff = this.editSession?.diff;
		if (editSessionDiff == null) throw new Error("FileDiff.updateRenderCache: requires an active edit session");
		this.detachAdditionLines();
		this.hunksRenderer.beginEditSession(editSessionDiff);
		const { shouldRefreshDiffsView, lineCountChangeInFlight } = options;
		if (this.hunksRenderer.updateRenderCache(dirtyLines, themeType, lineCountChangeInFlight)) {
			if (this.refreshViewTimeout != null) {
				clearTimeout(this.refreshViewTimeout);
				this.refreshViewTimeout = void 0;
			}
			this.lineStateRefreshPending = true;
			this.escalateEditSessionRender();
			return;
		}
		if (shouldRefreshDiffsView === true) {
			if (this.refreshViewTimeout != null) clearTimeout(this.refreshViewTimeout);
			this.lineStateRefreshPending = true;
			this.refreshViewTimeout = setTimeout(() => {
				this.refreshViewTimeout = void 0;
				if (this.options.diffStyle === "split") this.refreshSplitDiffView();
				else this.refreshUnifiedDiffView();
				this.flushDeferredLineState();
			}, 150);
		}
	}
	detachAdditionLines() {
		const editSessionDiff = this.editSession?.diff;
		const { fileDiff } = this;
		if (editSessionDiff != null && (editSessionDiff.additionLines === fileDiff?.additionLines || editSessionDiff.additionLines === editSessionDiff.deletionLines)) editSessionDiff.additionLines = [...editSessionDiff.additionLines];
	}
	isLineRenderable(lineNumber) {
		const fileDiff = this.getRenderedDiff();
		if (fileDiff == null) return true;
		const { expandUnchanged = false, collapsedContextThreshold = 1 } = this.options;
		return isAdditionLineRenderable({
			fileDiff,
			lineNumber,
			expandedHunks: expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap(),
			collapsedContextThreshold
		});
	}
	getNearestRenderableLine(lineNumber, direction) {
		const fileDiff = this.getRenderedDiff();
		if (fileDiff == null) return lineNumber;
		const { expandUnchanged = false, collapsedContextThreshold = 1 } = this.options;
		return getNearestRenderableAdditionLine({
			fileDiff,
			lineNumber,
			direction,
			expandedHunks: expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap(),
			collapsedContextThreshold
		});
	}
	revealLine(lineNumber) {
		const fileDiff = this.getRenderedDiff();
		const { expandUnchanged = false, collapsedContextThreshold = 1, expansionLineCount = 100 } = this.options;
		if (fileDiff == null || fileDiff.isPartial || expandUnchanged) return false;
		const expandedHunks = this.hunksRenderer.getExpandedHunksMap();
		for (const [hunkIndex, hunk] of fileDiff.hunks.entries()) {
			const [hunkStart, hunkEnd] = getHunkAdditionLineRange(hunk);
			if (lineNumber < hunkStart) {
				const region = getExpandedRegion({
					isPartial: fileDiff.isPartial,
					rangeSize: hunk.collapsedBefore,
					expandedHunks,
					hunkIndex,
					collapsedContextThreshold
				});
				const gapStart = hunkStart - region.rangeSize;
				if (region.renderAll || lineNumber < gapStart + region.fromStart || lineNumber >= hunkStart - region.fromEnd) return false;
				const fromStartDistance = lineNumber - (gapStart + region.fromStart) + 1;
				const fromEndDistance = hunkStart - region.fromEnd - lineNumber;
				if (fromStartDistance <= fromEndDistance) this.expandHunk(hunkIndex, "up", fromStartDistance + expansionLineCount);
				else this.expandHunk(hunkIndex, "down", fromEndDistance + expansionLineCount);
				return true;
			}
			if (lineNumber < hunkEnd) return false;
		}
		const trailingRegion = getTrailingExpandedRegion({
			fileDiff,
			hunkIndex: fileDiff.hunks.length - 1,
			expandedHunks,
			collapsedContextThreshold,
			errorPrefix: "FileDiff.revealLine"
		});
		if (trailingRegion == null || trailingRegion.renderAll) return false;
		const lastHunk = fileDiff.hunks[fileDiff.hunks.length - 1];
		const [, trailingStart] = getHunkAdditionLineRange(lastHunk);
		if (lineNumber < trailingStart + trailingRegion.fromStart || lineNumber >= trailingStart + trailingRegion.rangeSize) return false;
		this.expandHunk(fileDiff.hunks.length, "up", lineNumber - (trailingStart + trailingRegion.fromStart) + 1 + expansionLineCount);
		return true;
	}
	shouldSelfHealEditSession() {
		return this.editor == null;
	}
	escalateEditSessionRender() {
		queueRender(this.handleEditSessionRender);
	}
	handleEditSessionRender = () => {
		this.rerender();
		this.flushDeferredLineState();
	};
	removeRenderedCode() {
		this.resizeManager.cleanUp();
		this.scrollSyncManager.cleanUp();
		this.interactionManager.cleanUp();
		this.bufferBefore?.remove();
		this.bufferBefore = void 0;
		this.bufferAfter?.remove();
		this.bufferAfter = void 0;
		this.codeUnified?.remove();
		this.codeUnified = void 0;
		this.codeDeletions?.remove();
		this.codeDeletions = void 0;
		this.codeAdditions?.remove();
		this.codeAdditions = void 0;
		this.pre?.remove();
		this.pre = void 0;
		this.appliedPreAttributes = void 0;
		this.lastRowCount = void 0;
	}
	clearAuxiliaryNodes() {
		for (const { element } of this.separatorCache.values()) element.remove();
		this.separatorCache.clear();
		for (const { element } of this.annotationCache.values()) element.remove();
		this.annotationCache.clear();
		this.gutterUtilityContent?.remove();
		this.gutterUtilityContent = void 0;
	}
	renderPlaceholder(height) {
		if (this.fileContainer == null) return false;
		this.emitPostRender(true);
		this.cleanChildNodes();
		if (this.placeHolder == null) {
			const shadowRoot = this.fileContainer.shadowRoot ?? this.fileContainer.attachShadow({ mode: "open" });
			this.placeHolder = document.createElement("div");
			this.placeHolder.dataset.placeholder = "";
			shadowRoot.appendChild(this.placeHolder);
		}
		this.placeHolder.style.setProperty("height", `${height}px`);
		return true;
	}
	async primeHighlightCache(fileDiff = this.fileDiff) {
		const { workerManager } = this;
		if (fileDiff == null || workerManager == null || !workerManager.isWorkingPool() || fileDiff.cacheKey == null || isDiffPlainText(fileDiff)) return;
		const tokenizeMaxLength = this.options.tokenizeMaxLength ?? 1e5;
		if (Math.max(fileDiff.additionLines.length, fileDiff.deletionLines.length) > tokenizeMaxLength) return;
		await workerManager.primeDiffHighlightCache(fileDiff).catch((error) => {
			console.error(error);
		});
	}
	cleanChildNodes() {
		this.resizeManager.cleanUp();
		this.scrollSyncManager.cleanUp();
		this.interactionManager.cleanUp();
		this.clearAuxiliaryNodes();
		this.bufferAfter?.remove();
		this.bufferBefore?.remove();
		this.codeAdditions?.remove();
		this.codeDeletions?.remove();
		this.codeUnified?.remove();
		this.errorWrapper?.remove();
		this.headerElement?.remove();
		this.headerPrefix?.remove();
		this.headerFilenameSuffix?.remove();
		this.headerMetadata?.remove();
		this.headerCustom?.remove();
		this.pre?.remove();
		this.spriteSVG?.remove();
		this.themeCSSStyle?.remove();
		this.unsafeCSSStyle?.remove();
		this.bufferAfter = void 0;
		this.bufferBefore = void 0;
		this.codeAdditions = void 0;
		this.codeDeletions = void 0;
		this.codeUnified = void 0;
		this.errorWrapper = void 0;
		this.headerElement = void 0;
		this.headerPrefix = void 0;
		this.headerFilenameSuffix = void 0;
		this.headerMetadata = void 0;
		this.headerCustom = void 0;
		this.pre = void 0;
		this.spriteSVG = void 0;
		this.themeCSSStyle = void 0;
		this.appliedThemeCSS = void 0;
		this.hasAdoptedThemeCSS = false;
		this.unsafeCSSStyle = void 0;
		this.appliedUnsafeCSS = void 0;
		this.headerCache.lastRenderedHTML = void 0;
		this.lastRowCount = void 0;
		this.mounted = false;
	}
	renderSeparators(hunkData) {
		const { hunkSeparators } = this.options;
		if (this.isContainerManaged || this.fileContainer == null || typeof hunkSeparators !== "function") {
			for (const { element } of this.separatorCache.values()) element.remove();
			this.separatorCache.clear();
			return;
		}
		const staleSeparators = new Map(this.separatorCache);
		for (const hunk of hunkData) {
			const id = hunk.slotName;
			let cache = this.separatorCache.get(id);
			if (cache == null || !areHunkDataEqual(hunk, cache.hunkData)) {
				cache?.element.remove();
				const element = document.createElement("div");
				element.style.display = "contents";
				element.slot = hunk.slotName;
				const child = hunkSeparators(hunk, this);
				if (child != null) element.appendChild(child);
				this.fileContainer.appendChild(element);
				cache = {
					element,
					hunkData: hunk
				};
				this.separatorCache.set(id, cache);
			}
			staleSeparators.delete(id);
		}
		for (const [id, { element }] of staleSeparators.entries()) {
			this.separatorCache.delete(id);
			element.remove();
		}
	}
	renderAnnotations() {
		if (this.isContainerManaged || this.fileContainer == null) {
			for (const { element } of this.annotationCache.values()) element.remove();
			this.annotationCache.clear();
			return;
		}
		const staleAnnotations = new Map(this.annotationCache);
		const { renderAnnotation } = this.options;
		const lineAnnotations = this.getLatestAnnotations();
		if (renderAnnotation != null && lineAnnotations.length > 0) for (const [index, annotation] of lineAnnotations.entries()) {
			const name = this.getAnnotationSlotName(annotation);
			const id = `${index}-${name}`;
			let cache = this.annotationCache.get(id);
			if (cache == null || !areDiffLineAnnotationsEqual(annotation, cache.annotation)) {
				cache?.element.remove();
				const content = renderAnnotation(annotation);
				if (content == null) continue;
				cache = {
					element: createAnnotationWrapperNode(name),
					annotation
				};
				cache.element.appendChild(content);
				this.fileContainer.appendChild(cache.element);
				this.annotationCache.set(id, cache);
			}
			staleAnnotations.delete(id);
		}
		for (const [id, { element }] of staleAnnotations.entries()) {
			this.annotationCache.delete(id);
			element.remove();
		}
	}
	renderGutterUtility() {
		const { renderGutterUtility } = this.options;
		if (this.fileContainer == null || renderGutterUtility == null) {
			this.gutterUtilityContent?.remove();
			this.gutterUtilityContent = void 0;
			return;
		}
		const element = renderGutterUtility(this.interactionManager.getHoveredLine);
		if (element != null && this.gutterUtilityContent != null) return;
		else if (element == null) {
			this.gutterUtilityContent?.remove();
			this.gutterUtilityContent = void 0;
			return;
		}
		const gutterUtilityContent = createGutterUtilityContentNode();
		gutterUtilityContent.appendChild(element);
		this.fileContainer.appendChild(gutterUtilityContent);
		this.gutterUtilityContent = gutterUtilityContent;
	}
	getOrCreateFileContainer(fileContainer, parentNode) {
		const { fileContainer: previousContainer } = this;
		const nextContainer = fileContainer ?? previousContainer ?? document.createElement("diffs-container");
		const containerChanged = previousContainer !== nextContainer;
		if (previousContainer != null && containerChanged) this.editor?.__captureFocusForDOMReplacement();
		if (containerChanged) this.emitPostRender(true);
		this.fileContainer = nextContainer;
		if (previousContainer != null && containerChanged) {
			this.headerCache.lastRenderedHTML = void 0;
			this.headerElement = void 0;
		}
		if (parentNode != null && this.fileContainer.parentNode !== parentNode) parentNode.appendChild(this.fileContainer);
		if (containerChanged) this.adoptReusableShellElements(this.fileContainer);
		this.ensureSpriteSVG(this.fileContainer);
		return this.fileContainer;
	}
	adoptReusableShellElements(fileContainer) {
		const { shadowRoot } = fileContainer;
		if (shadowRoot == null) return;
		for (const element of shadowRoot.children) if (element instanceof SVGElement) this.spriteSVG ??= element;
		else if (isStyleNode(element) && element.hasAttribute("data-theme-css")) {
			this.themeCSSStyle ??= element;
			this.hasAdoptedThemeCSS = true;
		} else if (isStyleNode(element) && element.hasAttribute("data-unsafe-css")) {
			this.unsafeCSSStyle ??= element;
			this.appliedUnsafeCSS ??= this.options.unsafeCSS ?? void 0;
		}
	}
	ensureSpriteSVG(fileContainer) {
		const shadowRoot = fileContainer.shadowRoot ?? fileContainer.attachShadow({ mode: "open" });
		if (this.spriteSVG == null) {
			const fragment = document.createElement("div");
			fragment.innerHTML = SVGSpriteSheet;
			const firstChild = fragment.firstChild;
			if (firstChild instanceof SVGElement) this.spriteSVG = firstChild;
		}
		if (this.spriteSVG != null && this.spriteSVG.parentNode !== shadowRoot) shadowRoot.appendChild(this.spriteSVG);
	}
	getOrCreatePreNode(container) {
		const shadowRoot = container.shadowRoot ?? container.attachShadow({ mode: "open" });
		if (this.pre == null) {
			this.pre = document.createElement("pre");
			this.appliedPreAttributes = void 0;
			this.codeUnified = void 0;
			this.codeDeletions = void 0;
			this.codeAdditions = void 0;
			shadowRoot.appendChild(this.pre);
		} else if (this.pre.parentNode !== shadowRoot) {
			this.editor?.__captureFocusForDOMReplacement();
			shadowRoot.appendChild(this.pre);
			this.appliedPreAttributes = void 0;
		}
		this.placeHolder?.remove();
		this.placeHolder = void 0;
		return this.pre;
	}
	syncCodeNodesFromPre(pre) {
		this.codeUnified = void 0;
		this.codeDeletions = void 0;
		this.codeAdditions = void 0;
		for (const child of Array.from(pre.children)) {
			if (!(child instanceof HTMLElement)) continue;
			if (child.hasAttribute("data-unified")) this.codeUnified = child;
			else if (child.hasAttribute("data-deletions")) this.codeDeletions = child;
			else if (child.hasAttribute("data-additions")) this.codeAdditions = child;
		}
	}
	applyHeaderToDOM(headerAST, container, fileDiff) {
		this.cleanupErrorWrapper();
		this.placeHolder?.remove();
		this.placeHolder = void 0;
		const { headerCache: { fileDiff: cachedHeaderDiff, html: cachedHeaderHTML, lastRenderedHTML } } = this;
		const headerHTML = (fileDiff !== this.editSession?.diff && areDiffTargetsEqual(cachedHeaderDiff, fileDiff) ? cachedHeaderHTML : void 0) ?? toHtml(headerAST);
		this.headerCache.html = headerHTML;
		this.headerCache.fileDiff = fileDiff;
		if (headerHTML !== lastRenderedHTML) {
			const tempDiv = document.createElement("div");
			tempDiv.innerHTML = headerHTML;
			const newHeader = tempDiv.firstElementChild;
			if (!(newHeader instanceof HTMLElement)) return;
			if (this.headerElement != null) container.shadowRoot?.replaceChild(newHeader, this.headerElement);
			else container.shadowRoot?.prepend(newHeader);
			this.headerElement = newHeader;
			this.headerCache.lastRenderedHTML = headerHTML;
		}
		if (this.isContainerManaged) return;
		const { renderCustomHeader, renderHeaderPrefix, renderHeaderFilenameSuffix, renderHeaderMetadata } = this.options;
		if (renderCustomHeader != null) {
			const content = renderCustomHeader(fileDiff) ?? void 0;
			this.headerCustom = this.upsertHeaderSlotElement(container, this.headerCustom, CUSTOM_HEADER_SLOT_ID, content);
			this.headerPrefix?.remove();
			this.headerFilenameSuffix?.remove();
			this.headerMetadata?.remove();
			this.headerPrefix = void 0;
			this.headerFilenameSuffix = void 0;
			this.headerMetadata = void 0;
			return;
		}
		const prefix = renderHeaderPrefix?.(fileDiff) ?? void 0;
		const suffix = renderHeaderFilenameSuffix?.(fileDiff) ?? void 0;
		const content = renderHeaderMetadata?.(fileDiff) ?? void 0;
		this.headerPrefix = this.upsertHeaderSlotElement(container, this.headerPrefix, HEADER_PREFIX_SLOT_ID, prefix);
		this.headerFilenameSuffix = this.upsertHeaderSlotElement(container, this.headerFilenameSuffix, HEADER_FILENAME_SUFFIX_SLOT_ID, suffix);
		this.headerMetadata = this.upsertHeaderSlotElement(container, this.headerMetadata, HEADER_METADATA_SLOT_ID, content);
		this.headerCustom?.remove();
		this.headerCustom = void 0;
	}
	clearReusableHeader() {
		this.headerCache.html = void 0;
		this.headerCache.fileDiff = void 0;
	}
	clearHeaderSlots() {
		this.headerPrefix?.remove();
		this.headerFilenameSuffix?.remove();
		this.headerMetadata?.remove();
		this.headerCustom?.remove();
		this.headerPrefix = void 0;
		this.headerFilenameSuffix = void 0;
		this.headerMetadata = void 0;
		this.headerCustom = void 0;
	}
	upsertHeaderSlotElement(container, current, slot, content) {
		if (content == null) {
			current?.remove();
			return;
		}
		const element = current ?? this.createHeaderSlotElement(slot);
		if (current == null) container.appendChild(element);
		this.replaceHeaderSlotContent(element, content);
		return element;
	}
	replaceHeaderSlotContent(element, content) {
		element.replaceChildren();
		if (content instanceof Element) element.appendChild(content);
		else element.innerText = `${content}`;
	}
	createHeaderSlotElement(slot) {
		const element = document.createElement("div");
		element.slot = slot;
		return element;
	}
	injectUnsafeCSS() {
		const { unsafeCSS } = this.options;
		const shadowRoot = this.fileContainer?.shadowRoot;
		if (shadowRoot == null) return;
		if (unsafeCSS == null || unsafeCSS === "") {
			if (this.unsafeCSSStyle != null) {
				this.unsafeCSSStyle.remove();
				this.unsafeCSSStyle = void 0;
			}
			this.appliedUnsafeCSS = void 0;
			return;
		}
		if (this.unsafeCSSStyle?.parentNode === shadowRoot && this.appliedUnsafeCSS === unsafeCSS) return;
		this.unsafeCSSStyle ??= createUnsafeCSSStyleNode();
		if (this.unsafeCSSStyle.parentNode !== shadowRoot) shadowRoot.appendChild(this.unsafeCSSStyle);
		this.unsafeCSSStyle.textContent = wrapUnsafeCSS(unsafeCSS);
		this.appliedUnsafeCSS = unsafeCSS;
	}
	applyThemeState(container, themeStyles, themeType, baseThemeType) {
		const shadowRoot = container.shadowRoot ?? container.attachShadow({ mode: "open" });
		const effectiveThemeType = baseThemeType ?? themeType;
		const currentTheme = this.getTheme();
		const theme = typeof currentTheme === "string" ? currentTheme : { ...currentTheme };
		const scrollbarGutter = getMeasuredScrollbarGutter(shadowRoot);
		if (this.themeCSSStyle?.parentNode === shadowRoot && this.appliedThemeCSS?.themeStyles === themeStyles && this.appliedThemeCSS.themeType === effectiveThemeType && this.appliedThemeCSS.scrollbarGutter === scrollbarGutter) {
			this.appliedThemeCSS.theme = theme;
			return;
		}
		if (this.hasAdoptedThemeCSS && this.themeCSSStyle?.parentNode === shadowRoot) {
			this.hasAdoptedThemeCSS = false;
			this.appliedThemeCSS = {
				theme,
				themeStyles,
				themeType: effectiveThemeType,
				baseThemeType,
				scrollbarGutter
			};
			return;
		}
		this.themeCSSStyle = upsertHostThemeStyle({
			shadowRoot,
			currentNode: this.themeCSSStyle,
			themeCSS: wrapThemeCSS(themeStyles, effectiveThemeType, scrollbarGutter)
		});
		this.appliedThemeCSS = this.themeCSSStyle != null ? {
			theme,
			themeStyles,
			themeType: effectiveThemeType,
			baseThemeType,
			scrollbarGutter
		} : void 0;
	}
	hydrateMeasuredScrollbar() {
		const shadowRoot = this.fileContainer?.shadowRoot;
		if (shadowRoot == null || this.themeCSSStyle == null) return;
		this.themeCSSStyle.textContent = patchScrollbarGutterSize(this.themeCSSStyle.textContent ?? "", getMeasuredScrollbarGutter(shadowRoot));
	}
	shouldGuardRebuildScroll() {
		return this.editor != null && isSafari();
	}
	applyHunksToDOM(pre, result) {
		if (this.shouldGuardRebuildScroll()) guardWebKitScrollDuringRebuild(pre, () => this.replaceCodeColumns(pre, result));
		else this.replaceCodeColumns(pre, result);
	}
	applyCodeColumnsInPlace(code, ast, rowCount) {
		const columns = this.getColumnPair(code);
		if (columns == null) return false;
		const gutterChildren = getElementChildren(ast[0]);
		const contentChildren = getElementChildren(ast[1]);
		if (gutterChildren == null || contentChildren == null) return false;
		columns.gutter.innerHTML = toHtml(gutterChildren);
		columns.content.innerHTML = toHtml(contentChildren);
		if (rowCount !== this.lastRowCount) {
			columns.gutter.style.setProperty("grid-row", `span ${rowCount}`);
			columns.content.style.setProperty("grid-row", `span ${rowCount}`);
		}
		return true;
	}
	replaceCodeColumns(pre, result) {
		const { overflow = "scroll" } = this.options;
		const containerSize = (this.options.hunkSeparators ?? "line-info") === "line-info";
		const rowSpan = overflow === "wrap" ? result.rowCount : void 0;
		this.cleanupErrorWrapper();
		this.applyPreNodeAttributes(pre, result);
		let shouldReplace = false;
		const codeElements = [];
		const unifiedAST = this.hunksRenderer.renderCodeAST("unified", result);
		const deletionsAST = this.hunksRenderer.renderCodeAST("deletions", result);
		const additionsAST = this.hunksRenderer.renderCodeAST("additions", result);
		this.editor?.__captureFocusForDOMReplacement();
		if (unifiedAST != null) {
			shouldReplace = this.codeUnified == null || this.codeAdditions != null || this.codeDeletions != null;
			this.codeDeletions?.remove();
			this.codeDeletions = void 0;
			this.codeAdditions?.remove();
			this.codeAdditions = void 0;
			this.codeUnified = getOrCreateCodeNode({
				code: this.codeUnified,
				columnType: "unified",
				rowSpan,
				containerSize
			});
			if (!this.applyCodeColumnsInPlace(this.codeUnified, unifiedAST, result.rowCount)) this.codeUnified.innerHTML = this.hunksRenderer.renderPartialHTML(unifiedAST);
			codeElements.push(this.codeUnified);
		} else if (deletionsAST != null || additionsAST != null) {
			if (deletionsAST != null) {
				shouldReplace = this.codeDeletions == null || this.codeUnified != null;
				this.codeUnified?.remove();
				this.codeUnified = void 0;
				this.codeDeletions = getOrCreateCodeNode({
					code: this.codeDeletions,
					columnType: "deletions",
					rowSpan,
					containerSize
				});
				if (!this.applyCodeColumnsInPlace(this.codeDeletions, deletionsAST, result.rowCount)) this.codeDeletions.innerHTML = this.hunksRenderer.renderPartialHTML(deletionsAST);
				codeElements.push(this.codeDeletions);
			} else {
				this.codeDeletions?.remove();
				this.codeDeletions = void 0;
			}
			if (additionsAST != null) {
				shouldReplace = shouldReplace || this.codeAdditions == null || this.codeUnified != null;
				this.codeUnified?.remove();
				this.codeUnified = void 0;
				this.codeAdditions = getOrCreateCodeNode({
					code: this.codeAdditions,
					columnType: "additions",
					rowSpan,
					containerSize
				});
				if (!this.applyCodeColumnsInPlace(this.codeAdditions, additionsAST, result.rowCount)) this.codeAdditions.innerHTML = this.hunksRenderer.renderPartialHTML(additionsAST);
				codeElements.push(this.codeAdditions);
			} else {
				this.codeAdditions?.remove();
				this.codeAdditions = void 0;
			}
		} else {
			this.codeUnified?.remove();
			this.codeUnified = void 0;
			this.codeDeletions?.remove();
			this.codeDeletions = void 0;
			this.codeAdditions?.remove();
			this.codeAdditions = void 0;
		}
		if (codeElements.length === 0) pre.textContent = "";
		else if (shouldReplace) pre.replaceChildren(...codeElements);
		this.lastRowCount = result.rowCount;
	}
	applyPartialRender({ fileDiff, previousRenderRange, renderRange }) {
		const { pre, codeUnified, codeAdditions, codeDeletions, options: { diffStyle = "split" } } = this;
		if (pre == null || previousRenderRange == null || renderRange == null || !Number.isFinite(previousRenderRange.totalLines) || !Number.isFinite(renderRange.totalLines) || this.lastRowCount == null) return false;
		const codeElements = this.getCodeColumns(diffStyle, codeUnified, codeDeletions, codeAdditions);
		if (codeElements == null) return false;
		const previousStart = previousRenderRange.startingLine;
		const nextStart = renderRange.startingLine;
		const previousEnd = previousStart + previousRenderRange.totalLines;
		const nextEnd = nextStart + renderRange.totalLines;
		const overlapStart = Math.max(previousStart, nextStart);
		const overlapEnd = Math.min(previousEnd, nextEnd);
		if (overlapEnd <= overlapStart) return false;
		const trimStart = Math.max(0, overlapStart - previousStart);
		const trimEnd = Math.max(0, previousEnd - overlapEnd);
		const trimResult = this.trimColumns({
			columns: codeElements,
			trimStart,
			trimEnd,
			previousStart,
			overlapStart,
			overlapEnd,
			diffStyle
		});
		if (trimResult < 0) throw new Error("FileDiff.applyPartialRender: failed to trim to overlap");
		if (this.lastRowCount < trimResult) throw new Error("FileDiff.applyPartialRender: trimmed beyond DOM row count");
		let rowCount = this.lastRowCount - trimResult;
		const renderChunk = (startingLine, totalLines) => {
			if (totalLines <= 0) return;
			return this.hunksRenderer.renderDiff(fileDiff, {
				startingLine,
				totalLines,
				bufferBefore: 0,
				bufferAfter: 0
			});
		};
		const prependResult = renderChunk(nextStart, Math.max(overlapStart - nextStart, 0));
		if (prependResult == null && nextStart < overlapStart) return false;
		const appendResult = renderChunk(overlapEnd, Math.max(nextEnd - overlapEnd, 0));
		if (appendResult == null && nextEnd > overlapEnd) return false;
		const applyChunk = (result, insertPosition) => {
			if (result == null) return;
			if (diffStyle === "unified" && !Array.isArray(codeElements)) this.insertPartialHTML(diffStyle, codeElements, result, insertPosition);
			else if (diffStyle === "split" && Array.isArray(codeElements)) this.insertPartialHTML(diffStyle, codeElements, result, insertPosition);
			else throw new Error("FileDiff.applyPartialRender.applyChunk: invalid chunk application");
			rowCount += result.rowCount;
			this.renderedDiff = result.fileDiff;
		};
		this.cleanupErrorWrapper();
		applyChunk(prependResult, "afterbegin");
		applyChunk(appendResult, "beforeend");
		if (this.lastRowCount !== rowCount) {
			this.applyRowSpan(diffStyle, codeElements, rowCount);
			this.lastRowCount = rowCount;
		}
		return true;
	}
	insertPartialHTML(diffStyle, columns, result, insertPosition) {
		if (diffStyle === "unified" && !Array.isArray(columns)) {
			const unifiedAST = this.hunksRenderer.renderCodeAST("unified", result);
			this.renderPartialColumn(columns, unifiedAST, insertPosition);
		} else if (diffStyle === "split" && Array.isArray(columns)) {
			const deletionsAST = this.hunksRenderer.renderCodeAST("deletions", result);
			const additionsAST = this.hunksRenderer.renderCodeAST("additions", result);
			this.renderPartialColumn(columns[0], deletionsAST, insertPosition);
			this.renderPartialColumn(columns[1], additionsAST, insertPosition);
		} else throw new Error("FileDiff.insertPartialHTML: Invalid argument composition");
	}
	refreshSplitDiffView() {
		const fileDiff = this.getLatestDiff();
		if (this.options.diffStyle !== "split" || fileDiff == null) return;
		const hunksResult = this.hunksRenderer.renderDiff(fileDiff, this.renderRange);
		if (hunksResult == null) return;
		const columns = this.getCodeColumns("split", this.codeUnified, this.codeDeletions, this.codeAdditions);
		if (!Array.isArray(columns)) return;
		const applyLineType = (type, column) => {
			if (column == null) return;
			const ast = this.hunksRenderer.renderCodeAST(type, hunksResult);
			const gutterChildren = getElementChildren(ast?.[0]);
			const contentChildren = getElementChildren(ast?.[1]);
			for (const [el, astChildren] of [[column.gutter, gutterChildren], [column.content, contentChildren]]) if (astChildren != null && el.childElementCount === astChildren.length) for (let i = 0; i < astChildren.length; i++) {
				const gutterElement = el.children[i];
				const lineType = astChildren[i].properties["data-line-type"];
				if (lineType != null && gutterElement.dataset.lineType !== lineType) gutterElement.dataset.lineType = lineType;
			}
		};
		applyLineType("deletions", columns[0]);
		applyLineType("additions", columns[1]);
		this.renderedDiff = hunksResult.fileDiff;
	}
	refreshUnifiedDiffView() {
		const fileDiff = this.getLatestDiff();
		if (this.options.diffStyle !== "unified" || fileDiff == null) return;
		const hunksResult = this.hunksRenderer.renderDiff(fileDiff, this.renderRange);
		if (hunksResult == null) return;
		const columns = this.getCodeColumns("unified", this.codeUnified, this.codeDeletions, this.codeAdditions);
		if (columns == null || Array.isArray(columns)) return;
		const ast = this.hunksRenderer.renderCodeAST("unified", hunksResult);
		const gutterChildren = getElementChildren(ast?.[0]);
		const contentChildren = getElementChildren(ast?.[1]);
		const applyColumns = () => {
			for (const [el, astChildren] of [[columns.gutter, gutterChildren], [columns.content, contentChildren]]) if (astChildren != null) el.innerHTML = toHtml(astChildren);
			if (hunksResult.rowCount !== this.lastRowCount) {
				this.applyRowSpan("unified", columns, hunksResult.rowCount);
				this.lastRowCount = hunksResult.rowCount;
			}
			this.renderedDiff = hunksResult.fileDiff;
		};
		if (this.shouldGuardRebuildScroll()) guardWebKitScrollDuringRebuild(this.pre, applyColumns);
		else applyColumns();
		this.renderSeparators(hunksResult.hunkData);
		this.managersDirty = true;
		this.flushManagers();
		this.syncRenderViewToEditor();
	}
	renderPartialColumn(column, ast, insertPosition) {
		if (column == null || ast == null) return;
		const gutterChildren = getElementChildren(ast[0]);
		const contentChildren = getElementChildren(ast[1]);
		if (gutterChildren == null || contentChildren == null) throw new Error("FileDiff.insertPartialHTML: Unexpected AST structure");
		const firstHASTElement = contentChildren.at(0);
		if (insertPosition === "beforeend" && firstHASTElement?.type === "element" && typeof firstHASTElement.properties["data-buffer-size"] === "number") this.mergeBuffersIfNecessary(firstHASTElement.properties["data-buffer-size"], column.content.children[column.content.children.length - 1], column.gutter.children[column.gutter.children.length - 1], gutterChildren, contentChildren, true);
		const lastHASTElement = contentChildren.at(-1);
		if (insertPosition === "afterbegin" && lastHASTElement?.type === "element" && typeof lastHASTElement.properties["data-buffer-size"] === "number") this.mergeBuffersIfNecessary(lastHASTElement.properties["data-buffer-size"], column.content.children[0], column.gutter.children[0], gutterChildren, contentChildren, false);
		column.gutter.insertAdjacentHTML(insertPosition, this.hunksRenderer.renderPartialHTML(gutterChildren));
		column.content.insertAdjacentHTML(insertPosition, this.hunksRenderer.renderPartialHTML(contentChildren));
	}
	mergeBuffersIfNecessary(adjustmentSize, contentElement, gutterElement, gutterChildren, contentChildren, fromStart) {
		if (!(contentElement instanceof HTMLElement) || !(gutterElement instanceof HTMLElement)) return;
		const currentSize = this.getBufferSize(contentElement.dataset);
		if (currentSize == null) return;
		if (fromStart) {
			gutterChildren.shift();
			contentChildren.shift();
		} else {
			gutterChildren.pop();
			contentChildren.pop();
		}
		this.updateBufferSize(contentElement, currentSize + adjustmentSize);
		this.updateBufferSize(gutterElement, currentSize + adjustmentSize);
	}
	applyRowSpan(diffStyle, columns, rowCount) {
		const applySpan = (column) => {
			if (column == null) return;
			column.gutter.style.setProperty("grid-row", `span ${rowCount}`);
			column.content.style.setProperty("grid-row", `span ${rowCount}`);
		};
		if (diffStyle === "unified" && !Array.isArray(columns)) applySpan(columns);
		else if (diffStyle === "split" && Array.isArray(columns)) {
			applySpan(columns[0]);
			applySpan(columns[1]);
		} else throw new Error("dun fuuuuked up");
	}
	trimColumnRows(columns, preTrimCount, postTrimStart) {
		let visibleLineIndex = 0;
		let rowCount = 0;
		let rowIndex = 0;
		let pendingMetadataTrim = false;
		const hasPostTrim = postTrimStart >= 0;
		if (columns == null) return 0;
		const contentChildren = Array.from(columns.content.children);
		const gutterChildren = Array.from(columns.gutter.children);
		if (contentChildren.length !== gutterChildren.length) throw new Error("FileDiff.trimColumnRows: columns do not match");
		while (rowIndex < contentChildren.length) {
			if (preTrimCount <= 0 && !hasPostTrim && !pendingMetadataTrim) break;
			const gutterElement = gutterChildren[rowIndex];
			const contentElement = contentChildren[rowIndex];
			rowIndex++;
			if (!(gutterElement instanceof HTMLElement) || !(contentElement instanceof HTMLElement)) {
				console.error({
					gutterElement,
					contentElement
				});
				throw new Error("FileDiff.trimColumnRows: invalid row elements");
			}
			if (pendingMetadataTrim) {
				pendingMetadataTrim = false;
				if (gutterElement.dataset.gutterBuffer === "annotation" && "lineAnnotation" in contentElement.dataset || gutterElement.dataset.gutterBuffer === "metadata" && "noNewline" in contentElement.dataset) {
					gutterElement.remove();
					contentElement.remove();
					rowCount++;
					continue;
				}
			}
			if ("lineIndex" in gutterElement.dataset && "lineIndex" in contentElement.dataset) {
				if (preTrimCount > 0 || hasPostTrim && visibleLineIndex >= postTrimStart) {
					gutterElement.remove();
					contentElement.remove();
					if (preTrimCount > 0) {
						preTrimCount--;
						if (preTrimCount === 0) pendingMetadataTrim = true;
					}
					rowCount++;
				}
				visibleLineIndex++;
				continue;
			}
			if ("separator" in gutterElement.dataset && "separator" in contentElement.dataset) {
				if (preTrimCount > 0 || hasPostTrim && visibleLineIndex >= postTrimStart) {
					gutterElement.remove();
					contentElement.remove();
					rowCount++;
				}
				continue;
			}
			if (gutterElement.dataset.gutterBuffer === "annotation" && "lineAnnotation" in contentElement.dataset) {
				if (preTrimCount > 0 || hasPostTrim && visibleLineIndex >= postTrimStart) {
					gutterElement.remove();
					contentElement.remove();
					rowCount++;
				}
				continue;
			}
			if (gutterElement.dataset.gutterBuffer === "metadata" && "noNewline" in contentElement.dataset) {
				if (preTrimCount > 0 || hasPostTrim && visibleLineIndex >= postTrimStart) {
					gutterElement.remove();
					contentElement.remove();
					rowCount++;
				}
				continue;
			}
			if (gutterElement.dataset.gutterBuffer === "buffer" && "contentBuffer" in contentElement.dataset) {
				const totalRows = this.getBufferSize(contentElement.dataset);
				if (totalRows == null) throw new Error("FileDiff.trimColumnRows: invalid element");
				if (preTrimCount > 0) {
					const rowsToRemove = Math.min(preTrimCount, totalRows);
					const newSize = totalRows - rowsToRemove;
					if (newSize > 0) {
						this.updateBufferSize(gutterElement, newSize);
						this.updateBufferSize(contentElement, newSize);
						rowCount += rowsToRemove;
					} else {
						gutterElement.remove();
						contentElement.remove();
						rowCount += totalRows;
					}
					preTrimCount -= rowsToRemove;
					if (preTrimCount === 0 && newSize === 0) pendingMetadataTrim = true;
				} else if (hasPostTrim) {
					const bufferStart = visibleLineIndex;
					const bufferEnd = visibleLineIndex + totalRows - 1;
					if (postTrimStart <= bufferStart) {
						gutterElement.remove();
						contentElement.remove();
						rowCount += totalRows;
					} else if (postTrimStart <= bufferEnd) {
						const rowsToRemove = bufferEnd - postTrimStart + 1;
						const newSize = totalRows - rowsToRemove;
						this.updateBufferSize(gutterElement, newSize);
						this.updateBufferSize(contentElement, newSize);
						rowCount += rowsToRemove;
					}
				}
				visibleLineIndex += totalRows;
				continue;
			}
			console.error({
				gutterElement,
				contentElement
			});
			throw new Error("FileDiff.trimColumnRows: unknown row elements");
		}
		return rowCount;
	}
	trimColumns({ columns, diffStyle, overlapEnd, overlapStart, previousStart, trimEnd, trimStart }) {
		const preTrimCount = Math.max(0, overlapStart - previousStart);
		const postTrimStart = overlapEnd - previousStart;
		if (postTrimStart < 0) throw new Error("FileDiff.trimColumns: overlap ends before previous");
		const shouldTrimStart = trimStart > 0;
		const shouldTrimEnd = trimEnd > 0;
		if (!shouldTrimStart && !shouldTrimEnd) return 0;
		const effectivePreTrimCount = shouldTrimStart ? preTrimCount : 0;
		const effectivePostTrimStart = shouldTrimEnd ? postTrimStart : -1;
		if (diffStyle === "unified" && !Array.isArray(columns)) return this.trimColumnRows(columns, effectivePreTrimCount, effectivePostTrimStart);
		else if (diffStyle === "split" && Array.isArray(columns)) {
			const deletionsTrim = this.trimColumnRows(columns[0], effectivePreTrimCount, effectivePostTrimStart);
			const additionsTrim = this.trimColumnRows(columns[1], effectivePreTrimCount, effectivePostTrimStart);
			if (columns[0] != null && columns[1] != null && deletionsTrim !== additionsTrim) throw new Error("FileDiff.trimColumns: split columns out of sync");
			return columns[0] != null ? deletionsTrim : additionsTrim;
		} else {
			console.error({
				diffStyle,
				columns
			});
			throw new Error("FileDiff.trimColumns: Invalid columns for diffType");
		}
	}
	getBufferSize(properties) {
		const parsed = Number.parseInt(properties?.bufferSize ?? "", 10);
		return Number.isNaN(parsed) ? void 0 : parsed;
	}
	updateBufferSize(element, size) {
		element.dataset.bufferSize = `${size}`;
		element.style.setProperty("grid-row", `span ${size}`);
		element.style.setProperty("min-height", `calc(${size} * 1lh)`);
	}
	getColumnPair(code) {
		if (code == null) return;
		const gutter = code.children[0];
		const content = code.children[1];
		if (!(gutter instanceof HTMLElement) || !(content instanceof HTMLElement) || gutter.dataset.gutter == null || content.dataset.content == null) return;
		return {
			gutter,
			content
		};
	}
	getCodeColumns(diffStyle, codeUnified, codeDeletions, codeAdditions) {
		if (diffStyle === "unified") return this.getColumnPair(codeUnified);
		else {
			const deletions = this.getColumnPair(codeDeletions);
			const additions = this.getColumnPair(codeAdditions);
			return deletions != null || additions != null ? [deletions, additions] : void 0;
		}
	}
	updateBuffers(renderRange) {
		if (this.pre != null) this.applyBuffers(this.pre, renderRange);
	}
	applyBuffers(pre, renderRange) {
		if (renderRange == null || this.shouldDisableVirtualizationBuffers()) {
			if (this.bufferBefore != null) {
				this.bufferBefore.remove();
				this.bufferBefore = void 0;
			}
			if (this.bufferAfter != null) {
				this.bufferAfter.remove();
				this.bufferAfter = void 0;
			}
			return;
		}
		if (renderRange.bufferBefore > 0) {
			if (this.bufferBefore == null) {
				this.bufferBefore = document.createElement("div");
				this.bufferBefore.dataset.virtualizerBuffer = "before";
				pre.before(this.bufferBefore);
			}
			this.bufferBefore.style.setProperty("height", `${renderRange.bufferBefore}px`);
			this.bufferBefore.style.setProperty("contain", "strict");
		} else if (this.bufferBefore != null) {
			this.bufferBefore.remove();
			this.bufferBefore = void 0;
		}
		if (renderRange.bufferAfter > 0) {
			if (this.bufferAfter == null) {
				this.bufferAfter = document.createElement("div");
				this.bufferAfter.dataset.virtualizerBuffer = "after";
				pre.after(this.bufferAfter);
			}
			this.bufferAfter.style.setProperty("height", `${renderRange.bufferAfter}px`);
			this.bufferAfter.style.setProperty("contain", "strict");
		} else if (this.bufferAfter != null) {
			this.bufferAfter.remove();
			this.bufferAfter = void 0;
		}
	}
	shouldDisableVirtualizationBuffers() {
		return this.options.disableVirtualizationBuffers ?? false;
	}
	applyPreNodeAttributes(pre, { additionsContentAST, deletionsContentAST, totalLines }, customProperties) {
		const { diffIndicators = "bars", disableBackground = false, disableLineNumbers = false, overflow = "scroll", diffStyle = "split" } = this.options;
		const preProperties = {
			type: "diff",
			diffIndicators,
			disableBackground,
			disableLineNumbers,
			overflow,
			split: diffStyle === "unified" ? false : additionsContentAST != null && deletionsContentAST != null,
			totalLines,
			customProperties
		};
		if (arePrePropertiesEqual(preProperties, this.appliedPreAttributes)) return;
		setPreNodeProperties(pre, preProperties);
		this.appliedPreAttributes = preProperties;
	}
	applyErrorToDOM(error, container) {
		this.cleanupErrorWrapper();
		this.pre?.remove();
		this.pre = void 0;
		this.appliedPreAttributes = void 0;
		const shadowRoot = container.shadowRoot ?? container.attachShadow({ mode: "open" });
		this.errorWrapper ??= document.createElement("div");
		this.errorWrapper.dataset.errorWrapper = "";
		this.errorWrapper.textContent = "";
		shadowRoot.appendChild(this.errorWrapper);
		const errorMessage = document.createElement("div");
		errorMessage.dataset.errorMessage = "";
		errorMessage.innerText = error.message;
		this.errorWrapper.appendChild(errorMessage);
		const errorStack = document.createElement("pre");
		errorStack.dataset.errorStack = "";
		errorStack.innerText = error.stack ?? "No Error Stack";
		this.errorWrapper.appendChild(errorStack);
	}
	cleanupErrorWrapper() {
		this.errorWrapper?.remove();
		this.errorWrapper = void 0;
	}
};
function getAdditionFile(fileDiff) {
	return {
		name: fileDiff.name,
		lang: fileDiff.lang,
		contents: fileDiff.additionLines.join("")
	};
}
function areOptionalFilesEqual(fileA, fileB) {
	if (fileA == null || fileB == null) return fileA == null && fileB == null;
	return areFilesEqual(fileA, fileB);
}
function hasDiffContent({ fileDiff, oldFile, newFile }) {
	return fileDiff != null && fileDiff.hunks.length > 0 || oldFile != null || newFile != null;
}
function hasDiffHeaderContent({ fileDiff, oldFile, newFile }) {
	return fileDiff != null || oldFile != null || newFile != null;
}
function shouldRenderCode(pre, hasContent, collapsed = false) {
	return !collapsed && pre == null && hasContent;
}
function shouldRenderHeader(headerElement, hasContent, disableFileHeader = false) {
	return headerElement == null && hasContent && !disableFileHeader;
}
function getElementChildren(node) {
	if (node == null || node.type !== "element") return;
	return node.children ?? [];
}
//#endregion
export { FileDiff };

//# sourceMappingURL=FileDiff.js.map