UNPKG

@pierre/diffs

Version:

3,677 lines 160 kB
import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
import { isGutterUtilityPath } from "../utils/isGutterUtilityPath.js";
import { computeLineOffsets } from "../utils/computeFileOffsets.js";
import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
import { isSafari } from "../utils/platform.js";
import { EditStack } from "./editStack.js";
import { getTextDocumentChangeTransaction } from "./textDocumentChangeTransaction.js";
import { TextDocument } from "./textDocument.js";
import { EditStateManager, cloneEditorViewState, toManagedEditState } from "./EditStateManager.js";
import { isMoveCursorShortcut, isPrimaryModifier } from "./platform.js";
import { resolveEditorCommandFromKeyboardEvent, resolveFindAgainShortcut } from "./command.js";
import { cloneRetainedDiffSessionSnapshot } from "./cloneRetainedDiffSessionSnapshot.js";
import editor_default from "./editor2.js";
import { buildEditPredictionRequest, matchesEditPredictionPattern, recordEditPrediction } from "./editPrediction.js";
import { resolveBlockCommentEdits, resolveCommentConfig, resolveLineCommentEdits } from "./languages.js";
import { addEventListener, clampDomOffset, extend, getLineNumberAttr, h, lookupScrollContainer, round } from "./utils.js";
import { applyDocumentChangeToLineAnnotations, renderLineAnnotations } from "./lineAnnotations.js";
import { PopoverManager } from "./popover.js";
import { applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNextMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionClipboardTexts, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapOffsetThroughEdits, remapSelectionsAfterEdits, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines, snapCharacterToGraphemeBoundary } from "./selection.js";
import { MarkerRenderer, markerSeverityDatasetKey } from "./marker.js";
import { findBracketMatchRanges } from "./matchBrackets.js";
import { createSpriteElement } from "./sprite.js";
import { SearchPanelWidget } from "./searchPanel.js";
import { SelectionActionWidget } from "./selectionAction.js";
import { Metrics, getExpandedAsciiTextColumns, getUnicodeMeasurementOffsets, snapTextOffsetToUnicodeBoundary } from "./textMeasure.js";
import { EditorTokenizer, renderLineTokens } from "./tokenizer.js";
//#region src/editor/editor.ts
function getShadowRootRange(shadowRoot) {
	const selection = shadowRoot.getSelection?.();
	if (selection == null || selection.rangeCount === 0) return;
	const range = selection.getRangeAt(0);
	return {
		collapsed: range.collapsed,
		startContainer: range.startContainer,
		startOffset: range.startOffset,
		endContainer: range.endContainer,
		endOffset: range.endOffset
	};
}
/** Describe replacing the complete document from its previous text. */
function createFullDocumentChange(previousContents, contents) {
	const lineOffsets = computeLineOffsets(previousContents);
	const lastLineOffset = lineOffsets[lineOffsets.length - 1] ?? 0;
	return {
		start: 0,
		end: previousContents.length,
		text: contents,
		range: {
			start: {
				line: 0,
				character: 0
			},
			end: {
				line: lineOffsets.length - 1,
				character: previousContents.length - lastLineOffset
			}
		}
	};
}
function isVirtualizedEditorComponent(instance) {
	return instance != null && "renderType" in instance && instance.renderType === "virtualized";
}
const MAX_EDIT_WIDEN_WINDOW_MULTIPLE = 2;
const MAX_WRAP_OFFSETS_CACHE_LINES = 1e4;
const EDIT_PREDICTION_DEBOUNCE_MS = 300;
const MAX_EDIT_PREDICTION_RESPONSE_EDITS = 256;
const MAX_EDIT_PREDICTION_RESPONSE_BYTES = 128 * 1024;
const editPredictionTextEncoder = new TextEncoder();
const SELECTION_ACTION_POPOVER_PLACEMENT_KEY = "selection-action";
const MULTI_SELECTION_CLIPBOARD_TYPE = "application/vnd.pierre.diffs-selections+json";
var Editor = class {
	type;
	#options;
	#initialState;
	#editSession;
	#metrics = new Metrics();
	#tokenizer;
	#popoverManager;
	#editorEventDisposes;
	#globalEventDisposes;
	#selectEventDisposes;
	#detach;
	#attachState = {
		generation: 0,
		callback: void 0,
		delivered: false
	};
	#contentOffset;
	#gutterWidthCache;
	#contentWidthCache;
	#lineYCache = /* @__PURE__ */ new Map();
	#wrapLineOffsetsCache = /* @__PURE__ */ new Map();
	#lineElementsCache = /* @__PURE__ */ new Map();
	#lastAccessedCharX;
	#globalStyleElement;
	#editorStyleElement;
	#themeStyleElement;
	#spriteElement;
	#fileContainer;
	#gutterElement;
	#contentElement;
	#deletionsColumn;
	#overlayElement;
	#overlayElements;
	#caretElements;
	#caretHighlightElements;
	#primaryCaretElement;
	#resizeObserver;
	#fileInstance;
	#applyDocumentChange;
	#publishChange;
	#isRendering = false;
	#restoreEditorStateOnSync = false;
	#lineAnnotations;
	#editStateKey;
	#ownsVerticalViewport;
	#renderRange;
	#viewportWindowLines;
	#markerRenderer;
	#carets;
	#searchPanel;
	#selectionAction;
	#canMountSelectionAction = true;
	#shouldIgnoreSelectionChange = false;
	#suppressNativeSelectionSync = false;
	#contentHasFocus = false;
	#replacementFocusRequest;
	#isComposing = false;
	#isGutterMouseDown = false;
	#isContentMouseDown = false;
	#altColumnDrag;
	#shiftKeyPressed = false;
	#selectionStart;
	#deletedSelectionText = "";
	#reservedSelections;
	#initSelections;
	#selections;
	#matches;
	#scrollingToLine;
	#scrollingToLineChar;
	#scrollingToLineFixed = false;
	#scrollingToLineNoFocus = false;
	#retainSearchPanelFocus = false;
	#fontRemeasureScheduled = false;
	#themeSelectionRefreshFrame;
	#editPredictionTimer;
	#editPredictionAbortController;
	#editPredictionGeneration = 0;
	#editPredictionRevealed = false;
	#retryEditPredictionOnRender = false;
	#editPrediction;
	#editPredictionHistory = [];
	#editPredictionSpacers = /* @__PURE__ */ new Map();
	#ghostTextRows = /* @__PURE__ */ new Map();
	#onDeferTokenize = (lines, themeType) => {
		if (!this.#isRendering) return;
		this.#fileInstance?.updateRenderCache(lines, themeType);
		if (this.#renderRange !== void 0 && this.#renderRange.totalLines !== Infinity) {
			const predictionLines = this.#editPrediction === void 0 ? void 0 : new Set(this.#editPrediction.response.edits.map((edit) => edit.range.start.line));
			let refreshPrediction = false;
			const { startingLine, totalLines } = this.#renderRange;
			const endLine = Math.min(startingLine + totalLines, (this.#editSession ?? this.#initialState)?.document?.lineCount ?? 0);
			for (const [line, tokens] of lines) if (line >= startingLine && line < endLine) {
				const lineElement = this.#getLineElement(line);
				if (lineElement !== void 0) {
					lineElement.replaceChildren(...renderLineTokens(tokens));
					refreshPrediction ||= predictionLines?.has(line) === true;
				}
			}
			if (refreshPrediction && this.#selections !== void 0) this.#updateSelections(this.#selections);
		}
	};
	/**
	* @param type The component surface this editor can attach to.
	* @param options Configure editor behavior and lifecycle callbacks.
	* @param editStateKey Retain this editable draft and its undo/redo history
	* in memory so a later editor using the same type and key can resume them.
	*/
	constructor(type, options = {}, editStateKey) {
		this.type = type;
		this.#options = options;
		this.#ownsVerticalViewport = options.ownsVerticalViewport === true;
		this.#initialState = options.initialState;
		this.#editStateKey = editStateKey;
	}
	get #getTypedEditor() {
		return this.type === "file" ? this : this;
	}
	setOptions(options) {
		const previousRenderCaret = this.#options.renderCaret;
		const previousEditPrediction = this.#options.editPrediction;
		this.#options = {
			...this.#options,
			...options
		};
		if (previousRenderCaret !== this.#options.renderCaret) {
			this.#caretElements?.forEach((element) => element.remove());
			this.#caretElements = void 0;
			this.#renderCarets();
		}
		if (previousEditPrediction !== this.#options.editPrediction) {
			this.#cancelEditPrediction(true);
			this.#editPredictionHistory = [];
			this.#scheduleEditPrediction();
		}
	}
	__emitEditComplete(event) {
		this.#options.onComplete?.(event);
	}
	/** @internal */
	__getGhostTextRows() {
		return this.#ghostTextRows;
	}
	setCarets(carets) {
		const textDocument = this.#editSession?.document;
		if (textDocument === void 0) return;
		this.#caretElements?.forEach((element) => element.remove());
		this.#caretElements = void 0;
		this.#caretHighlightElements?.forEach((element) => element.remove());
		this.#caretHighlightElements = void 0;
		this.#carets = carets.length === 0 ? void 0 : carets.map((caret) => {
			const anchor = textDocument.normalizePosition(caret.anchor);
			const focus = textDocument.normalizePosition(caret.focus);
			return {
				caret: {
					...caret,
					anchor,
					focus
				},
				anchorOffset: textDocument.offsetAt(anchor),
				focusOffset: textDocument.offsetAt(focus)
			};
		});
		this.#renderCarets();
	}
	edit(fileInstance) {
		const editor = this.#getTypedEditor;
		const previousSession = this.#editSession;
		if (this.#isRendering) throw new Error("Editor.edit: the editor is already rendering its component");
		if (this.#fileInstance != null && this.#fileInstance !== fileInstance) throw new Error("Editor.edit: a recycled edit session cannot attach to a different component");
		const initialState = this.#initialState;
		const initialEditorState = initialState?.editor;
		const editStateKey = this.#editStateKey;
		const editSession = getEditSession({
			type: this.type,
			editStateKey,
			owner: this,
			initialState,
			previousSession
		});
		const previousDiffSession = editSession.diffSession;
		this.#initialState = void 0;
		this.#editSession = editSession;
		this.#fileInstance = fileInstance;
		this.#isRendering = true;
		this.#restoreEditorStateOnSync = editSession.editor != null;
		try {
			this.#initialize();
			if (fileInstance.type === "file" && editor.type === "file") {
				editor.#applyDocumentChange = fileInstance.applyDocumentChange.bind(fileInstance);
				editor.#publishChange = (changes, file, lineAnnotations) => {
					const event = {
						changes,
						file,
						editor,
						lineAnnotations
					};
					fileInstance.__acceptEditorChange(event);
					editor.#options.onChange?.(event);
					fileInstance.emitEditChange(event);
				};
				if (this.#detach == null) this.#detach = fileInstance.__attachEditor(editor);
				else fileInstance.__resumeEditor(editor);
			} else if (fileInstance.type === "file-diff" && editor.type === "file-diff") {
				editor.#applyDocumentChange = fileInstance.applyDocumentChange.bind(fileInstance);
				editor.#publishChange = (changes, file, lineAnnotations) => {
					const event = {
						changes,
						file,
						editor,
						lineAnnotations
					};
					fileInstance.__acceptEditorChange(event);
					editor.#options.onChange?.(event);
					fileInstance.emitEditChange(event);
				};
				if (this.#detach == null) this.#detach = fileInstance.__attachEditor(editor);
				else fileInstance.__resumeEditor(editor);
			} else throw new Error("Editor.edit: Impossible edit state");
		} catch (error) {
			this.cleanUp("recycle");
			if (editSession.type === "file-diff" && editSession.diffSession == null) editSession.diffSession = previousDiffSession;
			if (this.#detach == null && editStateKey != null) if (editor.type === "file") EditStateManager.releaseFile(editStateKey, editor);
			else EditStateManager.releaseFileDiff(editStateKey, editor);
			this.#editSession = previousSession;
			if (this.#detach == null) {
				this.#fileInstance = void 0;
				this.#applyDocumentChange = void 0;
				this.#publishChange = void 0;
			}
			if (initialState != null && initialEditorState != null) initialState.editor = initialEditorState;
			this.#initialState = initialState;
			throw error;
		}
		return () => {
			this.cleanUp("complete");
		};
	}
	/**
	* Apply edits to current attached file. Every edit joins the undo timeline:
	* a programmatic edit must leave the document and its history exactly as
	* the same edit typed by the user would (history equivalence — see
	* TextDocument.applyResolvedEdits), so it is undoable like any other edit.
	*
	* @param updateHistory Whether to record caller selection snapshots for
	* exact undo/redo restoration. Defaults to true. When false, live selections
	* are remapped during replay and the text edit still joins the undo timeline.
	*/
	applyEdits(edits, updateHistory = true) {
		const textDocument = this.#editSession?.document;
		if (textDocument == null) throw new Error("Editor.applyEdits: Editor is not attached");
		const wasFocused = this.#contentHasFocus;
		const selectionsBefore = this.#selections;
		const selectionOffsetsBefore = selectionsBefore?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
		const resolvedEditOffsets = selectionsBefore === void 0 ? void 0 : textDocument.resolveEdits(edits).sort((a, b) => a.start - b.start);
		const change = textDocument.applyEdits(edits, updateHistory, selectionsBefore);
		if (change === void 0) return;
		let nextSelections;
		if (selectionsBefore !== void 0 && selectionOffsetsBefore !== void 0 && resolvedEditOffsets !== void 0) {
			nextSelections = remapSelectionsAfterEdits(textDocument, selectionsBefore, selectionOffsetsBefore, resolvedEditOffsets);
			if (updateHistory) textDocument.setLastUndoSelectionsAfter(nextSelections);
		}
		this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change), { skipFocus: !wasFocused });
	}
	/** Whether there is an edit to undo. */
	get canUndo() {
		return (this.#editSession ?? this.#initialState)?.document?.canUndo ?? false;
	}
	/** Whether there is an undone edit to redo. */
	get canRedo() {
		return (this.#editSession ?? this.#initialState)?.document?.canRedo ?? false;
	}
	/** Undo the last edit. Does nothing when there is nothing to undo. */
	undo() {
		this.#runCommand("undo");
	}
	/** Redo the last undone edit. Does nothing when there is nothing to redo. */
	redo() {
		this.#runCommand("redo");
	}
	getFile() {
		const editSession = this.#editSession ?? this.#initialState;
		const fileInfo = editSession?.fileInfo;
		const textDocument = editSession?.document;
		if (fileInfo === void 0 || textDocument === void 0) return;
		const file = { ...fileInfo };
		Object.defineProperty(file, "contents", {
			enumerable: true,
			get: () => textDocument.getText()
		});
		return file;
	}
	getText() {
		return (this.#editSession ?? this.#initialState)?.document?.getText() ?? "";
	}
	/** Return an isolated copy of selections and restorable view state. */
	getViewState() {
		const fileInstance = this.#isRendering ? this.#fileInstance : void 0;
		if (!this.#isRendering && this.#editSession?.editor != null) return cloneEditorViewState(this.#editSession.editor);
		const selections = this.#selections?.map((selection) => ({
			...selection,
			start: { ...selection.start },
			end: { ...selection.end }
		}));
		if (fileInstance == null) return { selections };
		const viewport = this.#getOwnedVerticalViewport();
		const scrollLeft = fileInstance.getCodeScrollLeft();
		if (viewport == null) return {
			selections,
			view: { scrollLeft }
		};
		return {
			selections,
			view: {
				scrollLeft,
				scrollTop: viewport.scrollTop
			}
		};
	}
	/**
	* Return the objects that make up the active edit session, or undefined when
	* no complete session exists.
	*/
	getEditState() {
		const editSession = this.#editSession;
		if (editSession == null) return;
		if (this.#fileInstance == null) throw new Error(`Editor.getEditState: active ${this.type} session has no component`);
		return toManagedEditState(editSession);
	}
	setViewState({ selections, view }) {
		if (!this.#isRendering || this.#fileInstance === void 0 || this.#editSession?.document === void 0) throw new Error("Editor.setViewState: Editor is not attached");
		this.#canMountSelectionAction = false;
		this.#updateSelections(selections ?? []);
		if (view != null) {
			this.#fileInstance.setCodeScrollLeft(view.scrollLeft);
			const viewport = this.#getOwnedVerticalViewport();
			if (view.scrollTop !== void 0 && viewport != null) viewport.scrollTop = view.scrollTop;
		} else if (this.#ownsVerticalViewport) this.#scrollToPrimaryCaret();
		this.#checkpointEditSessionState();
	}
	setSelections(selections) {
		const textDocument = this.#editSession?.document;
		if (textDocument === void 0) throw new Error("Editor.setSelections: Text document is not initialized");
		const resolvedSelections = selections.map((selection) => {
			let start = textDocument.normalizePosition(selection.start);
			let end = textDocument.normalizePosition(selection.end);
			let direction = selection.direction === "none" ? 0 : selection.direction === "backward" ? -1 : 1;
			if (comparePosition(start, end) > 0) {
				[start, end] = [end, start];
				if (direction !== 0) direction = direction === 1 ? -1 : 1;
			}
			return {
				direction,
				start,
				end
			};
		});
		this.#canMountSelectionAction = false;
		this.#updateSelections(resolvedSelections);
		const primarySelection = resolvedSelections.at(-1);
		if (primarySelection !== void 0) this.#revealLineIfCollapsed(getCaretPosition(primarySelection).line);
		this.#scrollToPrimaryCaret();
	}
	setMarkers(markers) {
		const textDocument = this.#editSession?.document;
		if (textDocument === void 0) throw new Error("Editor.setMarkers: Text document is not initialized");
		if (markers.length === 0) {
			this.#markerRenderer?.cleanup();
			this.#markerRenderer = void 0;
			this.#updateSelections(this.#selections ?? []);
			return;
		}
		this.#markerRenderer ??= new MarkerRenderer({
			popoverManager: this.#getPopoverManager(),
			getLineHeight: () => this.#metrics.lineHeight,
			getOverlayElement: () => this.#overlayElement,
			getGutterWidth: () => this.#getGutterWidth(),
			getCharX: (line, character) => this.#getCharX(line, character),
			getLineY: (line) => this.#getLineY(line),
			isMouseDown: () => this.#isContentMouseDown || this.#isGutterMouseDown
		});
		this.#markerRenderer.setMarkers(markers, textDocument);
		if (this.#contentElement !== void 0) this.#markerRenderer.listenHover(this.#contentElement);
		this.#updateSelections(this.#selections ?? []);
		this.#renderCarets();
	}
	focus(options) {
		const preventScroll = options?.preventScroll ?? false;
		const lineNumber = options?.lineNumber;
		if (lineNumber === "first-visible" || typeof lineNumber === "number") {
			const textDocument = this.#editSession?.document;
			if (textDocument == null || !this.#isRendering || this.#fileInstance == null) return;
			const targetLineNumber = lineNumber === "first-visible" ? this.#getFirstVisibleLineNumber(options?.offset) : lineNumber;
			if (targetLineNumber == null) return;
			const position = textDocument.normalizePosition({
				line: targetLineNumber - 1,
				character: lineNumber === "first-visible" ? 0 : options?.character ?? 0
			});
			this.#focusAtPosition(position, preventScroll);
			return;
		}
		const primarySelection = this.#selections?.at(-1);
		if (primarySelection !== void 0) {
			const pos = primarySelection.direction === -1 ? primarySelection.end : primarySelection.start;
			this.#focus(pos, preventScroll);
		} else this.#focus(void 0, preventScroll);
	}
	blur() {
		this.#replacementFocusRequest = void 0;
		this.#contentHasFocus = false;
		this.#shouldIgnoreSelectionChange = false;
		this.#contentElement?.blur();
	}
	cleanUp(reason = "discard") {
		const fileInstance = this.#fileInstance;
		const recycle = reason === "recycle";
		this.#cancelEditPrediction(true, false);
		if (!recycle) this.#editPredictionHistory = [];
		const editSession = this.#editSession;
		if (fileInstance != null && editSession != null) {
			const discardDiffState = !this.#checkpointEditSessionState();
			if (!recycle) this.#releaseEditSession(discardDiffState);
		}
		this.#invalidateOnAttach();
		if (!recycle) this.#attachState.delivered = false;
		this.#isRendering = false;
		dequeueRender(this.#handleCustomPasteEvent);
		this.#tokenizer?.cleanUp();
		this.#tokenizer = void 0;
		this.#globalEventDisposes?.forEach((dispose) => dispose());
		this.#globalEventDisposes = void 0;
		this.#editorEventDisposes?.forEach((dispose) => dispose());
		this.#editorEventDisposes = void 0;
		if (!recycle) {
			this.#detach?.();
			this.#detach = void 0;
		}
		this.#gutterWidthCache = void 0;
		this.#contentWidthCache = void 0;
		this.#lineYCache.clear();
		this.#wrapLineOffsetsCache.clear();
		this.#lineElementsCache.clear();
		this.#lastAccessedCharX = void 0;
		this.#globalStyleElement?.remove();
		this.#globalStyleElement = void 0;
		this.#editorStyleElement?.remove();
		this.#editorStyleElement = void 0;
		this.#themeStyleElement?.remove();
		this.#themeStyleElement = void 0;
		this.#spriteElement?.remove();
		this.#spriteElement = void 0;
		this.#fileContainer = void 0;
		this.#popoverManager?.cleanUp();
		this.#popoverManager = void 0;
		this.#gutterElement = void 0;
		this.#deletionsColumn = void 0;
		this.#contentElement?.removeAttribute("contentEditable");
		this.#contentElement = void 0;
		this.#replacementFocusRequest = void 0;
		this.#contentHasFocus = false;
		this.#overlayElement?.remove();
		this.#overlayElement = void 0;
		this.#resizeObserver?.disconnect();
		this.#resizeObserver = void 0;
		this.#fontRemeasureScheduled = false;
		if (this.#themeSelectionRefreshFrame !== void 0) {
			cancelAnimationFrame(this.#themeSelectionRefreshFrame);
			this.#themeSelectionRefreshFrame = void 0;
		}
		this.#resetState(recycle);
		if (!recycle) try {
			if (fileInstance != null && editSession != null) {
				const editor = this.#getTypedEditor;
				const mode = reason === "complete" ? "install" : "discard";
				if (fileInstance.type === "file" && editor.type === "file") fileInstance.__completeEditSession(editor, mode);
				else if (fileInstance.type === "file-diff" && editor.type === "file-diff") fileInstance.__completeEditSession(editor, mode);
				else throw new Error("Editor.cleanUp: Impossible edit state");
			}
		} finally {
			this.#editSession = void 0;
			this.#fileInstance = void 0;
			this.#applyDocumentChange = void 0;
			this.#publishChange = void 0;
		}
	}
	#checkpointEditSessionState() {
		const fileInstance = this.#fileInstance;
		const editSession = this.#editSession;
		if (fileInstance == null || editSession == null) return false;
		const editorState = this.#isRendering ? this.getViewState() : editSession.editor ?? {};
		const { view } = editorState;
		const hasNonDefaultView = view != null && (view.scrollLeft !== 0 || (view.scrollTop ?? 0) !== 0);
		const hasEditorState = editorState.selections != null || hasNonDefaultView;
		editSession.editor = cloneEditorViewState(editorState);
		if (editSession.type === "file") return false;
		const capturedDiffSession = fileInstance.__captureDocumentSessionState();
		editSession.diffSession = capturedDiffSession?.diffSession;
		if (capturedDiffSession == null) return false;
		const document = editSession.document;
		return capturedDiffSession.hasChanges || hasEditorState || document?.canUndo === true || document?.canRedo === true;
	}
	#releaseEditSession(discardDiffState = false) {
		const editStateKey = this.#editStateKey;
		if (editStateKey == null) return;
		const editor = this.#getTypedEditor;
		if (editor.type === "file") EditStateManager.releaseFile(editStateKey, editor);
		else EditStateManager.releaseFileDiff(editStateKey, editor, discardDiffState);
	}
	/** @internal */
	__postponeBgTokenizeToNextFrame() {
		const tokenizer = this.#tokenizer;
		if (tokenizer !== void 0) {
			tokenizer.pauseBackgroundTokenize();
			requestAnimationFrame(() => {
				tokenizer.resumeBackgroundTokenize();
			});
		}
	}
	/** @internal */
	__captureFocusForDOMReplacement() {
		const contentElement = this.#contentElement;
		const shadowActiveElement = this.#fileContainer?.shadowRoot?.activeElement;
		if (contentElement != null && (this.#contentHasFocus || shadowActiveElement === contentElement || shadowActiveElement != null && contentElement.contains(shadowActiveElement))) this.#replacementFocusRequest = {};
	}
	/** @internal */
	__getDocumentContents(fallbackFile) {
		const fileInfo = this.#editSession?.fileInfo;
		const textDocument = this.#editSession?.document;
		if (textDocument == null) return fileInfo == null || fallbackFile == null ? void 0 : {
			...fileInfo,
			contents: fallbackFile.contents
		};
		const resolvedFileInfo = fileInfo ?? fallbackFile;
		if (resolvedFileInfo == null) return;
		return {
			name: resolvedFileInfo.name,
			lang: resolvedFileInfo.lang,
			contents: textDocument.getText()
		};
	}
	/** @internal */
	__getDocumentSessionState() {
		const snapshot = this.#editSession?.diffSession;
		return snapshot != null ? cloneRetainedDiffSessionSnapshot(snapshot) : void 0;
	}
	/** @internal */
	__syncRenderView(props) {
		const { highlighter, fileContainer, renderRange, externalDocument = false } = props;
		const renderView = props;
		let fileOrDiff;
		if ("file" in renderView && this.type === "file") fileOrDiff = renderView.file;
		else if ("fileDiff" in renderView && this.type === "file-diff") fileOrDiff = renderView.fileDiff;
		else throw new Error("Editor.__syncRenderView: Impossible render state");
		const lineAnnotations = renderView.lineAnnotations;
		const editStateKey = this.#editStateKey;
		const fileInstance = this.#fileInstance;
		const editSession = this.#editSession;
		if (!this.#isRendering || fileInstance == null || editSession == null) return;
		const shadowRoot = fileContainer.shadowRoot;
		if (shadowRoot == null) {
			console.error("[editor] Could not find the shadow root.");
			return;
		}
		let codeElement;
		let gutterEl;
		let contentEl;
		let deletionsGutterEl;
		let deletionsContentEl;
		for (const el of shadowRoot.querySelectorAll("[data-code]")) {
			const isDeletions = el.dataset.deletions != null;
			if (!isDeletions) codeElement = el;
			for (const child of el.children) {
				if (!(child instanceof HTMLElement)) continue;
				const { gutter, content } = child.dataset;
				if (gutter != null) if (isDeletions) deletionsGutterEl = child;
				else gutterEl = child;
				else if (content != null) if (isDeletions) deletionsContentEl = child;
				else contentEl = child;
			}
		}
		if (deletionsContentEl == null) this.#deletionsColumn = void 0;
		else if (deletionsGutterEl == null) throw new Error("Editor.__syncRenderView: the deletions column has no gutter");
		else this.#deletionsColumn = {
			content: deletionsContentEl,
			gutter: deletionsGutterEl
		};
		if (codeElement === void 0 || contentEl === void 0) {
			this.#replacementFocusRequest = void 0;
			return;
		}
		this.#getPopoverManager().setViewportElements(fileContainer, codeElement);
		if (this.#fileContainer !== fileContainer) {
			this.#fileContainer = fileContainer;
			if (this.#globalStyleElement !== void 0) fileContainer.appendChild(this.#globalStyleElement);
			if (this.#editorStyleElement !== void 0) shadowRoot.appendChild(this.#editorStyleElement);
			if (this.#themeStyleElement !== void 0) shadowRoot.appendChild(this.#themeStyleElement);
			if (this.#spriteElement !== void 0) shadowRoot.prepend(this.#spriteElement);
		}
		const languageId = fileOrDiff.lang ?? getFiletypeFromFileName(fileOrDiff.name);
		const contents = "contents" in fileOrDiff ? fileOrDiff.contents : fileOrDiff.additionLines.join("");
		const previousTextDocument = editSession.document;
		const resetHistory = props.resetHistory ?? (previousTextDocument != null && editSession.fileInfo != null && (editSession.fileInfo.name !== fileOrDiff.name || previousTextDocument.languageId !== languageId));
		const documentChanges = externalDocument && previousTextDocument !== void 0 && previousTextDocument.getText() !== contents ? [createFullDocumentChange(previousTextDocument.getText(), contents)] : void 0;
		const resetForExternalDocument = externalDocument && resetHistory;
		if (editSession.document == null || editSession.fileInfo == null || editStateKey == null && (editSession.fileInfo.name !== fileOrDiff.name || editSession.document.languageId !== languageId) || resetForExternalDocument) {
			this.#cancelEditPrediction(true);
			this.#editPredictionHistory = [];
			this.#invalidateOnAttach();
			const { name, lang } = fileOrDiff;
			let textDocument = resetForExternalDocument || editStateKey == null && editSession.fileInfo != null ? void 0 : editSession.document;
			if (previousTextDocument != null && textDocument == null) editSession.editor = void 0;
			if (textDocument == null) {
				const editStack = new EditStack({ maxEntries: this.#options.historyMaxEntries });
				textDocument = new TextDocument(fileOrDiff.name, contents, languageId, 0, editStack);
			}
			editSession.fileInfo = {
				name,
				lang
			};
			editSession.document = textDocument;
			if (resetForExternalDocument) editSession.diffSession = void 0;
			this.#tokenizer?.cleanUp();
			this.#tokenizer = void 0;
			this.#resetState();
			this.#wrapLineOffsetsCache.clear();
			this.#selections = this.#initSelections;
			if (this.#options.__debug === true) console.log("[diffs/editor] text document initialized from", fileOrDiff.name);
		}
		if (externalDocument && !resetForExternalDocument) {
			this.#applyExternalDocumentReplacement(editSession, contents, lineAnnotations);
			const { name, lang } = fileOrDiff;
			editSession.fileInfo = {
				name,
				lang
			};
		}
		const textDocument = editSession.document;
		if (this.#tokenizer == null && textDocument != null) this.#tokenizer = new EditorTokenizer({
			highlighter,
			textDocument,
			codeOptions: this.#fileInstance?.__getEffectiveCodeOptions() ?? {},
			matchBrackets: this.#options.matchBrackets,
			onDeferTokenize: this.#onDeferTokenize,
			onThemeChange: () => this.#scheduleThemeSelectionRefresh(),
			setStyle: (css) => {
				this.#themeStyleElement.textContent = css;
			},
			__debug: this.#options.__debug
		});
		if (this.#contentElement !== contentEl) {
			this.#gutterElement = gutterEl;
			this.#contentElement = extend(contentEl, {
				contentEditable: "true",
				role: "textbox",
				ariaMultiLine: "true",
				autocapitalize: "off",
				writingSuggestions: "off",
				autocorrect: false,
				spellcheck: false,
				translate: false
			});
			if (this.#overlayElement !== void 0) contentEl.after(this.#overlayElement);
			this.#metrics.init(contentEl);
			this.#remeasureMetricsOnFontLoad();
			this.#listenContentElement(contentEl, gutterEl);
			if (this.#contentElement !== void 0 && this.#options.__debug === true) console.log("[diffs/editor] full re-render triggered !!!");
		}
		if (this.#replacementFocusRequest != null) this.#replacementFocusRequest.target = contentEl;
		if (contentEl.ariaLabel !== fileOrDiff.name) contentEl.ariaLabel = fileOrDiff.name;
		if (lineAnnotations !== void 0 && lineAnnotations.length > 0 || this.#isDiff) for (const child of this.#contentElement.children) {
			const el = child;
			const { lineAnnotation, lineType, separator } = el.dataset;
			if (lineAnnotation !== void 0 || separator !== void 0 || lineType === "change-deletion") el.setAttribute("contenteditable", "false");
		}
		this.#resetCache();
		this.#tokenizer?.syncTheme(this.#fileInstance?.__getEffectiveCodeOptions() ?? {});
		this.#lineAnnotations = lineAnnotations;
		this.#renderRange = renderRange;
		this.#viewportWindowLines = renderRange?.totalLines;
		this.#tokenizer?.prebuildStateStack(renderRange);
		const retainedEditorState = editSession.editor;
		const restoreEditorStateOnSync = this.#restoreEditorStateOnSync;
		try {
			this.#markerRenderer?.removePopover();
			if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
			this.#renderCarets();
			if (this.#retryEditPredictionOnRender) this.#scheduleEditPrediction();
			if (this.#initSelections !== void 0 && this.#primaryCaretElement !== void 0) {
				this.#initSelections = void 0;
				this.#scrollToPrimaryCaret(false, "center");
			} else if (this.#scrollingToLine !== void 0) this.#scrollToLine(this.#scrollingToLine, this.#scrollingToLineChar, this.#scrollingToLineNoFocus);
			else if (this.#replacementFocusRequest !== void 0) this.#restoreReplacementFocus();
			else if (this.#selections !== void 0 && this.#selections.length > 0 && this.#contentHasFocus && !this.#retainSearchPanelFocus) this.focus({ preventScroll: true });
			if (this.#retainSearchPanelFocus) this.#searchPanel?.focus();
			if (restoreEditorStateOnSync) {
				this.#restoreEditorStateOnSync = false;
				if (retainedEditorState !== void 0) this.setViewState(retainedEditorState);
			}
			if (this.#options.__debug === true && renderRange !== void 0) {
				const { startingLine, totalLines } = renderRange;
				console.log("[diffs/editor] render file:", fileOrDiff.name, "RenderRange:", startingLine + "-" + (startingLine + totalLines), "of", editSession.document?.lineCount, "lines");
			}
			if (externalDocument) fileInstance.__acknowledgeDocumentUpdate();
			this.#checkpointEditSessionState();
		} catch (error) {
			this.#restoreEditorStateOnSync = restoreEditorStateOnSync;
			editSession.editor = retainedEditorState;
			throw error;
		}
		if (documentChanges != null) this.#emitChange(documentChanges, lineAnnotations);
		this.#scheduleOnAttach(fileInstance);
	}
	#applyExternalDocumentReplacement(editSession, contents, lineAnnotations) {
		const textDocument = editSession.document;
		if (textDocument == null || textDocument.getText() === contents) return;
		const selections = this.#selections;
		const selectionOffsets = selections?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
		const replacement = {
			start: 0,
			end: textDocument.getText().length,
			text: contents
		};
		if (textDocument.applyResolvedEdits([replacement], true, selections, void 0, true) == null) return;
		if (selections != null && selectionOffsets != null) {
			const nextSelections = remapSelectionsAfterEdits(textDocument, selections, selectionOffsets, [replacement]);
			textDocument.setLastUndoSelectionsAfter(nextSelections);
			this.#selections = nextSelections;
		}
		if (this.#lineAnnotations != null || lineAnnotations != null) textDocument.setLastUndoLineAnnotations(this.#lineAnnotations ?? [], lineAnnotations ?? []);
		this.#tokenizer?.cleanUp();
		this.#tokenizer = void 0;
		this.#resetCache();
		this.#wrapLineOffsetsCache.clear();
		this.#markerRenderer?.removePopover();
		if (this.#searchPanel !== void 0 && this.#matches !== void 0) this.#searchPanel.updateMatches({ syncSelection: false });
	}
	get #fileDiffInstance() {
		return this.#fileInstance?.type === "file-diff" ? this.#fileInstance : void 0;
	}
	get #virtualizedInstance() {
		return isVirtualizedEditorComponent(this.#fileInstance) ? this.#fileInstance : void 0;
	}
	#isLineRenderable(line) {
		return this.#fileDiffInstance?.isLineRenderable?.(line + 1) ?? true;
	}
	get #resolveRenderableLine() {
		const fileInstance = this.#fileDiffInstance;
		if (fileInstance == null) return;
		return (line, direction) => {
			const nearest = fileInstance.getNearestRenderableLine(line + 1, direction);
			return nearest == null ? void 0 : nearest - 1;
		};
	}
	#revealLineIfCollapsed(line) {
		if (!this.#isLineRenderable(line)) this.#fileDiffInstance?.revealLine?.(line + 1);
	}
	get #diffSyle() {
		return this.#fileDiffInstance?.options.diffStyle ?? "split";
	}
	get #isDiff() {
		return this.type === "file-diff";
	}
	get #isWrap() {
		return this.#fileInstance?.options.overflow === "wrap";
	}
	#getPopoverManager() {
		return this.#popoverManager ??= new PopoverManager({
			hasActivePopover: () => this.#selectionAction !== void 0 || this.#markerRenderer?.isPopoverVisible() === true,
			updateActivePopover: () => {
				this.#updateSelectionActionPopover();
				this.#markerRenderer?.updatePopoverPosition();
			}
		});
	}
	#resetCache() {
		this.#lineYCache.clear();
		this.#lineElementsCache.clear();
		this.#lastAccessedCharX = void 0;
	}
	#resetState(preserveCarets = false) {
		this.#setEditorActiveLineSafe(null);
		this.#gutterWidthCache = void 0;
		this.#contentWidthCache = void 0;
		this.#resetSelectionGesture();
		this.#suppressNativeSelectionSync = false;
		this.#overlayElements?.forEach((el) => el.remove());
		this.#overlayElements = void 0;
		this.#caretElements?.forEach((element) => element.remove());
		this.#caretElements = void 0;
		this.#caretHighlightElements?.forEach((element) => element.remove());
		this.#caretHighlightElements = void 0;
		if (!preserveCarets) this.#carets = void 0;
		this.#selections = void 0;
		this.#scrollingToLine = void 0;
		this.#markerRenderer?.cleanup();
		this.#markerRenderer = void 0;
		this.#searchPanel?.cleanup();
		this.#searchPanel = void 0;
		this.#selectionAction?.cleanup();
		this.#selectionAction = void 0;
		this.#canMountSelectionAction = true;
	}
	#invalidateOnAttach() {
		const attachState = this.#attachState;
		attachState.generation++;
		if (attachState.callback != null) {
			dequeueRender(attachState.callback);
			attachState.callback = void 0;
		}
	}
	#scheduleOnAttach(fileInstance) {
		const attachState = this.#attachState;
		const textDocument = this.#editSession?.document;
		if (attachState.delivered || attachState.callback != null || textDocument == null) return;
		const { generation } = attachState;
		const callback = () => {
			if (attachState.callback !== callback) return;
			attachState.callback = void 0;
			if (generation !== attachState.generation || this.#fileInstance !== fileInstance || this.#editSession?.document !== textDocument) return;
			const contentElement = this.#contentElement;
			const shadowRoot = this.#fileContainer?.shadowRoot;
			if (contentElement == null || shadowRoot == null || !shadowRoot.contains(contentElement)) return;
			attachState.delivered = true;
			this.#options.onAttach?.(this, fileInstance);
		};
		attachState.callback = callback;
		queueRender(callback);
	}
	#getScrollViewport() {
		const viewport = this.#virtualizedInstance?.getEditorViewport?.();
		if (viewport != null) return viewport;
		const fileContainer = this.#fileContainer;
		if (this.#fileInstance == null || fileContainer == null) return;
		return lookupScrollContainer(fileContainer);
	}
	#getOwnedVerticalViewport() {
		if (!this.#ownsVerticalViewport) return;
		const viewport = this.#virtualizedInstance?.getEditorViewport?.();
		if (viewport instanceof HTMLElement) return viewport;
	}
	#initialize() {
		this.#globalStyleElement = h("style", {
			dataset: "editorGlobalCss",
			textContent: `
        [data-annotation-slot] {
          user-select: none;
          -webkit-user-select: none;
        }
      `
		});
		this.#editorStyleElement = h("style", {
			dataset: "editorCss",
			textContent: editor_default
		});
		this.#themeStyleElement = h("style", { dataset: "editorThemeCss" });
		this.#spriteElement = createSpriteElement();
		this.#overlayElement = h("div", { dataset: "editorOverlay" });
		const replacementFocusTargetIsContent = (event) => {
			const target = event.composedPath()[0];
			return target instanceof Node && this.#contentElement?.contains(target) === true;
		};
		const cancelReplacementFocus = () => {
			if (this.#replacementFocusRequest == null) return;
			this.#replacementFocusRequest = void 0;
			this.#contentHasFocus = false;
			this.#shouldIgnoreSelectionChange = false;
		};
		const finishMouseSelection = (event) => {
			if (event.pointerType !== "mouse") return;
			const refocusEditor = this.#isGutterMouseDown;
			this.#resetSelectionGesture();
			if (refocusEditor) this.#focus();
			if (this.#options.enabledSelectionAction === true && this.#selections !== void 0 && this.#selections.length > 0 && !isCollapsedSelection(this.#selections.at(-1))) this.#updateSelections(this.#selections);
		};
		this.#globalEventDisposes = [
			addEventListener(document, "focusin", (event) => {
				if (!replacementFocusTargetIsContent(event)) cancelReplacementFocus();
			}, { passive: true }),
			addEventListener(document, "pointerdown", (event) => {
				if (!replacementFocusTargetIsContent(event)) queueMicrotask(cancelReplacementFocus);
			}, {
				capture: true,
				passive: true
			}),
			addEventListener(document, "selectionchange", () => {
				const shadowRoot = this.#fileContainer?.shadowRoot;
				if (this.#shouldIgnoreSelectionChange || this.#suppressNativeSelectionSync || shadowRoot == null || !this.#contentHasFocus) return;
				if (this.#selections !== void 0 && this.#selections.length > 1 && !this.#isContentMouseDown) return;
				const selectionRaw = document.getSelection();
				if (selectionRaw == null) return;
				let composedRange;
				if (typeof selectionRaw.getComposedRanges === "function") composedRange = selectionRaw.getComposedRanges({ shadowRoots: [shadowRoot] })?.[0];
				else composedRange = getShadowRootRange(shadowRoot);
				if (composedRange === void 0 || !this.#rangeBelongsToEditor(composedRange)) return;
				let selection = convertSelection(composedRange, 0);
				if (selection === void 0) return;
				if (this.#isContentMouseDown && this.#shiftKeyPressed && this.#selections !== void 0 && this.#selections.length > 0) {
					const primarySelection = this.#selections.at(-1);
					this.#updateSelections([extendSelection(primarySelection, selection)]);
					return;
				}
				if (this.#isContentMouseDown) if (this.#selectionStart !== void 0) selection = createSelectionFrom(this.#selectionStart, selection);
				else this.#selectionStart = selection;
				else if (this.#selectionStart !== void 0) selection.direction = createSelectionFrom(this.#selectionStart, selection).direction;
				else if (this.#selections !== void 0 && this.#selections.length === 1) {
					const previous = this.#selections[0];
					if (comparePosition(previous.start, selection.start) === 0 && comparePosition(previous.end, selection.end) === 0) selection.direction = previous.direction;
				}
				if (this.#altColumnDrag !== void 0) {
					this.#altColumnDrag.focusLine = getCaretPosition(selection).line;
					if (this.#updateAltColumnSelections()) return;
				}
				if (this.#reservedSelections !== void 0) this.#updateSelections([...this.#reservedSelections.filter((reservedSelection) => !selectionIntersects(reservedSelection, selection)), selection]);
				else this.#updateSelections([selection]);
			}, { passive: true }),
			addEventListener(document, "pointerup", finishMouseSelection, { passive: true }),
			addEventListener(document, "pointercancel", finishMouseSelection, { passive: true }),
			addEventListener(document, "keydown", (e) => {
				if (e.key === "Shift") this.#selectionStart = this.#selections?.at(-1);
				else if (e.key === "Alt" && !e.repeat && this.#contentHasFocus && this.#options.editPrediction?.mode === "subtle") {
					this.#editPredictionRevealed = !this.#editPredictionRevealed;
					this.#updateSelections(this.#selections ?? []);
				}
			}, { passive: true }),
			addEventListener(document, "keyup", (e) => {
				if (e.key === "Shift") this.#selectionStart = void 0;
			}, { passive: true })
		];
	}
	#resetSelectionGesture() {
		this.#selectEventDisposes?.forEach((dispose) => dispose());
		this.#selectEventDisposes = void 0;
		this.#shouldIgnoreSelectionChange = false;
		this.#isGutterMouseDown = false;
		this.#isContentMouseDown = false;
		this.#altColumnDrag = void 0;
		this.#shiftKeyPressed = false;
		this.#selectionStart = void 0;
		this.#reservedSelections = void 0;
	}
	#replaceSelectEventListeners(disposes) {
		this.#selectEventDisposes?.forEach((dispose) => dispose());
		this.#selectEventDisposes = disposes;
	}
	#isLineSelectionEnabled() {
		return this.#fileInstance?.options.enableLineSelection === true;
	}
	#preserveEditorSelectionsForGutterGesture() {
		this.#suppressNativeSelectionSync = this.#selections !== void 0;
	}
	#listenContentElement(contentEl, gutterEl) {
		const { onFocus, onBlur } = this.#options;
		const targetIsContentElement = (e) => {
			const target = e.composedPath()[0];
			return target !== void 0 && (target === contentEl || contentEl.contains(target));
		};
		this.#editorEventDisposes?.forEach((dispose) => dispose());
		this.#editorEventDisposes = [
			addEventListener(contentEl, "focus", () => {
				this.#contentHasFocus = true;
				onFocus?.();
				if (!this.#isContentMouseDown && !this.#shouldIgnoreSelectionChange && this.#selections !== void 0 && this.#selections.length > 0) this.#setWindowSelection(this.#selections.at(-1));
			}, { passive: true }),
			addEventListener(contentEl, "blur", () => {
				if (this.#replacementFocusRequest?.target === contentEl) {
					this.#replacementFocusRequest = void 0;
					this.#shouldIgnoreSelectionChange = false;
				}
				this.#contentHasFocus = false;
				onBlur?.();
			}, { passive: true }),
			addEventListener(contentEl, "pointerdown", (e) => {
				this.#canMountSelectionAction = true;
				this.#suppressNativeSelectionSync = false;
				if (e.pointerType !== "mouse") return;
				this.#altColumnDrag = void 0;
				if (this.#isDeletedLineTarget(e)) {
					this.#setDeletedTextSelectionActive(true);
					if (this.#selections !== void 0) this.#updateSelections([]);
					return;
				}
				this.#setDeletedTextSelectionActive(false);
				this.#markerRenderer?.removePopover();
				const selectEventDisposes = [];
				if (isSafari() && this.#lineAnnotations !== void 0 && this.#lineAnnotations.length > 0) {
					const annotationDisposes = [...contentEl.querySelectorAll("[data-line-annotation]")].map((el) => [addEventListener(el, "mouseenter", () => {
						this.#shouldIgnoreSelectionChange = true;
					}), addEventListener(el, "mouseleave", () => {
						this.#shouldIgnoreSelectionChange = false;
					})]).flat();
					selectEventDisposes.push(...annotationDisposes);
				}
				this.#isContentMouseDown = true;
				const isAltColumnDrag = e.button === 0 && e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey;
				this.#selectionStart = void 0;
				if (isAltColumnDrag) {
					this.#altColumnDrag = {
						pointerId: e.pointerId,
						startClientX: e.clientX,
						clientX: e.clientX,
						startScrollLeft: contentEl.parentElement?.scrollLeft ?? 0
					};
					this.#reservedSelections = void 0;
					this.#selections = void 0;
					this.#updateSelections([]);
					selectEventDisposes.push(addEventListener(document, "pointermove", (moveEvent) => {
						const drag = this.#altColumnDrag;
						if (drag === void 0 || moveEvent.pointerType !== "mouse" || moveEvent.pointerId !== drag.pointerId) return;
						drag.clientX = moveEvent.clientX;
						this.#updateAltColumnSelections();
					}, {
						capture: true,
						passive: true
					}));
				} else if (e.button === 0 && isPrimaryModifier(e)) this.#reservedSelections = this.#selections?.map((selection) => ({ ...selection }));
				this.#replaceSelectEventListeners(selectEventDisposes);
				if (e.shiftKey) {
					const primarySelection = this.#selections?.at(-1);
					if (primarySelection !== void 0) {
						const pos = primarySelection.direction === -1 ? primarySelection.end : primarySelection.start;
						this.#setWindowSelection({
							start: pos,
							end: pos,
							direction: 0
						});
					}
					this.#shiftKeyPressed = true;
				}
			}, { passive: true }),
			addEventListener(contentEl, "keydown", (e) => {
				if (!targetIsContentElement(e)) return;
				this.#canMountSelectionAction = true;
				this.#suppressNativeSelectionSync = false;
				if (e.isComposing || e.keyCode === 229) return;
				if (e.key === "Tab" && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.isComposing && !this.#isComposing && (!e.altKey || this.#options.editPrediction?.mode === "subtle") && this.#editPrediction?.rendered === true) {
					this.#acceptEditPrediction();
					e.preventDefault();
					return;
				}
				if (e.key === "Escape" && this.#editPrediction !== void 0) {
					this.#cancelEditPrediction(true);
					e.preventDefault();
					return;
				}
				const command = resolveEditorCommandFromKeyboardEvent(e, this.#options.keymap);
				if (command !== void 0) {
					e.preventDefault();
					if (command === "simplifySelection") {
						this.#searchPanel?.close();
						this.#searchPanel = void 0;
						this.#retainSearchPanelFocus = false;
						this.#selectionAction?.cleanup();
						this.#selectionAction = void 0;
					}
					this.#runCommand(command);
					return;
				}
				const mvShortcut = isMoveCursorShortcut(e);
				const textDocument = this.#editSession?.document;
				if (this.#selections !== void 0 && this.#selections.length > 0 && mvShortcut !== void 0 && textDocument !== void 0) {
					const cursorMoveOptions = {
						getSoftLineOffsets: this.#isWrap ? (line) => this.#wrapLineTextOrWholeLine(line) : void 0,
						resolveRenderableLine: this.#resolveRenderableLine
					};
					if (e.shiftKey) this.#updateSelections(mapSelectionShift(textDocument, this.#selections, mvShortcut, cursorMoveOptions));
					else this.#updateSelections(mapCursorMove(textDocument, this.#selections, mvShortcut, cursorMoveOptions));
					this.#scrollToPrimaryCaret();
					e.preventDefault();
					return;
				}
				if ((e.key.length === 1 ? e.key.toLowerCase() : e.key) === "v" && isPrimaryModifier(e)) {
					if (e.repeat && this.#isDiff) {
						e.preventDefault();
						return;
					}
					if (this.#options.clipboard !== void 0) {
						e.preventDefault();
						queueRender(this.#handleCustomPasteEvent);
						return;
					}
				}
				if (this.#searchPanel !== void 0) {
					const findAgain = resolveFindAgainShortcut(e);
					if (findAgain !== void 0) {
						e.preventDefault();
						this.#searchPanel.navigate(findAgain === "previous");
						return;
					}
				}
			}),
			addEventListener(contentEl, "copy", (e) => {
				if (!targetIsContentElement(e)) return;
				e.preventDefault();
				if (this.#isDeletedTextSelectionActive()) e.clipboardData?.setData("text", this.#deletedTextForClipboard());
				else this.#writeSelectionClipboardData(e.clipboardData, this.#getSelectionText(), this.#getSelectionClipboardTexts());
			}),
			addEventListener(contentEl, "cut", (e) => {
				if (!targetIsContentElement(e)) return;
				e.preventDefault();
				if (this.#isDeletedTextSelectionActive()) e.clipboardData?.setData("text", this.#deletedTextForClipboard());
				else {
					const selectionTexts = this.#getSelectionClipboardTexts();
					this.#writeSelectionClipboardData(e.clipboardData, this.#cutSelectionText(), selectionTexts);
				}
			}),
			addEventListener(contentEl, "paste", (e) => {
				if (!targetIsContentElement(e)) return;
				e.preventDefault();
				const clipboardData = e.clipboardData;
				const textDocument = this.#editSession?.document;
				if (clipboardData === null || textDocument === void 0) return;
				let text = clipboardData.getData("text");
				const selectionCount = this.#selections?.length ?? 0;
				if (selectionCount > 1) {
					const multiSelectionText = clipboardData.getData(MULTI_SELECTION_CLIPBOARD_TYPE);
					if (multiSelectionText !== void 0) try {
						const selectionTexts = JSON.parse(multiSelectionText);
						if (Array.isArray(selectionTexts) && selectionTexts.length === selectionCount) text = selectionTexts;
					} catch {}
				}
				this.#replaceSelectionText(Array.isArray(text) ? text.map((t) => textDocument.normalizeEol(t)) : textDocument.normalizeEol(text), void 0, true, "document");
			}),
			addEventListener(contentEl, "beforeinput", (e) => {
				if (!targetIsContentElement(e)) return;
				if (e.inputType === "insertCompositionText") {
					this.#cancelEditPrediction(true);
					return;
				}
				e.preventDefault();
				this.#handleInput(e.inputType, e.data);
			}),
			addEventListener(contentEl, "drop", (e) => {
				if (!targetIsContentElement(e)) return;
				e.preventDefault();
			}),
			addEventListener(contentEl, "compositionstart", (e) => {
				if (!targetIsContentElement(e)) return;
				this.#cancelEditPrediction(true);
				this.#isComposing = true;
				this.#shouldIgnoreSelectionChange = true;
			}, { passive: true }),
			addEventListener(contentEl, "compositionend", (e) => {
				if (!targetIsContentElement(e)) return;
				this.#shouldIgnoreSelectionChange = false;
				const wasComposing = this.#isComposing;
				this.#isComposing = false;
				if (e.data !== "" || !wasComposing) this.#handleInput("insertText", e.data);
			}, { passive: true })
		];
		const deletionsCode = this.#fileContainer?.shadowRoot?.querySelector("[data-deletions]");
		if (deletionsCode != null) this.#editorEventDisposes.push(addEventListener(deletionsCode, "pointerdown", (e) => {
			const path = e.composedPath();
			if (isGutterUtilityPath(path)) {
				this.#preserveEditorSelectionsForGutterGesture();
				return;
			}
			const target = path[0];
			const gutterRow = target instanceof HTMLElement ? target.closest("[data-column-number]") : null;
			if (gutterRow instanceof HTMLElement) {
				if (this.#isLineSelectionEnabled()) {
					this.#preserveEditorSelectionsForGutterGesture();
					return;
				}
				if (this.#beginDeletionGutterSelection(gutterRow, deletionsCode, e.pointerType === "mouse")) return;
			}
			this.#setDeletedTextSelectionActive(true);
			if (this.#selections !== void 0) this.#updateSelections([]);
		}, { passive: true }));
		if (gutterEl !== void 0) {
			const resolveGutterTarget = (eventTarget, includeContentLine = false) => {
				let target = eventTarget;
				if (target?.dataset.lineNumberContent !== void 0) target = target.parentElement ?? void 0;
				else if (includeContentLine && target?.tagName === "SPAN") target = target.closest("[data-line]");
				return target;
			};
			const resolveEditableLine = (target) => {
				if (target === void 0) return;
				const lineType = target.dataset.lineType;
				const lineNumber = getLineNumberAttr(target) ?? getLineNumberAttr(target, "columnNumber");
				if (lineNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) return;
				return lineNumber - 1;
			};
			this.#editorEventDisposes.push(addEventListener(gutterEl, "pointerdown", (e) => {
				const path = e.composedPath();
				if (isGutterUtilityPath(path)) {
					this.#preserveEditorSelectionsForGutterGesture();
					return;
				}
				const gutterRow = resolveGutterTarget(path[0]);
				if (gutterRow !== void 0 && this.#isLineSelectionEnabled()) {
					this.#preserveEditorSelectionsForGutterGesture();
					return;
				}
				if (gutterRow?.dataset.lineType === "change-deletion") {
					const code = gutterRow.closest("[data-code]");
					if (code != null) this.#beginDeletionGutterSelection(gutterRow, code, e.pointerType === "mouse");
					return;
				}
				if (e.pointerType !== "mouse") return;
				const textDocument = this.#editSession?.document;
				const lineIndex = resolveEditableLine(gutterRow);
				if (lineIndex === void 0 || textDocument === void 0) return;
				this.#canMountSelectionAction = true;
				this.#markerRenderer?.removePopover();
				const selection = this.#spanLineSelection(lineIndex, lineIndex, textDocument);
				this.#isGutterMouseDown = true;
				this.#selectionStart = selection;
				this.#updateSelections([selection]);
				this.#setWindowSelection(selection);
				this.#focus();
				this.#replaceSelectEventListeners([addEventListener(document, "mousemove", (e) => {
					if (!this.#isGutterMouseDown) return;
					const textDocument = this.#editSession?.document;
					const lineIndex = resolveEditableLine(resolveGutterTarget(e.composedPath()[0], true));
					if (lineIndex === void 0 || textDocument === void 0) return;
					const anchorLine = this.#selectionStart?.start.line ?? lineIndex;
					const selection = this.#spanLineSelection(anchorLine, lineIndex, textDocument);
					this.#selectionStart ??= selection;
					this.#updateSelections([selection]);
					this.#focus(selection.direction === -1 ? selection.start : selection.end);
				}, { passive: true })]);
			}, { passive: true }));
		}
		this.#markerRenderer?.listenHover(contentEl);
		this.#resizeObserver?.disconnect();
		this.#resizeObserver = new ResizeObserver(this.#handleLayoutResize);
		this.#resizeObserver.observe(contentEl);
		this.#resizeObserver.observe(contentEl.parentElement);
		this.#computeContentOffset(contentEl);
	}
	#handleCustomPasteEvent = async () => {
		const clipboard = this.#options.clipboard;
		if (clipboard !== void 0) {
			let text = await clipboard.readText();
			const selectionCount = this.#selections?.length ?? 0;
			if (selectionCount > 1) {
				const multiSelectionText = await clipboard.readText(MULTI_SELECTION_CLIPBOARD_TYPE);
				try {
					const selectionTexts = JSON.parse(multiSelectionText);
					if (Array.isArray(selectionTexts) && selectionTexts.length === selectionCount) text = selectionTexts;
				} catch {}
			}
			const textDocument = this.#editSession?.document;
			if (textDocument != null) this.#replaceSelectionText(Array.isArray(text) ? text.map((t) => textDocument.normalizeEol(t)) : textDocument.normalizeEol(text), void 0, true, "document");
		}
	};
	#computeContentOffset(contentEl) {
		if (this.#isDiff && this.#diffSyle === "split" && this.#isWrap) {
			this.#contentOffset = {
				top: contentEl.offsetTop - this.#metrics.paddingTop,
				left: contentEl.offsetLeft - this.#getGutterWidth()
			};
			if (this.#options.__debug === true) console.log("[diffs/editor] content offset:", this.#contentOffset);
		}
	}
	get #activeContentOffset() {
		if (this.#isDiff && this.#diffSyle === "split" && this.#isWrap) return this.#contentOffset;
	}
	#runCommand(command) {
		const textDocument = this.#editSession?.document;
		if (textDocument == null) return;
		switch (command) {
			case "openSearchPanel":
				this.#openSearchPanel("find");
				break;
			case "openSearchReplacePanel":
				this.#openSearchPanel("replace");
				break;
			case "findNextMatch": {
				const selections = this.#selections;
				if (selections === void 0) break;
				if (selections.some(isCollapsedSelection)) {
					const expanded = selections.map((sel) => {
						if (isCollapsedSelection(sel)) return expandCollapsedSelectionToWord(textDocument, sel);
						return sel;
					});
					this.#updateSelections(expanded);
					this.focus();
				} else {
					const nextMatch = findNextMatch(textDocument, selections);
					if (nextMatch !== void 0) {
						this.#updateSelections(nextMatch);
						const primaryMatch = nextMatch.at(-1);
						if (primaryMatch !== void 0) this.#revealLineIfCollapsed(getCaretPosition(primaryMatch).line);
						this.#scrollToPrimaryCaret();
					}
				}
				break;
			}
			case "moveLineUp":
			case "moveLineDown":
				this.#moveSelectedLines(command === "moveLineUp" ? -1 : 1);
				break;
			case "copyLineUp":
			case "copyLineDown":
				this.#copySelectedLines(command === "copyLineUp" ? -1 : 1);
				break;
			case "simplifySelection": {
				const selections = this.#selections;
				const primarySelection = selections?.at(-1);
				if (selections === void 0 || primarySelection === void 0) break;
				if (selections.length > 1) {
					this.#updateSelections([primarySelection]);
					this.#focus(getCaretPosition(primarySelection));
				} else if (!isCollapsedSelection(primarySelection)) {
					const caret = getCaretPosition(primarySelection);
					this.#updateSelections([{
						start: caret,
						end: caret,
						direction: 0
					}]);
					this.#focus(caret);
				}
				break;
			}
			case "insertBlankLine":
				this.#insertBlankLine();
				break;
			case "deleteHardLineForward":
				this.#deleteHardLineForward();
				break;
			case "toggleComment":
			case "toggleBlockComment": {
				const selections = this.#selections;
				if (selections === void 0) break;
				const { lineComment, blockComment } = resolveCommentConfig(textDocument.languageId, this.#options.languageCommentConfig);
				if (command === "toggleComment" && lineComment !== null) {
					this.#applyCommandEdits(resolveLineCommentEdits(textDocument, selections, lineComment));
					break;
				}
				const linewise = command === "toggleComment";
				const result = resolveBlockCommentEdits(textDocument, selections, blockComment, linewise);
				if (result !== void 0) this.#applyCommandEdits(result.edits, linewise ? void 0 : (document) => result.nextSelectionOffsets.map(([start, end, direction]) => ({
					start: document.positionAt(start),
					end: document.positionAt(end),
					direction
				})));
				break;
			}
			case "indent":
			case "outdent":
			case "indentLess":
			case "indentMore":
				if (this.#selections !== void 0) {
					const edits = [];
					const nextSelections = [];
					const editedLines = /* @__PURE__ */ new Set();
					const sameLineIndents = [];
					for (const selection of this.#selections) {
						const startLine = selection.start.line;
						const outdent = command === "outdent" || command === "indentLess";
						const lineBased = command === "indentLess" || command === "indentMore";
						if (startLine !== selection.end.line || outdent || lineBased) {
							const ret = resolveIndentEdits(textDocument, selection, this.#metrics.tabSize, outdent);
							for (const edit of ret[0]) {
								const line = edit.range.start.line;
								if (!editedLines.has(line)) {
									editedLines.add(line);
									edits.push(edit);
								}
							}
							nextSelections.push(ret[1]);
						} else {
							const text = textDocument.charAt({
								line: startLine,
								character: 0
							}) === "	" ? "	" : " ".repeat(this.#metrics.tabSize);
							edits.push({
								range: selection,
								newText: text
							});
							sameLineIndents.push({
								line: startLine,
								startCharacter: selection.start.character,
								addedLength: text.length - (selection.end.character - selection.start.character),
								selectionIndex: nextSelections.length
							});
							const nextPosition = {
								line: selection.start.line,
								character: selection.start.character + text.length
							};
							nextSelections.push({
								start: nextPosition,
								end: nextPosition,
								direction: 0
							});
						}
					}
					for (const indent of sameLineIndents) {
						let shift = 0;
						for (const other of sameLineIndents) if (other.line === indent.line && other.startCharacter < indent.startCharacter) shift += other.addedLength;
						if (shift !== 0) {
							const current = nextSelections[indent.selectionIndex];
							const position = {
								line: indent.line,
								character: current.start.character + shift
							};
							nextSelections[indent.selectionIndex] = {
								start: position,
								end: position,
								direction: 0
							};
						}
					}
					const change = textDocument.applyEdits(edits, true, this.#selections, nextSelections);
					if (change !== void 0) this.#applyChange(change, nextSelections);
				}
				break;
			case "selectAll": {
				const fullSelection = getDocumentFullSelection(textDocument);
				this.#updateSelections([fullSelection]);
				this.focus();
				const renderedLineRanges = this.#getRenderedEditableLineRanges();
				const firstRenderedLine = renderedLineRanges[0]?.[0];
				const lastRenderedLine = renderedLineRanges.at(-1)?.[1];
				if (firstRenderedLine !== void 0 && lastRenderedLine !== void 0) {
					this.#suppressNativeSelectionSync = true;
					this.#setWindowSelection({
						start: {
							line: firstRenderedLine,
							character: 0
						},
						end: {
							line: lastRenderedLine,
							character: textDocument.getLineLength(lastRenderedLine)
						},
						direction: 1
					});
				}
				break;
			}
			case "moveCursorToDocStart":
			case "moveCursorToDocEnd":
				{
					const boundarySelection = getDocumentBoundarySelection(textDocument, command === "moveCursorToDocEnd", this.#isDiff);
					this.#updateSelections([boundarySelection]);
					this.#revealLineIfCollapsed(getCaretPosition(boundarySelection).line);
					this.#scrollToPrimaryCaret();
				}
				break;
			case "expandSelectionDocStart":
			case "expandSelectionDocEnd":
				{
					const atEnd = command === "expandSelectionDocEnd";
					const selections = this.#selections;
					if (selections !== void 0) {
						const boundarySelection = getDocumentBoundarySelection(textDocument, atEnd, this.#isDiff);
						this.#updateSelections(extendSelections(selections, boundarySelection));
						this.#revealLineIfCollapsed(getCaretPosition(boundarySelection).line);
						this.#scrollToPrimaryCaret();
					}
				}
				break;
			case "undo":
			case "redo":
				this.#applyHistoryChange(command);
				break;
		}
	}
	#applyHistoryChange(command) {
		const textDocument = this.#editSession?.document;
		if (textDocument == null) return;
		if (command === "undo" && !textDocument.canUndo || command === "redo" && !textDocument.canRedo) return;
		const selections = this.#selections;
		const selectionOffsets = selections?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
		const result = command === "undo" ? textDocument.undo() : textDocument.redo();
		if (result === void 0) return;
		const [change, recordedSelections, lineAnnotations, selectionEdits] = result;
		const nextSelections = recordedSelections ?? (selections !== void 0 && selectionOffsets !== void 0 && selectionEdits !== void 0 ? remapSelectionsAfterEdits(textDocument, selections, selectionOffsets, selectionEdits) : void 0);
		const replayedLineAnnotations = lineAnnotations ?? (this.#lineAnnotations != null ? applyDocumentChangeToLineAnnotations(change, this.#lineAnnotations) : void 0);
		this.#applyChange(change, nextSelections, replayedLineAnnotations);
	}
	/** Applies one undoable command batch and records its resulting selections. */
	#applyCommandEdits(edits, resolveNextSelections) {
		const textDocument = this.#editSession?.document;
		const selections = this.#selections;
		if (textDocument === void 0 || selections === void 0) return;
		const remapSelections = resolveNextSelections === void 0;
		const selectionOffsets = remapSelections ? selections.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]) : void 0;
		const resolvedEdits = remapSelections ? edits.map((edit) => {
			const start = textDocument.offsetAt(edit.range.start);
			const end = textDocument.offsetAt(edit.range.end);
			return {
				start: Math.min(start, end),
				end: Math.max(start, end),
				text: edit.newText
			};
		}).sort((a, b) => {
			const startOrder = a.start - b.start;
			return startOrder !== 0 ? startOrder : a.end - b.end;
		}) : void 0;
		const change = textDocument.applyEdits(edits, true, selections, void 0, true);
		if (change === void 0) return;
		const nextSelections = remapSelections ? remapSelectionsAfterEdits(textDocument, selections, selectionOffsets, resolvedEdits) : resolveNextSelections(textDocument);
		textDocument.setLastUndoSelectionsAfter(nextSelections);
		this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
	}
	/** Copies selected line blocks and keeps the selection in the requested copy. */
	#copySelectedLines(direction) {
		const textDocument = this.#editSession?.document;
		const selections = this.#selections;
		if (textDocument === void 0 || selections === void 0) return;
		const blocks = getSelectedLineBlocks(selections);
		if (blocks.length === 0) return;
		const copiedLinesBefore = [];
		let copiedLineCount = 0;
		const edits = [];
		for (const block of blocks) {
			copiedLinesBefore.push(copiedLineCount);
			const blockLineCount = block.endLine - block.startLine + 1;
			copiedLineCount += blockLineCount;
			const text = textDocument.getText({
				start: {
					line: block.startLine,
					character: 0
				},
				end: {
					line: block.endLine,
					character: textDocument.getLineLength(block.endLine)
				}
			});
			if (direction > 0) {
				const position = {
					line: block.startLine,
					character: 0
				};
				edits.push({
					range: {
						start: position,
						end: position
					},
					newText: text + textDocument.eol
				});
			} else if (block.endLine < textDocument.lineCount - 1) {
				const position = {
					line: block.endLine + 1,
					character: 0
				};
				edits.push({
					range: {
						start: position,
						end: position
					},
					newText: text + textDocument.eol
				});
			} else {
				const position = {
					line: block.endLine,
					character: textDocument.getLineLength(block.endLine)
				};
				edits.push({
					range: {
						start: position,
						end: position
					},
					newText: textDocument.eol + text
				});
			}
		}
		const nextSelections = selections.map((selection) => {
			const line = selection.start.line;
			let low = 0;
			let high = blocks.length - 1;
			while (low <= high) {
				const middle = low + high >>> 1;
				const block = blocks[middle];
				if (line < block.startLine) high = middle - 1;
				else if (line > block.endLine) low = middle + 1;
				else {
					low = middle;
					break;
				}
			}
			const blockIndex = Math.max(0, low <= high ? low : high);
			const block = blocks[blockIndex];
			const shift = copiedLinesBefore[blockIndex] + (direction > 0 ? block.endLine - block.startLine + 1 : 0);
			return {
				start: {
					...selection.start,
					line: selection.start.line + shift
				},
				end: {
					...selection.end,
					line: selection.end.line + shift
				},
				direction: selection.direction
			};
		});
		this.#applyCommandEdits(edits, () => nextSelections);
	}
	/** Inserts an indented blank line after each selection's final line. */
	#insertBlankLine() {
		const textDocument = this.#editSession?.document;
		const selections = this.#selections;
		if (textDocument === void 0 || selections === void 0) return;
		const selectionLines = selections.map((selection) => getCaretPosition(selection).line);
		const targetLines = Array.from(new Set(selectionLines)).sort((a, b) => a - b);
		const targetIndex = /* @__PURE__ */ new Map();
		const indents = /* @__PURE__ */ new Map();
		const edits = [];
		for (let index = 0; index < targetLines.length; index++) {
			const line = targetLines[index];
			const lineText = textDocument.getLineText(line);
			const indent = /^\s*/.exec(lineText)?.[0] ?? "";
			targetIndex.set(line, index);
			indents.set(line, indent);
			const position = {
				line,
				character: textDocument.getLineLength(line)
			};
			edits.push({
				range: {
					start: position,
					end: position
				},
				newText: textDocument.eol + indent
			});
		}
		const nextSelections = selections.map((_, index) => {
			const line = selectionLines[index];
			const position = {
				line: line + 1 + targetIndex.get(line),
				character: indents.get(line).length
			};
			return {
				start: position,
				end: position,
				direction: 0
			};
		});
		this.#applyCommandEdits(edits, () => nextSelections);
	}
	#moveSelectedLines(direction) {
		const textDocument = this.#editSession?.document;
		const selections = this.#selections;
		if (textDocument === void 0 || selections === void 0) return;
		const blocks = getSelectedLineBlocks(selections);
		if (blocks.length === 0 || direction < 0 && blocks[0].startLine === 0 || direction > 0 && blocks.at(-1).endLine >= textDocument.lineCount - 1) return;
		const lineCount = textDocument.lineCount;
		const lineRangeEnd = (line) => line < lineCount - 1 ? {
			line: line + 1,
			character: 0
		} : {
			line,
			character: textDocument.getLineLength(line)
		};
		const getLinesText = (lines, appendFinalLineBreak) => {
			const text = lines.map((line) => textDocument.getLineText(line)).join(textDocument.eol);
			return appendFinalLineBreak ? text + textDocument.eol : text;
		};
		const edits = [];
		if (direction < 0) for (const block of blocks) {
			const previousLine = block.startLine - 1;
			const blockLines = [];
			for (let line = block.startLine; line <= block.endLine; line++) blockLines.push(line);
			edits.push({
				range: {
					start: {
						line: previousLine,
						character: 0
					},
					end: lineRangeEnd(block.endLine)
				},
				newText: getLinesText([...blockLines, previousLine], block.endLine < lineCount - 1)
			});
		}
		else for (let index = blocks.length - 1; index >= 0; index--) {
			const block = blocks[index];
			const nextLine = block.endLine + 1;
			const blockLines = [];
			for (let line = block.startLine; line <= block.endLine; line++) blockLines.push(line);
			edits.push({
				range: {
					start: {
						line: block.startLine,
						character: 0
					},
					end: lineRangeEnd(nextLine)
				},
				newText: getLinesText([nextLine, ...blockLines], nextLine < lineCount - 1)
			});
		}
		const lastBlock = blocks.at(-1);
		const lastLineLengthAfterMove = direction > 0 && lastBlock.endLine === lineCount - 2 ? textDocument.getLineLength(lastBlock.endLine) : textDocument.getLineLength(lineCount - 1);
		const nextSelections = selections.map((selection) => shiftSelectionLines(selection, direction, lineCount, (line) => line === lineCount - 1 ? lastLineLengthAfterMove : textDocument.getLineLength(line)));
		const change = textDocument.applyEdits(edits, true, selections, nextSelections, true);
		if (change !== void 0) this.#applyChange(change, nextSelections);
	}
	#handleLayoutResize = () => {
		const lineAnnotations = this.#lineAnnotations?.length ?? 0;
		const prevGutterWidth = this.#gutterWidthCache;
		const prevContentWidth = this.#contentWidthCache;
		this.#gutterWidthCache = void 0;
		this.#contentWidthCache = void 0;
		const gutterWidthChanged = this.#getGutterWidth() !== prevGutterWidth;
		const contentWidthChanged = this.#getContentWidth() !== prevContentWidth;
		if (!gutterWidthChanged && !contentWidthChanged) return;
		this.#lineElementsCache.clear();
		this.#lastAccessedCharX = void 0;
		this.#metrics.clearTextWidthCache();
		if (contentWidthChanged) {
			this.#wrapLineOffsetsCache.clear();
			if (this.#isWrap || lineAnnotations > 0) this.#lineYCache.clear();
		}
		if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) {
			this.#updateSelections(this.#selections ?? []);
			if (this.#selections !== void 0 && this.#contentHasFocus) this.focus();
		}
		this.#markerRenderer?.removePopover();
		this.#computeContentOffset(this.#contentElement);
		this.#renderCarets();
	};
	#remeasureMetricsOnFontLoad() {
		if (this.#fontRemeasureScheduled) return;
		const fonts = document.fonts;
		if (fonts === void 0) return;
		this.#fontRemeasureScheduled = true;
		fonts.ready.then(() => {
			if (this.#contentElement === void 0 || !this.#metrics.remeasureCharacterWidth()) return;
			this.#gutterWidthCache = void 0;
			this.#contentWidthCache = void 0;
			this.#resetCache();
			this.#wrapLineOffsetsCache.clear();
			if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
			this.#renderCarets();
			this.#markerRenderer?.removePopover();
		});
	}
	#rerender(change, newLineAnnotations, renderRange = this.#renderRange, shouldUpdateBuffer) {
		const tokenizer = this.#tokenizer;
		const fileInstance = this.#fileInstance;
		const applyDocumentChange = this.#applyDocumentChange;
		const textDocument = this.#editSession?.document;
		const gutterEl = this.#gutterElement;
		const contentEl = this.#contentElement;
		if (tokenizer == null || fileInstance == null || applyDocumentChange == null || textDocument == null || contentEl == null) return;
		tokenizer.stopBackgroundTokenize();
		const t = performance.now();
		const dirtyLines = tokenizer.tokenize(change, renderRange, !this.#isDiff && this.#virtualizedInstance != null);
		const t2 = performance.now();
		if (dirtyLines.size > 0) {
			const children = contentEl.children;
			const dirtyLineIndexes = /* @__PURE__ */ new Set();
			for (const lineIndex of dirtyLines.keys()) if (this.#isLineRenderable(lineIndex)) dirtyLineIndexes.add(lineIndex);
			for (let i = 0; i < children.length; i++) {
				const child = children[i];
				if (child !== void 0) {
					const lineNumber = getLineNumberAttr(child);
					const lineType = child.dataset.lineType;
					if (lineNumber === void 0 || lineType === "change-deletion") continue;
					const lineIndex = lineNumber - 1;
					if (dirtyLineIndexes.has(lineIndex)) {
						const tokens = dirtyLines.get(lineIndex);
						child.replaceChildren(...renderLineTokens(tokens));
						dirtyLineIndexes.delete(lineIndex);
						if (dirtyLineIndexes.size === 0) break;
					}
				}
			}
			if (dirtyLineIndexes.size > 0) {
				let lastRenderedLineNumber = 0;
				for (let i = children.length - 1; i >= 0; i--) {
					const child = children[i];
					const lineNumber = getLineNumberAttr(child);
					if (lineNumber !== void 0 && child.dataset.lineType !== "change-deletion") {
						lastRenderedLineNumber = lineNumber;
						break;
					}
				}
				for (const lineIndex of dirtyLineIndexes) {
					if (lineIndex < lastRenderedLineNumber) continue;
					const tokens = dirtyLines.get(lineIndex);
					const lineNumber = String(lineIndex + 1);
					h("div", {
						dataset: {
							line: lineNumber,
							lineType: "context",
							lineIndex: lineIndex.toString()
						},
						children: renderLineTokens(tokens)
					}, contentEl);
					if (gutterEl !== void 0) h("div", {
						dataset: {
							lineType: "context",
							columnNumber: lineNumber,
							lineIndex: lineIndex.toString()
						},
						children: [h("span", {
							dataset: { lineNumberContent: "" },
							textContent: lineNumber
						})]
					}, gutterEl);
				}
			}
		}
		if (change.lineDelta < 0) for (const children of [contentEl.children, gutterEl?.children ?? []]) for (let i = children.length - 1; i >= 0; i--) {
			const child = children[i];
			const lineNumber = getLineNumberAttr(child) ?? getLineNumberAttr(child, "columnNumber");
			const lineType = child.dataset.lineType;
			if (lineNumber === void 0 || lineType === "change-deletion") continue;
			if (lineNumber - 1 < change.lineCount) break;
			child.remove();
		}
		const didLineCountChange = change.lineDelta !== 0;
		if (didLineCountChange) {
			let gridRow = contentEl.children.length;
			for (const child of contentEl.children) {
				const { bufferSize } = child.dataset;
				if (bufferSize !== void 0) gridRow += parseInt(bufferSize) - 1;
			}
			contentEl.style.gridRow = "span " + gridRow;
			if (gutterEl !== void 0) gutterEl.style.gridRow = "span " + gridRow;
		}
		fileInstance.updateRenderCache(dirtyLines, tokenizer.themeType, {
			shouldRefreshDiffsView: this.#isDiff && !didLineCountChange,
			lineCountChangeInFlight: didLineCountChange
		});
		if (didLineCountChange) applyDocumentChange(textDocument, newLineAnnotations, shouldUpdateBuffer);
		if (didLineCountChange || this.#isDiff && this.#diffSyle === "unified") this.#resetCache();
		if (newLineAnnotations != null) {
			this.#lineAnnotations = newLineAnnotations;
			if (!this.#isDiff || !didLineCountChange) renderLineAnnotations(newLineAnnotations, contentEl, gutterEl, fileInstance.getAnnotationSlotName);
		}
		this.#renderCarets();
		if (this.#options.__debug === true) console.log(`[diffs/editor] re-render in: ${round(performance.now() - t2)}ms,`, `tokenize in: ${round(t2 - t)}ms (${dirtyLines.size} dirty lines)`);
	}
	#handleInput(inputType, data) {
		switch (inputType) {
			case "insertText": {
				const text = data ?? "";
				const textDocument = this.#editSession?.document;
				const selections = this.#selections;
				const autoSurroundTexts = textDocument !== void 0 && selections !== void 0 ? getAutoSurroundReplacementTexts(textDocument, selections, text, this.#options.autoSurround) : void 0;
				this.#replaceSelectionText(autoSurroundTexts ?? text);
				break;
			}
			case "insertCompositionText": break;
			case "insertLineBreak":
			case "insertParagraph":
				this.#replaceSelectionText(this.#editSession?.document?.eol ?? "\n");
				break;
			case "deleteContentBackward":
				this.#deleteSelectionText();
				break;
			case "deleteContentForward":
				this.#deleteSelectionText(true);
				break;
			case "deleteSoftLineBackward":
			case "deleteHardLineBackward":
				this.#deleteSoftLineBackward();
				break;
			case "deleteWordBackward":
				this.#deleteWordBackward();
				break;
			case "insertTranspose":
				this.#insertTranspose();
				break;
			default:
				console.warn(`[diffs] Unknown input type: ${inputType}`, data);
				break;
		}
	}
	#focus(position, preventScroll = true, shouldFocus) {
		this.#contentHasFocus = true;
		if (position !== void 0) {
			this.#shouldIgnoreSelectionChange = true;
			this.#setWindowSelection({
				start: position,
				end: position,
				direction: 0
			});
			queueRender(() => {
				if (shouldFocus?.() === false || !this.#contentHasFocus) {
					this.#shouldIgnoreSelectionChange = false;
					return;
				}
				this.#contentElement?.focus({ preventScroll });
				queueRender(() => {
					this.#shouldIgnoreSelectionChange = false;
				});
			});
		} else if (shouldFocus?.() !== false) this.#contentElement?.focus({ preventScroll });
		else this.#contentHasFocus = false;
	}
	#restoreReplacementFocus(position) {
		const request = this.#replacementFocusRequest;
		if (request == null) return;
		if (this.#retainSearchPanelFocus) {
			this.#replacementFocusRequest = void 0;
			this.#contentHasFocus = false;
			this.#shouldIgnoreSelectionChange = false;
			return;
		}
		const contentElement = this.#contentElement;
		if (contentElement == null || !this.#replacementFocusIsAvailable(contentElement)) {
			this.#replacementFocusRequest = void 0;
			this.#contentHasFocus = false;
			return;
		}
		const shouldFocus = () => {
			if (this.#replacementFocusRequest !== request) return false;
			const shouldRestore = this.#replacementFocusIsAvailable(contentElement);
			this.#replacementFocusRequest = void 0;
			return shouldRestore;
		};
		if (position != null) {
			this.#focus(position, true, shouldFocus);
			return;
		}
		const primarySelection = this.#selections?.at(-1);
		if (primarySelection == null) {
			this.#focus(void 0, true, shouldFocus);
			return;
		}
		const primaryPosition = primarySelection.direction === -1 ? primarySelection.end : primarySelection.start;
		this.#focus(primaryPosition, true, shouldFocus);
	}
	#replacementFocusIsAvailable(contentElement) {
		const fileContainer = this.#fileContainer;
		const shadowRoot = fileContainer?.shadowRoot;
		if (fileContainer == null || shadowRoot == null || this.#contentElement !== contentElement || !shadowRoot.contains(contentElement)) return false;
		const { activeElement: shadowActiveElement } = shadowRoot;
		if (shadowActiveElement === contentElement || shadowActiveElement != null && contentElement.contains(shadowActiveElement)) return true;
		if (shadowActiveElement != null) return false;
		const { ownerDocument } = fileContainer;
		const { activeElement } = ownerDocument;
		return activeElement == null || activeElement === ownerDocument.body || activeElement === ownerDocument.documentElement || activeElement === fileContainer;
	}
	#getFirstVisibleLineNumber(offset = 0) {
		const contentElement = this.#contentElement;
		const fileContainer = this.#fileContainer;
		if (this.#editSession?.document == null || contentElement == null || fileContainer == null) return;
		const viewport = this.#getScrollViewport();
		if (viewport === void 0) return;
		let viewportTop;
		let viewportBottom;
		if (viewport instanceof HTMLElement) {
			const viewportRect = viewport.getBoundingClientRect();
			viewportTop = viewportRect.top;
			viewportBottom = viewportRect.bottom;
		} else {
			viewportTop = 0;
			viewportBottom = viewport.defaultView?.innerHeight ?? 0;
		}
		const stickyHeader = fileContainer.shadowRoot?.querySelector("[data-diffs-header][data-sticky]");
		if (stickyHeader != null) viewportTop = Math.max(viewportTop, stickyHeader.getBoundingClientRect().bottom);
		if (Number.isFinite(offset)) viewportTop += Math.max(0, offset);
		if (viewportBottom <= viewportTop) return;
		for (const child of contentElement.children) {
			const row = child;
			const lineNumber = getLineNumberAttr(row);
			const lineType = row.dataset.lineType;
			if (lineNumber == null || lineType == null || !isLineEditable(lineType)) continue;
			const rowRect = row.getBoundingClientRect();
			if (rowRect.bottom <= rowRect.top) continue;
			if (rowRect.top >= viewportTop && rowRect.top < viewportBottom) return lineNumber;
		}
	}
	#focusAtPosition(position, preventScroll) {
		const selection = {
			start: position,
			end: position,
			direction: 0
		};
		this.#canMountSelectionAction = false;
		this.#updateSelections([selection]);
		this.#revealLineIfCollapsed(position.line);
		if (preventScroll) this.#focus(position, true);
		else this.#scrollToPrimaryCaret();
	}
	#setWindowSelection(selection) {
		const winSelection = window.getSelection();
		if (winSelection === null) return;
		let { start, end, direction } = selection;
		if (comparePosition(start, end) > 0) [start, end] = [end, start];
		const startLineElement = this.#getLineElement(start.line);
		const endLineElement = this.#getLineElement(end.line);
		if (startLineElement === void 0 || endLineElement === void 0) return;
		let [anchorNode, anchorOffset] = getSelectionAnchor(startLineElement, start.character);
		let [focusNode, focusOffset] = getSelectionAnchor(endLineElement, end.character);
		if (direction === -1) [anchorNode, anchorOffset, focusNode, focusOffset] = [
			focusNode,
			focusOffset,
			anchorNode,
			anchorOffset
		];
		try {
			winSelection.setBaseAndExtent(anchorNode, clampDomOffset(anchorNode, anchorOffset), focusNode, clampDomOffset(focusNode, focusOffset));
		} catch (err) {
			console.error("[diffs/editor] failed to update window selection:", err);
		}
	}
	#scrollToPrimaryCaret(noFocus = false, scrollPosition = "nearest") {
		const primarySelection = this.#selections?.at(-1);
		if (primarySelection === void 0) return;
		const primaryCaretElement = this.#primaryCaretElement;
		if (primaryCaretElement !== void 0) {
			primaryCaretElement.scrollIntoView({
				block: scrollPosition,
				inline: "nearest"
			});
			if (!noFocus) {
				const position = primarySelection.direction === -1 ? primarySelection.end : primarySelection.start;
				if (this.#replacementFocusRequest !== void 0) this.#restoreReplacementFocus(position);
				else this.#focus(position);
			}
		} else {
			const pos = getCaretPosition(primarySelection);
			this.#scrollToLine(pos.line, pos.character, noFocus);
		}
	}
	#getScrollMargin() {
		const top = this.#searchPanel !== void 0 ? 48 : 0;
		const start = this.#getGutterWidth() + this.#metrics.ch;
		return `${top}px ${this.#metrics.ch}px 0 ${start}px`;
	}
	#scrollToLine(line, char = 0, noFocus = false) {
		this.__postponeBgTokenizeToNextFrame();
		const virtualCaret = h("div", { style: {
			position: "absolute",
			left: "0",
			width: "2px",
			height: this.#metrics.lineHeight + "px",
			scrollMargin: this.#getScrollMargin()
		} });
		if (this.#getLineElement(line) !== void 0) {
			const [left, wrapLine] = this.#getCharX(line, char);
			const lineY = this.#getLineY(line) + wrapLine * this.#metrics.lineHeight;
			virtualCaret.style.top = lineY + "px";
			virtualCaret.style.left = left + "px";
			this.#overlayElement?.appendChild(virtualCaret);
			virtualCaret.scrollIntoView({
				block: "center",
				inline: "nearest"
			});
			if (!noFocus) {
				const position = {
					line,
					character: char
				};
				if (this.#replacementFocusRequest !== void 0) this.#restoreReplacementFocus(position);
				else this.#focus(position);
			}
			this.#scrollingToLine = void 0;
			this.#scrollingToLineChar = void 0;
			this.#scrollingToLineFixed = false;
			this.#scrollingToLineNoFocus = false;
		} else {
			const modelLinePosition = this.#virtualizedInstance?.getLinePosition?.(line + 1);
			if (modelLinePosition !== void 0) {
				virtualCaret.style.top = modelLinePosition.top + "px";
				this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
				virtualCaret.scrollIntoView({
					block: "center",
					inline: "nearest"
				});
				if (modelLinePosition.height > 0 && this.#isLineRenderable(line)) {
					this.#scrollingToLine = line;
					this.#scrollingToLineChar = char;
					this.#scrollingToLineNoFocus = noFocus;
				} else {
					this.#scrollingToLine = void 0;
					this.#scrollingToLineChar = void 0;
					this.#scrollingToLineNoFocus = false;
				}
			} else {
				let yFix = 0;
				if (this.#scrollingToLine === line && this.#contentElement !== void 0) for (let i = this.#contentElement.childElementCount - 1; i >= 0; i--) {
					const child = this.#contentElement.children[i];
					const lineType = child.dataset.lineType;
					const lineNumber = getLineNumberAttr(child);
					if (lineType !== void 0 && isLineEditable(lineType) && lineNumber !== void 0) {
						yFix = (line - (lineNumber - 1)) * this.#metrics.lineHeight;
						break;
					}
				}
				const approximateLineY = ((this.#lineAnnotations ?? []).filter((annotation) => annotation.lineNumber < line).length + line) * this.#metrics.lineHeight + yFix;
				virtualCaret.style.top = approximateLineY + "px";
				this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
				virtualCaret.scrollIntoView({
					block: "center",
					inline: "nearest"
				});
				if (this.#scrollingToLine === line && (yFix === 0 || this.#scrollingToLineFixed) || !this.#isLineRenderable(line)) {
					this.#scrollingToLine = void 0;
					this.#scrollingToLineChar = void 0;
					this.#scrollingToLineFixed = false;
					this.#scrollingToLineNoFocus = false;
				} else {
					this.#scrollingToLine = line;
					this.#scrollingToLineChar = char;
					this.#scrollingToLineFixed = yFix !== 0;
					this.#scrollingToLineNoFocus = noFocus;
				}
			}
		}
		virtualCaret.remove();
	}
	#spanLineSelection(anchorLine, focusLine, textDocument) {
		const lineStart = (line) => ({
			line,
			character: 0
		});
		const lineEnd = (line) => ({
			line,
			character: textDocument.getLineText(line).length
		});
		if (focusLine < anchorLine) return {
			start: lineStart(focusLine),
			end: lineEnd(anchorLine),
			direction: -1
		};
		return {
			start: lineStart(anchorLine),
			end: lineEnd(focusLine),
			direction: 1
		};
	}
	#isDeletedLineTarget(event) {
		const target = event.composedPath()[0];
		return target instanceof HTMLElement && target.closest("[data-line]")?.getAttribute("data-line-type") === "change-deletion";
	}
	#selectDeletedLines(anchorContent, focusContent, deletionsCode) {
		const rows = [...deletionsCode.querySelectorAll("[data-content] > [data-line]")];
		const anchorIndex = rows.indexOf(anchorContent);
		const focusIndex = rows.indexOf(focusContent);
		const [topContent, bottomContent] = focusIndex < anchorIndex ? [focusContent, anchorContent] : [anchorContent, focusContent];
		const winSelection = window.getSelection();
		if (winSelection !== null) {
			const range = document.createRange();
			range.setStart(topContent, 0);
			range.setEnd(bottomContent, bottomContent.childNodes.length);
			winSelection.removeAllRanges();
			winSelection.addRange(range);
		}
		this.#setDeletedTextSelectionActive(true);
		this.#updateSelections([]);
		this.#setEditorActiveLineSafe(getLineNumberAttr(focusContent) ?? null, true, "deletions");
		const lo = Math.min(anchorIndex, focusIndex);
		const hi = Math.max(anchorIndex, focusIndex);
		this.#deletedSelectionText = lo < 0 ? "" : rows.slice(lo, hi + 1).filter((row) => row.getAttribute("data-line-type") === "change-deletion").map((row) => row.textContent ?? "").join("\n");
	}
	#contentRowForGutterRow(gutterRow, contentColumn) {
		const gutterColumn = gutterRow.parentElement;
		if (gutterColumn == null || contentColumn == null) return;
		const index = [...gutterColumn.children].indexOf(gutterRow);
		const row = contentColumn.children[index];
		return row instanceof HTMLElement ? row : void 0;
	}
	#beginDeletionGutterSelection(gutterRow, code, isMouse) {
		const contentColumn = code.querySelector("[data-content]");
		const anchorContent = this.#contentRowForGutterRow(gutterRow, contentColumn);
		if (anchorContent === void 0) return false;
		this.#selectDeletedLines(anchorContent, anchorContent, code);
		if (isMouse) this.#replaceSelectEventListeners([addEventListener(document, "mousemove", (moveEvent) => {
			const moveTarget = moveEvent.composedPath()[0];
			const moveGutter = moveTarget instanceof HTMLElement ? moveTarget.closest("[data-column-number]") : null;
			if (moveGutter instanceof HTMLElement && code.contains(moveGutter)) {
				const focusContent = this.#contentRowForGutterRow(moveGutter, contentColumn);
				if (focusContent !== void 0) this.#selectDeletedLines(anchorContent, focusContent, code);
			}
		}, { passive: true })]);
		return true;
	}
	#isDeletedTextSelectionActive() {
		return this.#fileContainer?.shadowRoot?.querySelector("pre")?.hasAttribute("data-deleted-text-selection") ?? false;
	}
	#deletedTextForClipboard() {
		return this.#deletedSelectionText !== "" ? this.#deletedSelectionText : window.getSelection()?.toString() ?? "";
	}
	#setDeletedTextSelectionActive(active) {
		const pre = this.#fileContainer?.shadowRoot?.querySelector("pre");
		if (pre == null) return;
		if (active) {
			pre.setAttribute("data-deleted-text-selection", "");
			this.#setEditorActiveLineSafe(null);
			this.#deletedSelectionText = "";
		} else {
			pre.removeAttribute("data-deleted-text-selection");
			this.#deletedSelectionText = "";
		}
	}
	#setEditorActiveLineSafe(lineNumber, lineNumberOnly = false, side = "additions") {
		try {
			this.#fileInstance?.setEditorActiveLine(lineNumber, {
				lineNumberOnly,
				side
			});
		} catch {}
	}
	#scheduleThemeSelectionRefresh() {
		if (this.#themeSelectionRefreshFrame !== void 0) return;
		this.#themeSelectionRefreshFrame = requestAnimationFrame(() => {
			this.#themeSelectionRefreshFrame = void 0;
			if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
		});
	}
	#updateAltColumnSelections() {
		const drag = this.#altColumnDrag;
		const selectionStart = this.#selectionStart;
		const textDocument = this.#editSession?.document;
		if (drag === void 0 || drag.focusLine === void 0 || selectionStart === void 0 || textDocument === void 0) return false;
		const anchor = selectionStart.direction === -1 ? selectionStart.end : selectionStart.start;
		const scrollLeft = this.#contentElement?.parentElement?.scrollLeft ?? 0;
		const characterDeltaRaw = (drag.clientX - drag.startClientX + scrollLeft - drag.startScrollLeft) / this.#metrics.ch;
		const characterDelta = characterDeltaRaw < 0 ? -Math.round(-characterDeltaRaw) : Math.round(characterDeltaRaw);
		const focusCharacter = Math.max(0, anchor.character + characterDelta);
		const goal = {
			line: drag.focusLine,
			character: focusCharacter
		};
		if (drag.renderedGoal !== void 0 && comparePosition(drag.renderedGoal, goal) === 0) return true;
		drag.renderedGoal = goal;
		const selections = [];
		const step = goal.line < anchor.line ? -1 : 1;
		for (let line = anchor.line;; line += step) {
			if (this.#isLineRenderable(line)) {
				const lineText = textDocument.getLineText(line);
				const anchorOffset = Math.min(anchor.character, lineText.length);
				const focusOffset = Math.min(focusCharacter, lineText.length);
				const anchorCharacter = snapCharacterToGraphemeBoundary(lineText, anchorOffset);
				const lineFocusCharacter = focusOffset === anchorOffset ? anchorCharacter : snapCharacterToGraphemeBoundary(lineText, focusOffset);
				selections.push({
					start: {
						line,
						character: Math.min(anchorCharacter, lineFocusCharacter)
					},
					end: {
						line,
						character: Math.max(anchorCharacter, lineFocusCharacter)
					},
					direction: anchorCharacter === lineFocusCharacter ? 0 : anchorCharacter < lineFocusCharacter ? 1 : -1
				});
			}
			if (line === goal.line) break;
		}
		this.#updateSelections(selections);
		return true;
	}
	#removeRenderedEditPrediction() {
		for (const [key, element] of this.#overlayElements ?? []) if (key.startsWith("editPrediction")) {
			element.remove();
			this.#overlayElements?.delete(key);
		}
	}
	#composeEditPredictionGroup(textDocument, edits, startIndex) {
		const firstEdit = edits[startIndex];
		let endIndex = startIndex;
		let endLine = firstEdit.range.end.line;
		while (endIndex + 1 < edits.length && edits[endIndex + 1].range.start.line <= endLine) {
			endIndex++;
			endLine = Math.max(endLine, edits[endIndex].range.end.line);
		}
		if (endIndex === startIndex && (firstEdit.newText.length === 0 || comparePosition(firstEdit.range.start, firstEdit.range.end) === 0)) return {
			edit: firstEdit,
			endIndex
		};
		const start = firstEdit.range.start;
		const end = {
			line: endLine,
			character: textDocument.getLineLength(endLine)
		};
		const parts = [];
		let consumed = textDocument.offsetAt(start);
		for (let index = startIndex; index <= endIndex; index++) {
			const edit = edits[index];
			const editStart = textDocument.offsetAt(edit.range.start);
			const editEnd = textDocument.offsetAt(edit.range.end);
			parts.push(textDocument.getTextSlice(consumed, editStart), edit.newText);
			consumed = editEnd;
		}
		parts.push(textDocument.getTextSlice(consumed, textDocument.offsetAt(end)));
		return {
			edit: {
				range: {
					start,
					end
				},
				newText: parts.join("")
			},
			endIndex
		};
	}
	#getEditPredictionGroups() {
		const prediction = this.#editPrediction;
		const textDocument = this.#editSession?.document;
		if (prediction == null || textDocument == null || prediction.document !== textDocument || prediction.version !== textDocument.version || this.#options.editPrediction?.mode === "subtle" && !this.#editPredictionRevealed) return;
		const { edits } = prediction.response;
		const groups = [];
		let allVisible = edits.length > 0;
		for (let startIndex = 0; startIndex < edits.length;) {
			const { edit, endIndex } = this.#composeEditPredictionGroup(textDocument, edits, startIndex);
			for (let line = edit.range.start.line; allVisible && line <= edit.range.end.line; line++) allVisible = this.#isLineVisible(line);
			groups.push({
				edit,
				startIndex,
				endIndex
			});
			startIndex = endIndex + 1;
		}
		return {
			groups,
			allVisible
		};
	}
	#fillGhostTextElement(element, newText, anchorLeft, lineLeft, insertionSuffix) {
		if (this.#isWrap) {
			element.dataset.wrap = "";
			element.style.width = `calc(100cqw - ${lineLeft}px)`;
		} else {
			delete element.dataset.wrap;
			element.style.width = "max-content";
		}
		const lines = newText.split(/\r\n|\r|\n/);
		for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
			const lineText = lines[lineIndex];
			const suffix = lineIndex === lines.length - 1 ? insertionSuffix : void 0;
			const isEmpty = lineText.length === 0 && suffix === void 0;
			const line = h("span", {
				dataset: isEmpty ? ["editPredictionLine", "empty"] : "editPredictionLine",
				textContent: isEmpty ? "​" : lineText
			}, element);
			if (suffix !== void 0) h("span", { dataset: "editPredictionSuffix" }, line).append(suffix);
			if (lineIndex === 0 && anchorLeft !== lineLeft) line.style.textIndent = `${anchorLeft - lineLeft}px`;
		}
	}
	#cloneInsertionSuffix(group) {
		const prediction = this.#editPrediction;
		const textDocument = this.#editSession?.document;
		if (prediction == null || textDocument == null) return;
		const { edit, startIndex, endIndex } = group;
		const { start, end } = edit.range;
		if (!(comparePosition(start, end) === 0 && start.character < textDocument.getLineLength(start.line)) || prediction.response.edits[startIndex - 1]?.range.end.line === start.line || prediction.response.edits[endIndex + 1]?.range.start.line === start.line) return;
		const sourceLine = this.#getLineElement(start.line);
		if (sourceLine === void 0) return document.createTextNode(textDocument.getLineText(start.line).slice(start.character));
		const [suffixNode, suffixOffset] = getSelectionAnchor(sourceLine, start.character);
		const suffixRange = document.createRange();
		suffixRange.selectNodeContents(sourceLine);
		suffixRange.setStart(suffixNode, clampDomOffset(suffixNode, suffixOffset));
		const suffix = suffixRange.cloneContents();
		if (suffix.firstChild?.textContent === "") suffix.firstChild.remove();
		return suffix;
	}
	#measureGhostTextRows(group) {
		const { edit } = group;
		const { start } = edit.range;
		const [anchorLeft, anchorWrapLine] = this.#getCharX(start.line, start.character);
		let anchorVisualLines = 1;
		let ghostVisualLines = edit.newText.split(/\r\n|\r|\n/).length;
		const overlayElement = this.#overlayElement;
		if (this.#isWrap && overlayElement != null) {
			anchorVisualLines = Math.max(1, this.#wrapLineTextOrWholeLine(start.line).length - 1);
			const probe = h("span", {
				dataset: "editPrediction",
				style: { visibility: "hidden" }
			}, overlayElement);
			this.#fillGhostTextElement(probe, edit.newText, anchorLeft, this.#getCharX(start.line, 0)[0], this.#cloneInsertionSuffix(group));
			const { width, height } = probe.getBoundingClientRect();
			probe.remove();
			const { lineHeight } = this.#metrics;
			if (width > 0 && height > 0 && lineHeight > 0) ghostVisualLines = Math.round(height / lineHeight);
		}
		return Math.max(0, ghostVisualLines - (anchorVisualLines - anchorWrapLine));
	}
	#syncEditPredictionSpacers(notifyComponent = true) {
		const nextSpacers = /* @__PURE__ */ new Map();
		const ghostTextRows = /* @__PURE__ */ new Map();
		const previousRows = this.#ghostTextRows;
		let rowsChanged = false;
		const contentElement = this.#contentElement;
		const composed = this.#getEditPredictionGroups();
		if (contentElement != null && composed != null && composed.allVisible) {
			for (const group of composed.groups) {
				const { edit } = group;
				if (edit.newText.length === 0) continue;
				const count = this.#measureGhostTextRows(group);
				if (count > (ghostTextRows.get(edit.range.start.line) ?? 0)) ghostTextRows.set(edit.range.start.line, count);
			}
			if (ghostTextRows.size > 0) {
				let rowIndexes;
				const startingLine = this.#renderRange?.startingLine ?? 0;
				for (const [line, count] of ghostTextRows) {
					rowsChanged ||= previousRows.get(line) !== count;
					const lineElement = this.#getLineElement(line);
					if (lineElement === void 0) continue;
					let rowIndex = line - startingLine;
					if (contentElement.children[rowIndex] !== lineElement) {
						if (rowIndexes === void 0) {
							rowIndexes = /* @__PURE__ */ new Map();
							for (let index = 0; index < contentElement.children.length; index++) rowIndexes.set(contentElement.children[index], index);
						}
						rowIndex = rowIndexes.get(lineElement) ?? -1;
					}
					if (rowIndex < 0) continue;
					nextSpacers.set(lineElement, count);
					const gutterRow = this.#gutterElement?.children[rowIndex];
					if (gutterRow instanceof HTMLElement) nextSpacers.set(gutterRow, count);
					const partner = this.#deletionsColumnPartner(rowIndex);
					if (partner != null) {
						nextSpacers.set(partner.content, (nextSpacers.get(partner.content) ?? 0) + count);
						nextSpacers.set(partner.gutter, (nextSpacers.get(partner.gutter) ?? 0) + count);
					}
				}
			}
		}
		let changed = false;
		for (const [element, count] of this.#editPredictionSpacers) {
			if (nextSpacers.get(element) === count) continue;
			delete element.dataset.editPredictionSpacer;
			element.style.removeProperty("--diffs-edit-prediction-spacer-height");
			changed = true;
		}
		for (const [element, count] of nextSpacers) {
			if (this.#editPredictionSpacers.get(element) === count) continue;
			element.dataset.editPredictionSpacer = "";
			element.style.setProperty("--diffs-edit-prediction-spacer-height", `${count}lh`);
			changed = true;
		}
		this.#editPredictionSpacers = nextSpacers;
		if (rowsChanged || ghostTextRows.size !== previousRows.size) {
			this.#ghostTextRows = ghostTextRows;
			if (notifyComponent) this.#virtualizedInstance?.syncGhostTextRows();
		}
		if (changed) this.#resetCache();
	}
	#deletionsColumnPartner(rowIndex) {
		const deletionsColumn = this.#deletionsColumn;
		const contentElement = this.#contentElement;
		if (deletionsColumn == null || contentElement == null || !this.#isDiff || this.#diffSyle !== "split" || this.#isWrap) return;
		let track = 0;
		for (let index = 0; index < rowIndex; index++) track += gridTrackSpan(contentElement.children[index]);
		const { content, gutter } = deletionsColumn;
		let covered = 0;
		for (let index = 0; index < content.children.length; index++) {
			const child = content.children[index];
			covered += gridTrackSpan(child);
			if (covered > track) {
				const gutterChild = gutter.children[index];
				if (!(child instanceof HTMLElement) || !(gutterChild instanceof HTMLElement)) throw new Error("Editor: deletions column rows and gutter cells are out of step");
				return {
					content: child,
					gutter: gutterChild
				};
			}
		}
	}
	#cancelEditPrediction(removeRendered, notifyComponent = true) {
		if (this.#editPredictionTimer !== void 0) {
			clearTimeout(this.#editPredictionTimer);
			this.#editPredictionTimer = void 0;
		}
		this.#editPredictionAbortController?.abort();
		this.#editPredictionAbortController = void 0;
		this.#editPredictionGeneration++;
		this.#editPredictionRevealed = false;
		this.#retryEditPredictionOnRender = false;
		this.#editPrediction = void 0;
		this.#syncEditPredictionSpacers(notifyComponent);
		if (removeRendered) this.#removeRenderedEditPrediction();
	}
	#includesEditPredictionPath(path) {
		const options = this.#options.editPrediction;
		if (options === void 0) return false;
		const normalizedPath = path.replaceAll("\\", "/");
		return (options.include === void 0 || options.include.some((pattern) => matchesEditPredictionPattern(normalizedPath, pattern))) && options.exclude?.some((pattern) => matchesEditPredictionPattern(normalizedPath, pattern)) !== true;
	}
	#scheduleEditPrediction() {
		this.#cancelEditPrediction(true);
		const selection = this.#selections?.[0];
		if (this.#options.editPrediction === void 0 || this.#editSession?.document === void 0 || this.#editSession?.fileInfo === void 0 || this.#selections?.length !== 1 || selection === void 0 || !isCollapsedSelection(selection)) return;
		const document = this.#editSession?.document;
		const cursorOffset = document.offsetAt(getCaretPosition(selection));
		this.#editPredictionTimer = setTimeout(() => {
			this.#editPredictionTimer = void 0;
			const options = this.#options.editPrediction;
			const currentSelection = this.#selections?.[0];
			const path = this.#editSession?.fileInfo?.name;
			if (options === void 0 || path === void 0 || this.#editSession?.document !== document || this.#selections?.length !== 1 || currentSelection === void 0 || !isCollapsedSelection(currentSelection) || document.offsetAt(getCaretPosition(currentSelection)) !== cursorOffset) return;
			if (!this.#includesEditPredictionPath(path)) return;
			const isLineEditable = (line) => this.#isLineInRenderRange(line) && this.#isLineRenderable(line);
			if (!isLineEditable(getCaretPosition(currentSelection).line)) {
				this.#retryEditPredictionOnRender = true;
				return;
			}
			const request = buildEditPredictionRequest(path, document, cursorOffset, this.#editPredictionHistory, isLineEditable);
			if (request === void 0) return;
			const excerptStartOffset = document.offsetAt({
				line: request.excerptStartLine,
				character: 0
			});
			const editableStart = excerptStartOffset + request.editableRange.start;
			const editableEnd = excerptStartOffset + request.editableRange.end;
			const controller = new AbortController();
			const generation = ++this.#editPredictionGeneration;
			this.#editPredictionAbortController = controller;
			let prediction;
			try {
				prediction = options.provider.predict(request, { signal: controller.signal });
			} catch {
				this.#editPredictionAbortController = void 0;
				return;
			}
			Promise.resolve(prediction).then((response) => {
				const selection = this.#selections?.[0];
				if (controller.signal.aborted || generation !== this.#editPredictionGeneration || this.#editPredictionAbortController !== controller || this.#editSession?.document !== document || document.version !== request.version || this.#selections?.length !== 1 || selection === void 0 || !isCollapsedSelection(selection) || document.offsetAt(getCaretPosition(selection)) !== cursorOffset) return;
				if (response == null || !Array.isArray(response.edits) || response.edits.length === 0 || response.edits.length > MAX_EDIT_PREDICTION_RESPONSE_EDITS || response.newCursor == null) return;
				const resolvedEdits = [];
				let responseBytes = 0;
				for (const edit of response.edits) {
					if (edit == null || typeof edit.newText !== "string" || !isValidEditPredictionPosition(document, edit.range?.start) || !isValidEditPredictionPosition(document, edit.range?.end) || comparePosition(edit.range.start, edit.range.end) > 0) return;
					responseBytes += editPredictionTextEncoder.encode(edit.newText).byteLength;
					if (responseBytes > MAX_EDIT_PREDICTION_RESPONSE_BYTES) return;
					const start = document.offsetAt(edit.range.start);
					const end = document.offsetAt(edit.range.end);
					const resolvedEdit = document.resolveEdits([edit])[0];
					if (resolvedEdit.start !== start || resolvedEdit.end !== end) return;
					resolvedEdits.push(resolvedEdit);
				}
				resolvedEdits.sort((left, right) => {
					const startDelta = left.start - right.start;
					return startDelta === 0 ? left.end - right.end : startDelta;
				});
				for (let index = 0; index < resolvedEdits.length; index++) {
					const edit = resolvedEdits[index];
					if (edit.start < editableStart || edit.end > editableEnd || index > 0 && resolvedEdits[index - 1].end > edit.start) return;
				}
				const edits = resolvedEdits.filter((edit) => edit.text !== document.getTextSlice(edit.start, edit.end));
				if (edits.length === 0) return;
				const firstEditPosition = document.positionAt(edits[0].start);
				const lastEditPosition = document.positionAt(edits.at(-1).end);
				const affectedStart = document.offsetAt({
					line: firstEditPosition.line,
					character: 0
				});
				const affectedEnd = document.offsetAt({
					line: lastEditPosition.line,
					character: document.getLineLength(lastEditPosition.line)
				});
				const predictedParts = [];
				let consumed = affectedStart;
				for (const edit of edits) {
					predictedParts.push(document.getTextSlice(consumed, edit.start), edit.text);
					consumed = edit.end;
				}
				predictedParts.push(document.getTextSlice(consumed, affectedEnd));
				const predictedLines = predictedParts.join("").split(/\r\n|\r|\n/);
				const affectedEndLine = firstEditPosition.line + predictedLines.length - 1;
				const lineDelta = predictedLines.length - (lastEditPosition.line - firstEditPosition.line + 1);
				const newCursor = response.newCursor;
				if (!Number.isInteger(newCursor.line) || !Number.isInteger(newCursor.character) || newCursor.line < 0 || newCursor.character < 0) return;
				if (newCursor.line >= firstEditPosition.line && newCursor.line <= affectedEndLine) {
					const line = predictedLines[newCursor.line - firstEditPosition.line];
					if (newCursor.character > line.length || splitsSurrogatePair(line, newCursor.character)) return;
				} else {
					const originalLine = newCursor.line < firstEditPosition.line ? newCursor.line : newCursor.line - lineDelta;
					if (originalLine < 0 || originalLine >= document.lineCount || newCursor.character > document.getLineLength(originalLine)) return;
					const originalOffset = document.offsetAt({
						line: originalLine,
						character: newCursor.character
					});
					if (splitsSurrogatePair(document.charAt(originalOffset - 1) + document.charAt(originalOffset), 1)) return;
				}
				this.#editPrediction = {
					document,
					version: request.version,
					cursorOffset,
					rendered: false,
					response: {
						edits: edits.map((edit) => ({
							range: {
								start: document.positionAt(edit.start),
								end: document.positionAt(edit.end)
							},
							newText: edit.text
						})),
						newCursor: { ...newCursor }
					}
				};
				this.#updateSelections(this.#selections);
			}).catch(() => {}).finally(() => {
				if (this.#editPredictionAbortController === controller) this.#editPredictionAbortController = void 0;
			});
		}, EDIT_PREDICTION_DEBOUNCE_MS);
	}
	#recordEditPredictionHistory(change, source) {
		const textDocument = this.#editSession?.document;
		const path = this.#editSession?.fileInfo?.name;
		const transaction = getTextDocumentChangeTransaction(change);
		if (textDocument === void 0 || path === void 0 || transaction === void 0 || !this.#includesEditPredictionPath(path)) return;
		this.#editPredictionHistory = recordEditPrediction(this.#editPredictionHistory, path, textDocument, transaction, source);
	}
	#acceptEditPrediction() {
		const prediction = this.#editPrediction;
		const textDocument = this.#editSession?.document;
		const selection = this.#selections?.[0];
		if (prediction === void 0 || !prediction.rendered || textDocument === void 0 || prediction.document !== textDocument || prediction.version !== textDocument.version || this.#selections?.length !== 1 || selection === void 0 || !isCollapsedSelection(selection) || textDocument.offsetAt(getCaretPosition(selection)) !== prediction.cursorOffset) return;
		const { edits, newCursor } = prediction.response;
		this.#cancelEditPrediction(true);
		const change = textDocument.applyEdits(edits.map((edit) => ({
			range: {
				start: { ...edit.range.start },
				end: { ...edit.range.end }
			},
			newText: edit.newText
		})), true, this.#selections, void 0, true);
		if (change === void 0) {
			this.#scheduleEditPrediction();
			return;
		}
		const cursor = textDocument.normalizePosition(newCursor);
		const nextSelections = [{
			start: cursor,
			end: cursor,
			direction: 0
		}];
		textDocument.setLastUndoSelectionsAfter(nextSelections);
		this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change), { editSource: "prediction" });
	}
	#renderEditPrediction(renderCtx) {
		const prediction = this.#editPrediction;
		const textDocument = this.#editSession?.document;
		if (prediction !== void 0) prediction.rendered = false;
		const composed = this.#getEditPredictionGroups();
		if (prediction == null || textDocument == null || composed == null || !composed.allVisible) return;
		for (const group of composed.groups) {
			const { edit, endIndex } = group;
			const { start, end } = edit.range;
			const isDeletion = edit.newText.length === 0;
			const isReplacement = comparePosition(start, end) !== 0;
			const lineLength = textDocument.getLineLength(start.line);
			const isMidLineInsertion = !isReplacement && start.character < lineLength;
			if (isReplacement) this.#renderSelection(renderCtx, isDeletion ? "editPredictionDeletion" : "editPredictionReplacement", {
				start,
				end
			});
			else if (isMidLineInsertion) this.#renderSelection(renderCtx, "editPredictionInsertion", {
				start,
				end: {
					line: start.line,
					character: lineLength
				}
			});
			if (isDeletion) continue;
			const [anchorLeft, anchorWrapLine] = this.#getCharX(start.line, start.character);
			const lineLeft = this.#getCharX(start.line, 0)[0];
			const anchorTop = this.#getLineY(start.line) + anchorWrapLine * this.#metrics.lineHeight;
			const key = `editPrediction-${endIndex}`;
			let element = this.#overlayElements?.get(key);
			if (element !== void 0) {
				this.#overlayElements?.delete(key);
				element.replaceChildren();
			} else element = h("span", {
				ariaHidden: "true",
				contentEditable: "false",
				dataset: "editPrediction"
			}, renderCtx.fragment);
			if (isReplacement) {
				element.dataset.replacement = "";
				const lineElement = this.#getLineElement(start.line);
				if (lineElement !== void 0) element.style.setProperty("--diffs-edit-prediction-bg", getComputedStyle(lineElement).getPropertyValue("--diffs-line-bg"));
			} else {
				delete element.dataset.replacement;
				element.style.removeProperty("--diffs-edit-prediction-bg");
			}
			this.#fillGhostTextElement(element, edit.newText, anchorLeft, lineLeft, this.#cloneInsertionSuffix(group));
			element.style.transform = `translateX(${lineLeft}px) translateY(${anchorTop}px)`;
			renderCtx.elements.set(key, element);
		}
		prediction.rendered = true;
	}
	#updateSelections(selections) {
		this.__postponeBgTokenizeToNextFrame();
		const previousSelections = this.#selections;
		let selectionsChanged = previousSelections?.length !== selections.length;
		if (!selectionsChanged && previousSelections !== void 0) for (let i = 0; i < selections.length; i++) {
			const previous = previousSelections[i];
			const next = selections[i];
			if (previous.direction !== next.direction || comparePosition(previous.start, next.start) !== 0 || comparePosition(previous.end, next.end) !== 0) {
				selectionsChanged = true;
				break;
			}
		}
		if (selectionsChanged) this.#cancelEditPrediction(true);
		this.#syncEditPredictionSpacers();
		this.#primaryCaretElement = void 0;
		this.#setEditorActiveLineSafe(null);
		if (selections.length === 0 && this.#matches === void 0 && this.#markerRenderer === void 0) {
			this.#selections = void 0;
			this.#overlayElements?.forEach((el) => el.remove());
			this.#overlayElements?.clear();
			this.#selectionAction?.cleanup();
			this.#selectionAction = void 0;
			if (selectionsChanged) this.#scheduleEditPrediction();
			return;
		}
		const fragment = document.createDocumentFragment();
		const renderCtx = {
			fragment,
			elements: /* @__PURE__ */ new Map()
		};
		if (selections.length > 0) {
			const normalizedSelections = mergeOverlappingSelections(selections);
			const primarySelection = normalizedSelections.at(-1);
			this.#selections = normalizedSelections;
			const hasNonEmptySelection = normalizedSelections.some((selection) => !isCollapsedSelection(selection));
			const caretLine = getCaretPosition(primarySelection).line + 1;
			this.#setEditorActiveLineSafe(caretLine, hasNonEmptySelection);
			for (const selection of normalizedSelections) {
				if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, "selection", selection);
				this.#renderCaret(renderCtx, selection, selection === primarySelection);
			}
			const bracketMatchRanges = this.#options.matchBrackets !== false && this.#editSession?.document != null && this.#tokenizer !== void 0 && isCollapsedSelection(primarySelection) ? findBracketMatchRanges(this.#editSession?.document, this.#tokenizer, primarySelection.start) : void 0;
			if (bracketMatchRanges !== void 0) for (const range of bracketMatchRanges) this.#renderSelection(renderCtx, "bracketMatch", range);
		}
		const textDocument = this.#editSession?.document;
		if (this.#matches !== void 0 && textDocument !== void 0) {
			const matches = this.#matches;
			const renderRange = this.#renderRange;
			const renderedLineRanges = renderRange != null && Number.isFinite(renderRange.totalLines) || this.#isDiff && this.#fileDiffInstance?.options.expandUnchanged !== true ? this.#getRenderedEditableLineRanges() : void 0;
			const primarySelection = this.#selections?.at(-1);
			const primaryStartOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.start) : -1;
			const primaryEndOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.end) : -1;
			let firstMatchIndex = 0;
			const rangeCount = renderedLineRanges?.length ?? 1;
			for (let rangeIndex = 0; rangeIndex < rangeCount; rangeIndex++) {
				let visibleEndOffset = Infinity;
				const lineRange = renderedLineRanges?.[rangeIndex];
				if (lineRange !== void 0) {
					const [firstLine, lastLine] = lineRange;
					const visibleStartOffset = textDocument.offsetAt({
						line: firstLine,
						character: 0
					});
					const endLine = lastLine + 1;
					visibleEndOffset = endLine < textDocument.lineCount ? textDocument.offsetAt({
						line: endLine,
						character: 0
					}) : textDocument.offsetAt({
						line: endLine - 1,
						character: textDocument.getLineLength(endLine - 1)
					});
					let low = firstMatchIndex;
					let high = matches.length;
					while (low < high) {
						const middle = low + Math.floor((high - low) / 2);
						if (matches[middle][1] <= visibleStartOffset) low = middle + 1;
						else high = middle;
					}
					firstMatchIndex = low;
				}
				for (; firstMatchIndex < matches.length; firstMatchIndex++) {
					const [startOffset, endOffset] = matches[firstMatchIndex];
					if (startOffset >= visibleEndOffset) break;
					const range = {
						start: textDocument.positionAt(startOffset),
						end: textDocument.positionAt(endOffset)
					};
					const isFocused = primaryStartOffset === startOffset && primaryEndOffset === endOffset;
					this.#renderSelection(renderCtx, "match", range, isFocused ? "focus" : void 0);
				}
			}
		}
		if (this.#markerRenderer !== void 0 && textDocument !== void 0) for (const marker of this.#markerRenderer.markers) this.#renderSelection(renderCtx, "marker", marker, markerSeverityDatasetKey(marker.severity));
		this.#renderEditPrediction(renderCtx);
		this.#overlayElement?.appendChild(fragment);
		this.#overlayElements?.forEach((el) => el.remove());
		this.#overlayElements?.clear();
		this.#overlayElements = renderCtx.elements;
		this.#updateSelectionActionPopover();
		if (selectionsChanged) this.#scheduleEditPrediction();
	}
	#renderCarets() {
		const carets = this.#carets;
		const renderCaret = this.#options.renderCaret;
		const overlayElement = this.#overlayElement;
		this.#caretHighlightElements?.forEach((element) => element.remove());
		this.#caretHighlightElements = void 0;
		if (carets === void 0 || renderCaret === void 0 || overlayElement === void 0) {
			this.#caretElements?.forEach((element) => element.remove());
			this.#caretElements = void 0;
			return;
		}
		const elements = this.#caretElements ??= /* @__PURE__ */ new Map();
		for (const [trackedCaret, element] of elements) if (!this.#isLineVisible(trackedCaret.caret.focus.line)) {
			element.remove();
			elements.delete(trackedCaret);
		}
		const fragment = document.createDocumentFragment();
		const highlightElements = [];
		for (const trackedCaret of carets) {
			const { caret } = trackedCaret;
			const { line, character } = caret.focus;
			if (trackedCaret.anchorOffset !== trackedCaret.focusOffset) {
				const highlightContext = {
					fragment,
					elements: /* @__PURE__ */ new Map()
				};
				this.#renderSelection(highlightContext, "caretHighlight", trackedCaret.anchorOffset < trackedCaret.focusOffset ? {
					start: caret.anchor,
					end: caret.focus
				} : {
					start: caret.focus,
					end: caret.anchor
				}, void 0, caret.focus);
				for (const highlightElement of highlightContext.elements.values()) {
					highlightElement.style.setProperty("--diffs-caret-highlight-bg", `color-mix(in srgb, ${caret.metadata.color} 32%, transparent)`);
					highlightElements.push(highlightElement);
				}
			}
			if (!this.#isLineVisible(line)) continue;
			let element = elements.get(trackedCaret);
			if (element === void 0) {
				element = h("div", {
					dataset: "remoteCaret",
					children: [renderCaret(caret)]
				}, fragment);
				elements.set(trackedCaret, element);
			}
			const [left, wrapLine] = this.#getCharX(line, character);
			const top = this.#getLineY(line) + wrapLine * this.#metrics.lineHeight;
			element.style.transform = `translateX(${left}px) translateY(${top}px)`;
		}
		this.#caretHighlightElements = highlightElements;
		overlayElement.appendChild(fragment);
	}
	#renderSelection(renderCtx, type, range, extraDataset, connectedCaret) {
		if (this.#editSession?.document == null) return;
		const { start, end } = range;
		let firstLine = start.line;
		let endLine = end.line + 1;
		if (this.#renderRange !== void 0) {
			const { startingLine, totalLines } = this.#renderRange;
			firstLine = Math.max(firstLine, startingLine);
			endLine = Math.min(endLine, startingLine + totalLines);
		}
		for (let line = firstLine; line < endLine; line++) {
			if (!this.#isLineVisible(line)) continue;
			const isLastLine = line === end.line;
			const startChar = line === start.line ? start.character : 0;
			const endChar = isLastLine ? end.character : this.#editSession?.document.getLineLength(line);
			if (startChar === endChar && (type === "editPredictionDeletion" || type === "editPredictionReplacement")) {
				if (isLastLine && line !== start.line) continue;
				const [left, wrapLine] = this.#getCharX(line, startChar);
				this.#renderSelectionBlock(renderCtx, type, line, wrapLine, left, this.#metrics.ch, extraDataset);
				continue;
			}
			if (this.#isWrap) {
				const contentWidth = this.#getContentWidth();
				const lineText = this.#editSession?.document.getLineText(line);
				if (2 * this.#metrics.ch + this.#metrics.measureTextWidth(lineText) > contentWidth) {
					this.#renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset, connectedCaret);
					continue;
				}
			}
			let left = 0;
			let width = 0;
			let paddingEnd = 0;
			if (startChar === 0) left = this.#getGutterWidth() + this.#metrics.ch + (this.#activeContentOffset?.left ?? 0);
			else left = this.#getCharX(line, startChar)[0];
			if (!isLastLine && type === "selection") paddingEnd = this.#metrics.ch;
			if (startChar === endChar) width = paddingEnd;
			else width = this.#getCharX(line, endChar)[0] - left + paddingEnd;
			this.#renderSelectionBlock(renderCtx, type, line, 0, left, width, extraDataset, {
				start: connectedCaret?.line === start.line && connectedCaret.character === start.character && line === start.line,
				end: connectedCaret?.line === end.line && connectedCaret.character === end.character && line === end.line
			});
		}
	}
	#renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset, connectedCaret) {
		const wrapOffsets = this.#wrapLineTextOrWholeLine(line);
		const segmentCount = wrapOffsets.length - 1;
		const offsetLeft = this.#getGutterWidth() + this.#metrics.ch + (this.#activeContentOffset?.left ?? 0);
		for (let wrapLine = 0; wrapLine < segmentCount; wrapLine++) {
			const segmentStart = wrapOffsets[wrapLine];
			const segmentEnd = wrapOffsets[wrapLine + 1];
			const wrapStartChar = Math.max(startChar, segmentStart);
			const wrapEndChar = Math.min(endChar, segmentEnd);
			if (wrapStartChar > wrapEndChar) continue;
			const segmentStartWidth = this.#segmentTextWidth(lineText, segmentStart, wrapStartChar);
			const segmentLeft = offsetLeft + segmentStartWidth;
			let paddingEnd = 0;
			if (!isLastLine && wrapLine === segmentCount - 1 && type === "selection") paddingEnd = this.#metrics.ch;
			const segmentWidth = wrapStartChar === wrapEndChar ? paddingEnd : this.#segmentTextWidth(lineText, segmentStart, wrapEndChar) - segmentStartWidth + paddingEnd;
			this.#renderSelectionBlock(renderCtx, type, line, wrapLine, segmentLeft, segmentWidth, extraDataset, {
				start: connectedCaret?.line === line && connectedCaret.character === startChar && wrapStartChar === startChar,
				end: connectedCaret?.line === line && connectedCaret.character === endChar && wrapEndChar === endChar
			});
		}
	}
	#segmentTextWidth(lineText, segmentStart, character) {
		if (character <= segmentStart) return 0;
		const segmentText = lineText.slice(segmentStart, character);
		const asciiColumns = getExpandedAsciiTextColumns(segmentText, this.#metrics.tabSize);
		return asciiColumns !== -1 ? asciiColumns * this.#metrics.ch : this.#metrics.measureTextWidth(segmentText);
	}
	#renderSelectionBlock(renderCtx, type, line, wrapLine, left, width, extraDataset, connectedCaretEdge) {
		if (width === 0) return;
		const { ch, lineHeight } = this.#metrics;
		const y = this.#getLineY(line) + wrapLine * lineHeight;
		const cacheKey = `${type}-${line}/${wrapLine}-${left}-${width} ${extraDataset ?? ""}`;
		const overlayEls = this.#overlayElements;
		const rounded = (this.#options.roundedSelection ?? true) && (type === "selection" || type === "caretHighlight");
		const addRoundedCorner = (line, wrapLine, left, radius) => {
			const top = this.#getLineY(line) + wrapLine * lineHeight;
			const lineElement = this.#getLineElement(line);
			let cornerBg = "initial";
			if (lineElement !== void 0 && (lineElement.dataset.lineType === "change-addition" || lineElement.dataset.selectedLine !== void 0)) cornerBg = getComputedStyle(lineElement).getPropertyValue("--diffs-line-bg");
			const css = `width:${ch}px;transform:translateX(${left}px) translateY(${top}px);--diffs-selection-corner-bg:${cornerBg}`;
			const dataset = {
				selectionCorner: "",
				[radius]: ""
			};
			const cacheKeyPrefix = `${type}-block-${line}/${wrapLine}-${left}-1ch`;
			let cacheKey = cacheKeyPrefix + "-" + radius;
			if (radius === "rbl") {
				const prevCornerKey = cacheKeyPrefix + "-rtl";
				const prevCorner = renderCtx.elements.get(prevCornerKey);
				if (prevCorner !== void 0) {
					prevCorner.remove();
					renderCtx.elements.delete(prevCornerKey);
					cacheKey += "-rtl";
					dataset.rtl = "";
				}
			}
			let cornerEl = renderCtx.elements.get(cacheKey);
			if (cornerEl !== void 0) return;
			if (overlayEls?.has(cacheKey) === true) {
				cornerEl = overlayEls.get(cacheKey);
				cornerEl.style.cssText = css;
				overlayEls.delete(cacheKey);
			} else cornerEl = h("div", {
				dataset: type === "caretHighlight" ? "caretHighlightRange" : "selectionRange",
				style: { cssText: css },
				children: [h("div", { dataset })]
			}, renderCtx.fragment);
			renderCtx.elements.set(cacheKey, cornerEl);
		};
		const addRadiusStyle = (element) => {
			const end = left + width;
			const dataset = element.dataset;
			const previousSelectionRange = renderCtx.previousSelectionRange;
			if (previousSelectionRange === void 0 || previousSelectionRange.line !== line || previousSelectionRange.wrapLine !== wrapLine) renderCtx.previousSelectionRange = {
				element,
				line,
				wrapLine,
				left,
				width
			};
			if (previousSelectionRange === void 0 || end <= previousSelectionRange.left) [
				"rtl",
				"rtr",
				"rbl",
				"rbr"
			].forEach((key) => {
				dataset[key] = "";
			});
			else {
				const prevLine = previousSelectionRange.line;
				const prevWrapLine = previousSelectionRange.wrapLine;
				const prevLeft = previousSelectionRange.left;
				const prevDataset = previousSelectionRange.element.dataset;
				const prevEnd = prevLeft + previousSelectionRange.width;
				if (prevLeft > left) addRoundedCorner(prevLine, prevWrapLine, prevLeft - ch, "rbr");
				delete prevDataset.rbl;
				delete dataset.rtl;
				delete dataset.rtr;
				if (end >= prevEnd) delete prevDataset.rbr;
				if (end > prevEnd) {
					addRoundedCorner(prevLine, prevWrapLine, prevEnd, "rbl");
					dataset.rtr = "";
				}
				if (end < prevEnd) addRoundedCorner(line, wrapLine, end, "rtl");
				if (left < prevLeft) dataset.rtl = "";
				dataset.rbl = "";
				dataset.rbr = "";
			}
			if (connectedCaretEdge?.start === true) {
				delete dataset.rtl;
				delete dataset.rbl;
			}
			if (connectedCaretEdge?.end === true) {
				delete dataset.rtr;
				delete dataset.rbr;
			}
		};
		let rangeEl = renderCtx.elements.get(cacheKey);
		if (rangeEl !== void 0) {
			if (rounded) addRadiusStyle(rangeEl);
			return;
		}
		if (overlayEls?.has(cacheKey) === true) {
			rangeEl = overlayEls.get(cacheKey);
			overlayEls.delete(cacheKey);
		} else rangeEl = h("div", { dataset: extraDataset ? [type + "Range", extraDataset] : type + "Range" }, renderCtx.fragment);
		rangeEl.style.width = `${width}px`;
		rangeEl.style.transform = `translateX(${left}px) translateY(${y}px)`;
		if (type === "editPredictionInsertion" || type === "editPredictionReplacement") {
			const lineElement = this.#getLineElement(line);
			if (lineElement !== void 0) rangeEl.style.setProperty("--diffs-edit-prediction-bg", getComputedStyle(lineElement).getPropertyValue("--diffs-line-bg"));
		} else rangeEl.style.removeProperty("--diffs-edit-prediction-bg");
		if (rounded) addRadiusStyle(rangeEl);
		renderCtx.elements.set(cacheKey, rangeEl);
	}
	#renderCaret(renderCtx, selection, isPrimary) {
		const { line, character } = getCaretPosition(selection);
		if (!this.#isLineVisible(line)) return;
		const [left, wrapLine] = this.#getCharX(line, character);
		const cacheKey = "caret-" + line + "/" + wrapLine + ":" + character;
		if (renderCtx.elements.has(cacheKey)) return;
		const caretEl = h("div", {
			dataset: "caret",
			style: { transform: `translateX(${left - 1}px) translateY(${this.#getLineY(line) + wrapLine * this.#metrics.lineHeight}px)` }
		}, renderCtx.fragment);
		renderCtx.elements.set(cacheKey, caretEl);
		if (isPrimary) {
			caretEl.style.scrollMargin = this.#getScrollMargin();
			this.#primaryCaretElement = caretEl;
		}
	}
	#updateSelectionActionPopover() {
		const primarySelection = this.#selections?.at(-1);
		const overlayElement = this.#overlayElement;
		const textDocument = this.#editSession?.document;
		const renderSelectionAction = this.#options.renderSelectionAction;
		const cleanup = () => {
			this.#selectionAction?.cleanup();
			this.#selectionAction = void 0;
		};
		if (this.#options.enabledSelectionAction !== true || renderSelectionAction === void 0 || primarySelection === void 0 || isCollapsedSelection(primarySelection) || this.#isContentMouseDown || overlayElement === void 0 || textDocument === void 0) {
			cleanup();
			return;
		}
		const head = getCaretPosition(primarySelection);
		if (!this.#isLineVisible(head.line)) {
			cleanup();
			return;
		}
		if (this.#selectionAction === void 0) {
			if (!this.#canMountSelectionAction) return;
			const getActiveSelection = () => this.#selections?.at(-1) ?? primarySelection;
			const selectionActionElement = renderSelectionAction({
				textDocument,
				get selection() {
					return getActiveSelection();
				},
				applyEdits: (edits) => this.applyEdits(edits),
				getSelectionText: () => this.#editSession?.document?.getText(getActiveSelection()) ?? "",
				replaceSelectionText: (text) => {
					this.#replaceSelectionText(text, [getActiveSelection()]);
				},
				close: () => {
					cleanup();
					this.#scrollToPrimaryCaret();
				}
			});
			this.#selectionAction = new SelectionActionWidget(selectionActionElement, overlayElement, () => this.#updateSelectionActionPopover());
			this.#getPopoverManager().resetPlacement(SELECTION_ACTION_POPOVER_PLACEMENT_KEY);
		}
		const lineHeight = this.#metrics.lineHeight;
		const isBackward = primarySelection.direction === -1;
		const preferred = {
			placeAbove: isBackward,
			anchor: head
		};
		const fallback = isBackward ? {
			placeAbove: false,
			anchor: primarySelection.end
		} : {
			placeAbove: true,
			anchor: primarySelection.start
		};
		const popoverHeight = this.#selectionAction.height;
		const candidateGeometry = (candidate) => {
			const [left, candidateWrapLine] = this.#getCharX(candidate.anchor.line, candidate.anchor.character);
			const rowTop = this.#getLineY(candidate.anchor.line) + candidateWrapLine * lineHeight;
			const anchorTop = candidate.placeAbove ? rowTop : rowTop + lineHeight;
			const top = candidate.placeAbove ? anchorTop - popoverHeight : anchorTop;
			return {
				top,
				bottom: top + popoverHeight,
				left,
				anchorTop,
				rowTop
			};
		};
		const preferredGeometry = candidateGeometry(preferred);
		const lineCount = textDocument.lineCount;
		const atDocumentEdge = isBackward ? head.line < 3 : head.line >= lineCount - 3;
		const fallbackGeometry = this.#isLineVisible(fallback.anchor.line) ? candidateGeometry(fallback) : void 0;
		const popoverManager = this.#getPopoverManager();
		const viewport = popoverManager.getPlacementBounds();
		const useFallback = fallbackGeometry !== void 0 && popoverManager.choosePlacement({
			preferred: preferredGeometry,
			fallback: fallbackGeometry,
			viewport,
			popoverHeight,
			atDocumentEdge,
			placementKey: SELECTION_ACTION_POPOVER_PLACEMENT_KEY
		}) === "fallback";
		if (fallbackGeometry === void 0) popoverManager.setPlacement("preferred", SELECTION_ACTION_POPOVER_PLACEMENT_KEY);
		const { placeAbove } = useFallback ? fallback : preferred;
		const { left, anchorTop } = useFallback && fallbackGeometry !== void 0 ? fallbackGeometry : preferredGeometry;
		const selectionTop = isBackward ? preferredGeometry.rowTop : fallbackGeometry?.rowTop ?? -Infinity;
		const selectionBottom = (isBackward ? fallbackGeometry?.rowTop ?? Infinity : preferredGeometry.rowTop) + (primarySelection.end.line > primarySelection.start.line && primarySelection.end.character === 0 ? 0 : lineHeight);
		const isSelectionVisible = viewport === void 0 || selectionBottom > viewport.top && selectionTop < viewport.bottom;
		this.#selectionAction.reposition(left, anchorTop, this.#getGutterWidth(), placeAbove, isSelectionVisible, viewport);
	}
	#openSearchPanel(mode) {
		if (this.#searchPanel !== void 0) {
			this.#searchPanel.setMode(mode);
			return;
		}
		this.#renderSearchPanel(mode);
	}
	#renderSearchPanel(mode) {
		this.#searchPanel?.cleanup();
		const textDocument = this.#editSession?.document;
		const preElement = this.#fileContainer?.shadowRoot?.querySelector("pre");
		const selections = this.#selections;
		if (textDocument === void 0 || preElement == null) return;
		let defaultQuery = "";
		let initialMatch = void 0;
		if (selections !== void 0 && selections.length > 0) {
			let primarySelection = selections.at(-1);
			if (isCollapsedSelection(primarySelection)) {
				primarySelection = expandCollapsedSelectionToWord(textDocument, primarySelection);
				this.#updateSelections([...selections.slice(0, -1), primarySelection]);
				const selectionText = textDocument.getText(primarySelection);
				if (selectionText !== "" && !selectionText.includes("\n")) {
					defaultQuery = selectionText;
					initialMatch = [textDocument.offsetAt(primarySelection.start), textDocument.offsetAt(primarySelection.end)];
				}
			}
		}
		const scrollToMatch = ([startOffset, endOffset], retainFocus) => {
			const nextSelection = createSelectionFromAnchorAndFocusOffsets(textDocument, startOffset, endOffset);
			this.#updateSelections([nextSelection]);
			this.#revealLineIfCollapsed(getCaretPosition(nextSelection).line);
			this.#scrollToPrimaryCaret(true);
			this.#retainSearchPanelFocus = retainFocus;
		};
		const searchPanel = new SearchPanelWidget({
			textDocument,
			containerElement: preElement,
			defaultQuery,
			mode,
			initialMatch,
			scrollToMatch,
			applyReplace: (edits) => {
				if (edits.length === 0) return;
				const change = textDocument.applyEdits(edits.map((edit) => ({
					range: {
						start: textDocument.positionAt(edit.start),
						end: textDocument.positionAt(edit.end)
					},
					newText: edit.text
				})), true, this.#selections);
				if (change !== void 0) this.#applyChange(change, void 0, this.#applyChangeToLineAnnotations(change), { skipSearchRefresh: true });
			},
			onUpdate: (allMatches, options) => {
				if (allMatches.length === 0) {
					this.#matches = void 0;
					this.#updateSelections(this.#selections ?? []);
					return;
				}
				this.#matches = allMatches;
				if (options?.syncSelection === false) {
					this.#updateSelections(this.#selections ?? []);
					const primarySelection = this.#selections?.at(-1);
					if (primarySelection !== void 0) {
						const startOffset = textDocument.offsetAt(primarySelection.start);
						const endOffset = textDocument.offsetAt(primarySelection.end);
						for (const match of allMatches) if (match[0] === startOffset && match[1] === endOffset) return match;
					}
					return;
				}
				const primarySelection = this.#selections?.at(-1);
				let searchOffset = 0;
				let nextMatch;
				if (primarySelection !== void 0) searchOffset = textDocument.offsetAt(primarySelection.start);
				for (const m of allMatches) if (m[0] >= searchOffset) {
					nextMatch = m;
					break;
				}
				if (nextMatch !== void 0) scrollToMatch(nextMatch, true);
				else this.#updateSelections(this.#selections ?? []);
				return nextMatch;
			},
			onClose: () => {
				this.#searchPanel = void 0;
				this.#retainSearchPanelFocus = false;
				this.#matches = void 0;
				this.#updateSelections(this.#selections ?? []);
			}
		});
		this.#searchPanel = searchPanel;
		this.#retainSearchPanelFocus = false;
	}
	#getSelectionText() {
		const textDocument = this.#editSession?.document;
		const selections = this.#selections;
		if (textDocument === void 0 || selections === void 0) return "";
		return getSelectionText(textDocument, selections);
	}
	#getSelectionClipboardTexts() {
		const textDocument = this.#editSession?.document;
		const selections = this.#selections;
		if (textDocument === void 0 || selections === void 0) return [];
		return getSelectionClipboardTexts(textDocument, selections);
	}
	/** Writes both the portable text and the selection pairing metadata. */
	#writeSelectionClipboardData(clipboardData, text, selectionTexts) {
		if (clipboardData === null) return;
		clipboardData.setData("text", text);
		if (selectionTexts.length > 1) clipboardData.setData(MULTI_SELECTION_CLIPBOARD_TYPE, JSON.stringify(selectionTexts));
	}
	#cutSelectionText() {
		const textDocument = this.#editSession?.document;
		const selections = this.#selections;
		if (textDocument === void 0 || selections === void 0 || selections.length === 0) return "";
		if (selections.some((selection) => isCollapsedSelection(selection))) {
			const cut = resolveSelectionCut(textDocument, selections);
			this.#applySelectionCutEdits(cut.edits, cut.nextSelectionOffsets);
			return cut.text;
		}
		const text = getSelectionText(textDocument, selections);
		this.#replaceSelectionText("", void 0, true);
		return text;
	}
	#applySelectionCutEdits(edits, nextSelectionOffsets) {
		const textDocument = this.#editSession?.document;
		const selections = this.#selections;
		if (textDocument === void 0 || selections === void 0 || edits.length === 0) return;
		const change = textDocument.applyResolvedEdits(edits, true, selections, void 0, true);
		if (change === void 0) return;
		const nextSelections = nextSelectionOffsets.map((offset) => {
			const caret = textDocument.positionAt(offset);
			return {
				start: caret,
				end: caret,
				direction: 0
			};
		});
		textDocument.setLastUndoSelectionsAfter(nextSelections);
		this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
	}
	#replaceSelectionText(text, selections = this.#selections, undoBoundary = false, textOrder = "selection") {
		if (selections === void 0) return;
		const textDocument = this.#editSession?.document;
		const primarySelection = selections.at(-1);
		if (textDocument === void 0 || primarySelection === void 0) return;
		const { nextSelections, change } = Array.isArray(text) && text.length === selections.length ? applyTextReplaceToSelections(textDocument, selections, text, this.#lineAnnotations, undoBoundary, textOrder) : applyTextChangeToSelections(textDocument, selections, {
			start: textDocument.offsetAt(primarySelection.start),
			end: textDocument.offsetAt(primarySelection.end),
			text: Array.isArray(text) ? text.join(textDocument.eol) : text
		}, this.#lineAnnotations, void 0, undoBoundary);
		if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
	}
	#deleteSelectionText(forward = false) {
		const selections = this.#selections;
		const textDocument = this.#editSession?.document;
		if (selections === void 0 || textDocument === void 0) return;
		const { nextSelections, change } = applyDeleteCharacterToSelections(textDocument, selections, forward, this.#lineAnnotations, this.#metrics.tabSize);
		if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
	}
	#deleteSoftLineBackward() {
		const selections = this.#selections;
		const textDocument = this.#editSession?.document;
		if (selections === void 0 || textDocument === void 0) return;
		const { nextSelections, change } = applyDeleteSoftLineBackwardToSelections(textDocument, selections, this.#isWrap ? (line, character) => {
			const wrapOffsets = this.#wrapLineTextOrWholeLine(line);
			for (let w = 0; w + 1 < wrapOffsets.length; w++) {
				const segmentStart = wrapOffsets[w];
				const segmentEnd = wrapOffsets[w + 1];
				if (character >= segmentStart && character <= segmentEnd) return segmentStart;
			}
			return 0;
		} : void 0, this.#lineAnnotations);
		if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
	}
	#deleteWordBackward() {
		const selections = this.#selections;
		const textDocument = this.#editSession?.document;
		if (selections === void 0 || textDocument === void 0) return;
		const { nextSelections, change } = applyDeleteWordBackwardToSelections(textDocument, selections, this.#lineAnnotations);
		if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
	}
	#deleteHardLineForward() {
		const selections = this.#selections;
		const textDocument = this.#editSession?.document;
		if (selections === void 0 || textDocument === void 0) return;
		const { nextSelections, change } = applyDeleteHardLineForwardToSelections(textDocument, selections, this.#lineAnnotations);
		if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
	}
	#insertTranspose() {
		const selections = this.#selections;
		const textDocument = this.#editSession?.document;
		if (selections === void 0 || textDocument === void 0) return;
		const { nextSelections, change } = applyTransposeToSelections(textDocument, selections, this.#lineAnnotations);
		if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
	}
	#applyChange(change, newSelections, newLineAnnotations, options) {
		if (this.#editPrediction != null) this.#cancelEditPrediction(true);
		const textDocument = this.#editSession?.document;
		if (textDocument !== void 0 && this.#carets !== void 0) for (const trackedCaret of this.#carets) {
			trackedCaret.anchorOffset = remapOffsetThroughEdits(trackedCaret.anchorOffset, change.changes);
			trackedCaret.focusOffset = remapOffsetThroughEdits(trackedCaret.focusOffset, change.changes);
			trackedCaret.caret.anchor = textDocument.positionAt(trackedCaret.anchorOffset);
			trackedCaret.caret.focus = textDocument.positionAt(trackedCaret.focusOffset);
		}
		if (change.lineDelta !== 0 || this.#isWrap) {
			for (const line of this.#lineYCache.keys()) if (line >= change.startLine) this.#lineYCache.delete(line);
		}
		if (this.#wrapLineOffsetsCache.size > 0) {
			if (change.lineDelta === 0 && (change.changedLineRanges.length === 1 || change.changedLineChanges?.every(([, , editLineDelta]) => editLineDelta === 0) === true)) for (const [rangeStart, rangeEnd] of change.changedLineRanges) for (let line = rangeStart; line <= rangeEnd; line++) this.#wrapLineOffsetsCache.delete(line);
			else for (const line of this.#wrapLineOffsetsCache.keys()) if (line >= change.startLine) this.#wrapLineOffsetsCache.delete(line);
		}
		this.#lastAccessedCharX = void 0;
		let renderRange = this.#renderRange;
		let shouldUpdateBuffer;
		if (renderRange !== void 0 && newSelections !== void 0 && newSelections.length > 0) {
			const primarySelection = newSelections.at(-1);
			const renderRangeEndLine = renderRange.startingLine + renderRange.totalLines;
			if (primarySelection.end.line >= renderRangeEndLine) {
				const widenedTotalLines = primarySelection.end.line - renderRange.startingLine + 1;
				const maxWidenLines = this.#viewportWindowLines === void 0 || this.#viewportWindowLines === Infinity ? Infinity : this.#viewportWindowLines * MAX_EDIT_WIDEN_WINDOW_MULTIPLE;
				if (change.startLine <= renderRangeEndLine && widenedTotalLines <= maxWidenLines && this.#isLineRenderable(primarySelection.end.line)) {
					if (primarySelection.end.line > renderRangeEndLine) shouldUpdateBuffer = true;
					renderRange = {
						...renderRange,
						totalLines: widenedTotalLines
					};
					this.#renderRange = renderRange;
				} else shouldUpdateBuffer = true;
			}
		}
		this.#rerender(change, newLineAnnotations, renderRange, shouldUpdateBuffer);
		if (newSelections != null) this.#updateSelections(newSelections);
		this.#fileInstance?.__acknowledgeDocumentUpdate();
		this.#checkpointEditSessionState();
		this.#recordEditPredictionHistory(change, options?.editSource ?? "user");
		this.#scheduleEditPrediction();
		this.#emitChange(change.changes, newLineAnnotations ?? this.#lineAnnotations);
		if (options?.skipSearchRefresh !== true && this.#searchPanel !== void 0 && this.#matches !== void 0) this.#searchPanel.updateMatches({ syncSelection: false });
		if (newSelections != null) {
			if (options?.skipFocus !== true) {
				const revealTarget = newSelections.at(-1);
				if (revealTarget !== void 0) this.#revealLineIfCollapsed(getCaretPosition(revealTarget).line);
				if (this.#primaryCaretElement !== void 0) requestAnimationFrame(() => {
					this.#primaryCaretElement?.scrollIntoView({
						block: "nearest",
						inline: "nearest"
					});
				});
				else if (newSelections.length > 0) {
					const pos = getCaretPosition(newSelections.at(-1));
					this.#scrollToLine(pos.line, pos.character);
				}
				this.focus({ preventScroll: true });
			}
		}
	}
	#emitChange(changes, lineAnnotations) {
		const file = this.getFile();
		const publishChange = this.#publishChange;
		if (file == null || publishChange == null) return;
		publishChange(changes, file, lineAnnotations);
	}
	#applyChangeToLineAnnotations(change) {
		if (this.#lineAnnotations != null) {
			const nextLineAnnotations = applyDocumentChangeToLineAnnotations(change, this.#lineAnnotations);
			if (nextLineAnnotations != null) {
				this.#editSession?.document?.setLastUndoLineAnnotations(this.#lineAnnotations, nextLineAnnotations);
				return nextLineAnnotations;
			}
		}
	}
	#getRenderedEditableLineRanges() {
		const contentElement = this.#contentElement;
		if (contentElement === void 0) return [];
		const ranges = [];
		for (const child of contentElement.children) {
			const el = child;
			const lineType = el.dataset.lineType;
			const lineNumber = getLineNumberAttr(el);
			if (lineNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) continue;
			const line = lineNumber - 1;
			const current = ranges.at(-1);
			if (current !== void 0 && line <= current[1] + 1) current[1] = Math.max(current[1], line);
			else ranges.push([line, line]);
		}
		return ranges;
	}
	#isLineInRenderRange(line) {
		const renderRange = this.#renderRange;
		return renderRange == null || line >= renderRange.startingLine && line < renderRange.startingLine + renderRange.totalLines;
	}
	#getLineElement(line) {
		let lineElement = this.#lineElementsCache.get(line);
		if (lineElement !== void 0) return lineElement ?? void 0;
		const renderRange = this.#renderRange;
		if (!this.#isLineInRenderRange(line)) return;
		const contentElement = this.#contentElement;
		if (contentElement === void 0) return;
		if (renderRange !== void 0) {
			const { startingLine } = renderRange;
			const { children } = contentElement;
			for (let i = line - startingLine; i <= children.length; i++) {
				const child = children[i];
				if (child === void 0) break;
				const lineNumber = getLineNumberAttr(child);
				const lineType = child.dataset.lineType;
				if (lineNumber !== void 0 && lineNumber === line + 1 && lineType !== void 0 && isLineEditable(lineType)) {
					lineElement = child;
					break;
				}
			}
		}
		lineElement ??= contentElement.querySelector(`[data-line="${line + 1}"]` + (this.#diffSyle === "unified" ? ":not([data-line-type=\"change-deletion\"])" : ""));
		if (lineElement !== void 0) this.#lineElementsCache.set(line, lineElement);
		return lineElement ?? void 0;
	}
	#getGutterWidth() {
		if (this.#gutterElement === void 0) return 0;
		if (this.#gutterWidthCache === void 0) {
			const diffsColumnNumberWidth = this.#contentElement?.parentElement?.style.getPropertyValue("--diffs-column-number-width");
			if (diffsColumnNumberWidth !== void 0 && diffsColumnNumberWidth.length > 2 && diffsColumnNumberWidth.endsWith("px")) this.#gutterWidthCache = parseInt(diffsColumnNumberWidth.slice(0, -2), 10);
			else this.#gutterWidthCache = this.#gutterElement.offsetWidth;
		}
		return this.#gutterWidthCache;
	}
	#getContentWidth() {
		if (this.#contentElement === void 0) return 0;
		if (this.#contentWidthCache === void 0) {
			const diffsColumnContentWidth = this.#contentElement.parentElement?.style.getPropertyValue("--diffs-column-content-width");
			if (diffsColumnContentWidth !== void 0 && diffsColumnContentWidth.length > 2 && diffsColumnContentWidth.endsWith("px")) this.#contentWidthCache = parseFloat(diffsColumnContentWidth.slice(0, -2));
			else this.#contentWidthCache = this.#contentElement.offsetWidth;
		}
		return this.#contentWidthCache;
	}
	#getLineY(line) {
		const cachedY = this.#lineYCache.get(line);
		if (cachedY !== void 0) return cachedY;
		const lineElement = this.#getLineElement(line);
		if (lineElement === void 0) return -1;
		let y = lineElement.offsetTop + this.#metrics.paddingTop;
		y += this.#activeContentOffset?.top ?? 0;
		this.#lineYCache.set(line, y);
		return y;
	}
	#getCharX(line, char) {
		if (this.#lastAccessedCharX !== void 0 && this.#lastAccessedCharX[0] === line && this.#lastAccessedCharX[1] === char) return [this.#lastAccessedCharX[2], this.#lastAccessedCharX[3]];
		const lineText = this.#editSession?.document?.getLineText(line);
		const offsetLeft = this.#getGutterWidth() + this.#metrics.ch;
		if (lineText === void 0 || lineText.length === 0 || char <= 0) return [offsetLeft + (this.#activeContentOffset?.left ?? 0), 0];
		const boundedCharacter = snapTextOffsetToUnicodeBoundary(lineText, Math.min(char, lineText.length));
		const textBeforeCharacter = lineText.slice(0, boundedCharacter);
		const asciiColumns = getExpandedAsciiTextColumns(textBeforeCharacter, this.#metrics.tabSize);
		let left = 0;
		let wrapLine = 0;
		if (asciiColumns !== -1) left = offsetLeft + asciiColumns * this.#metrics.ch;
		else left = offsetLeft + this.#metrics.measureTextWidth(textBeforeCharacter);
		if (this.#isWrap) {
			const contentWidth = this.#getContentWidth();
			if (2 * this.#metrics.ch + this.#metrics.measureTextWidth(lineText) > contentWidth) {
				const wrapOffsets = this.#wrapLineText(line);
				if (wrapOffsets == null) return [left + (this.#activeContentOffset?.left ?? 0), 0];
				for (let w = 0; w + 1 < wrapOffsets.length; w++) {
					const segmentStart = wrapOffsets[w];
					if (boundedCharacter <= wrapOffsets[w + 1]) {
						wrapLine = w;
						const prefixInSegment = lineText.slice(segmentStart, boundedCharacter);
						const segmentAsciiColumns = getExpandedAsciiTextColumns(prefixInSegment, this.#metrics.tabSize);
						if (segmentAsciiColumns !== -1) left = offsetLeft + segmentAsciiColumns * this.#metrics.ch;
						else left = offsetLeft + this.#metrics.measureTextWidth(prefixInSegment);
						break;
					}
				}
			}
			left += this.#activeContentOffset?.left ?? 0;
		}
		if (this.#lastAccessedCharX !== void 0) {
			this.#lastAccessedCharX[0] = line;
			this.#lastAccessedCharX[1] = char;
			this.#lastAccessedCharX[2] = left;
			this.#lastAccessedCharX[3] = wrapLine;
		} else this.#lastAccessedCharX = [
			line,
			char,
			left,
			wrapLine
		];
		return [left, wrapLine];
	}
	#wrapLineText(line) {
		const cachedOffsets = this.#wrapLineOffsetsCache.get(line);
		if (cachedOffsets !== void 0) return cachedOffsets;
		if (this.#wrapLineOffsetsCache.size >= MAX_WRAP_OFFSETS_CACHE_LINES) this.#wrapLineOffsetsCache.clear();
		const lineText = this.#editSession?.document?.getLineText(line);
		if (lineText === void 0 || lineText.length === 0) {
			const offsets = new Uint32Array([0]);
			this.#wrapLineOffsetsCache.set(line, offsets);
			return offsets;
		}
		const contentElement = this.#contentElement;
		if (contentElement == null || !contentElement.isConnected) return;
		const div = h("div", {
			style: {
				position: "absolute",
				top: "0",
				left: "0",
				width: "100%",
				boxSizing: "border-box",
				visibility: "hidden",
				pointerEvents: "none",
				whiteSpace: "pre-wrap",
				wordBreak: "break-word",
				font: "inherit",
				paddingInline: "1ch",
				tabSize: this.#metrics.tabSize.toString()
			},
			textContent: lineText
		}, contentElement);
		const textNode = div.firstChild;
		const range = document.createRange();
		const starts = [];
		try {
			const divRect = div.getBoundingClientRect();
			if (divRect.width === 0) return;
			const unicodeOffsets = getUnicodeMeasurementOffsets(lineText);
			const wrapLineStartLeft = divRect.left + this.#metrics.ch;
			const graphemeCount = unicodeOffsets === void 0 ? lineText.length : unicodeOffsets.length - 1;
			const graphemeStart = (index) => unicodeOffsets === void 0 ? index : unicodeOffsets[index];
			const measureGrapheme = (index) => {
				range.setStart(textNode, graphemeStart(index));
				range.setEnd(textNode, graphemeStart(index + 1));
				const clientRects = range.getClientRects();
				return clientRects.item(clientRects.length - 1) ?? range.getBoundingClientRect();
			};
			starts.push(0);
			let lastTop = measureGrapheme(0).top;
			let searchStart = 1;
			while (searchStart < graphemeCount) {
				let low = searchStart;
				let high = graphemeCount;
				while (low < high) {
					const mid = low + high >> 1;
					if (measureGrapheme(mid).top > lastTop) high = mid;
					else low = mid + 1;
				}
				if (low >= graphemeCount) break;
				const { left, top } = measureGrapheme(low);
				const startsPastLineStart = isSafari() && left - wrapLineStartLeft > this.#metrics.ch / 2;
				starts.push(graphemeStart(startsPastLineStart ? low - 1 : low));
				lastTop = top;
				searchStart = low + 1;
			}
			const offsets = new Uint32Array(starts.length + 1);
			for (let i = 0; i < starts.length; i++) offsets[i] = starts[i];
			offsets[starts.length] = lineText.length;
			this.#wrapLineOffsetsCache.set(line, offsets);
			return offsets;
		} finally {
			div.remove();
		}
	}
	#wrapLineTextOrWholeLine(line) {
		const offsets = this.#wrapLineText(line);
		if (offsets !== void 0) return offsets;
		const length = this.#editSession?.document?.getLineText(line)?.length ?? 0;
		return Uint32Array.of(0, length);
	}
	#rangeBelongsToEditor({ startContainer, endContainer }) {
		const contentEl = this.#contentElement;
		if (contentEl === void 0) return false;
		return contentEl.contains(startContainer) && contentEl.contains(endContainer);
	}
	#isLineVisible(line) {
		const lineCount = this.#editSession?.document?.lineCount ?? 0;
		if (line < 0 || line >= lineCount) return false;
		return this.#getLineElement(line) !== void 0;
	}
};
function getEditSession({ type, editStateKey, owner, initialState, previousSession }) {
	if (initialState != null && initialState.type !== type) throw new TypeError(`Editor: initialState: a ${initialState.type} state cannot initialize a ${type} editor`);
	const initialSession = initialState;
	if (editStateKey != null) return EditStateManager.activate(type, editStateKey, owner, initialSession);
	if (initialSession != null) return initialSession;
	if (previousSession != null) return previousSession;
	return type === "file" ? { type: "file" } : { type: "file-diff" };
}
function gridTrackSpan(child) {
	if (!(child instanceof HTMLElement)) return 1;
	return Number(child.dataset.bufferSize ?? 1);
}
function isValidEditPredictionPosition(document, position) {
	return position !== void 0 && Number.isInteger(position.line) && Number.isInteger(position.character) && position.line >= 0 && position.line < document.lineCount && position.character >= 0 && position.character <= document.getLineLength(position.line);
}
function splitsSurrogatePair(text, offset) {
	const previous = text.charCodeAt(offset - 1);
	const next = text.charCodeAt(offset);
	return offset > 0 && offset < text.length && previous >= 55296 && previous <= 56319 && next >= 56320 && next <= 57343;
}
//#endregion
export { Editor };

//# sourceMappingURL=editor.js.map