@pierre/diffs 1.2.12 → 1.3.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/dist/components/CodeView.d.ts +91 -6
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +300 -38
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +14 -3
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +98 -12
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +68 -19
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +363 -25
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/FileStream.d.ts.map +1 -1
  14. package/dist/components/FileStream.js +2 -1
  15. package/dist/components/FileStream.js.map +1 -1
  16. package/dist/components/UnresolvedFile.d.ts +2 -2
  17. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  18. package/dist/components/UnresolvedFile.js +2 -2
  19. package/dist/components/UnresolvedFile.js.map +1 -1
  20. package/dist/components/VirtualizedFile.d.ts +4 -2
  21. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  22. package/dist/components/VirtualizedFile.js +59 -47
  23. package/dist/components/VirtualizedFile.js.map +1 -1
  24. package/dist/components/VirtualizedFileDiff.d.ts +27 -5
  25. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  26. package/dist/components/VirtualizedFileDiff.js +262 -56
  27. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  28. package/dist/components/Virtualizer.d.ts +4 -1
  29. package/dist/components/Virtualizer.d.ts.map +1 -1
  30. package/dist/components/Virtualizer.js +21 -4
  31. package/dist/components/Virtualizer.js.map +1 -1
  32. package/dist/constants.d.ts +4 -1
  33. package/dist/constants.d.ts.map +1 -1
  34. package/dist/constants.js +4 -1
  35. package/dist/constants.js.map +1 -1
  36. package/dist/editor/command.d.ts +7 -0
  37. package/dist/editor/command.d.ts.map +1 -0
  38. package/dist/editor/command.js +54 -0
  39. package/dist/editor/command.js.map +1 -0
  40. package/dist/editor/editStack.d.ts +71 -0
  41. package/dist/editor/editStack.d.ts.map +1 -0
  42. package/dist/editor/editStack.js +218 -0
  43. package/dist/editor/editStack.js.map +1 -0
  44. package/dist/editor/editor.d.ts +105 -0
  45. package/dist/editor/editor.d.ts.map +1 -0
  46. package/dist/editor/editor.js +2545 -0
  47. package/dist/editor/editor.js.map +1 -0
  48. package/dist/editor/editor2.js +6 -0
  49. package/dist/editor/editor2.js.map +1 -0
  50. package/dist/editor/index.d.ts +5 -0
  51. package/dist/editor/index.js +3 -0
  52. package/dist/editor/languages.d.ts +26 -0
  53. package/dist/editor/languages.d.ts.map +1 -0
  54. package/dist/editor/languages.js +363 -0
  55. package/dist/editor/languages.js.map +1 -0
  56. package/dist/editor/lineAnnotations.d.ts +9 -0
  57. package/dist/editor/lineAnnotations.d.ts.map +1 -0
  58. package/dist/editor/lineAnnotations.js +212 -0
  59. package/dist/editor/lineAnnotations.js.map +1 -0
  60. package/dist/editor/marker.d.ts +38 -0
  61. package/dist/editor/marker.d.ts.map +1 -0
  62. package/dist/editor/marker.js +245 -0
  63. package/dist/editor/marker.js.map +1 -0
  64. package/dist/editor/matchBrackets.d.ts +9 -0
  65. package/dist/editor/matchBrackets.d.ts.map +1 -0
  66. package/dist/editor/matchBrackets.js +138 -0
  67. package/dist/editor/matchBrackets.js.map +1 -0
  68. package/dist/editor/pieceTable.d.ts +41 -0
  69. package/dist/editor/pieceTable.d.ts.map +1 -0
  70. package/dist/editor/pieceTable.js +671 -0
  71. package/dist/editor/pieceTable.js.map +1 -0
  72. package/dist/editor/platform.d.ts +19 -0
  73. package/dist/editor/platform.d.ts.map +1 -0
  74. package/dist/editor/platform.js +57 -0
  75. package/dist/editor/platform.js.map +1 -0
  76. package/dist/editor/popover.d.ts +65 -0
  77. package/dist/editor/popover.d.ts.map +1 -0
  78. package/dist/editor/popover.js +187 -0
  79. package/dist/editor/popover.js.map +1 -0
  80. package/dist/editor/searchPanel.d.ts +40 -0
  81. package/dist/editor/searchPanel.d.ts.map +1 -0
  82. package/dist/editor/searchPanel.js +318 -0
  83. package/dist/editor/searchPanel.js.map +1 -0
  84. package/dist/editor/selection.d.ts +211 -0
  85. package/dist/editor/selection.d.ts.map +1 -0
  86. package/dist/editor/selection.js +1626 -0
  87. package/dist/editor/selection.js.map +1 -0
  88. package/dist/editor/selectionAction.d.ts +46 -0
  89. package/dist/editor/selectionAction.d.ts.map +1 -0
  90. package/dist/editor/selectionAction.js +64 -0
  91. package/dist/editor/selectionAction.js.map +1 -0
  92. package/dist/editor/sprite.d.ts +8 -0
  93. package/dist/editor/sprite.d.ts.map +1 -0
  94. package/dist/editor/sprite.js +45 -0
  95. package/dist/editor/sprite.js.map +1 -0
  96. package/dist/editor/stateStorage.d.ts +15 -0
  97. package/dist/editor/stateStorage.d.ts.map +1 -0
  98. package/dist/editor/stateStorage.js +65 -0
  99. package/dist/editor/stateStorage.js.map +1 -0
  100. package/dist/editor/textDocument.d.ts +77 -0
  101. package/dist/editor/textDocument.d.ts.map +1 -0
  102. package/dist/editor/textDocument.js +271 -0
  103. package/dist/editor/textDocument.js.map +1 -0
  104. package/dist/editor/textMeasure.d.ts +61 -0
  105. package/dist/editor/textMeasure.d.ts.map +1 -0
  106. package/dist/editor/textMeasure.js +209 -0
  107. package/dist/editor/textMeasure.js.map +1 -0
  108. package/dist/editor/tokenizer.d.ts +41 -0
  109. package/dist/editor/tokenizer.d.ts.map +1 -0
  110. package/dist/editor/tokenizer.js +513 -0
  111. package/dist/editor/tokenizer.js.map +1 -0
  112. package/dist/editor/utils.d.ts +21 -0
  113. package/dist/editor/utils.d.ts.map +1 -0
  114. package/dist/editor/utils.js +68 -0
  115. package/dist/editor/utils.js.map +1 -0
  116. package/dist/index.d.ts +10 -8
  117. package/dist/index.js +12 -10
  118. package/dist/managers/InteractionManager.d.ts +7 -0
  119. package/dist/managers/InteractionManager.d.ts.map +1 -1
  120. package/dist/managers/InteractionManager.js +22 -1
  121. package/dist/managers/InteractionManager.js.map +1 -1
  122. package/dist/managers/ResizeManager.d.ts +10 -2
  123. package/dist/managers/ResizeManager.d.ts.map +1 -1
  124. package/dist/managers/ResizeManager.js +33 -13
  125. package/dist/managers/ResizeManager.js.map +1 -1
  126. package/dist/managers/UniversalRenderingManager.d.ts +2 -1
  127. package/dist/managers/UniversalRenderingManager.d.ts.map +1 -1
  128. package/dist/managers/UniversalRenderingManager.js +6 -1
  129. package/dist/managers/UniversalRenderingManager.js.map +1 -1
  130. package/dist/react/CodeView.d.ts +26 -1
  131. package/dist/react/CodeView.d.ts.map +1 -1
  132. package/dist/react/CodeView.js +82 -19
  133. package/dist/react/CodeView.js.map +1 -1
  134. package/dist/react/EditContext.d.ts +15 -0
  135. package/dist/react/EditContext.d.ts.map +1 -0
  136. package/dist/react/EditContext.js +23 -0
  137. package/dist/react/EditContext.js.map +1 -0
  138. package/dist/react/File.d.ts +3 -1
  139. package/dist/react/File.d.ts.map +1 -1
  140. package/dist/react/File.js +4 -2
  141. package/dist/react/File.js.map +1 -1
  142. package/dist/react/FileDiff.d.ts +4 -1
  143. package/dist/react/FileDiff.d.ts.map +1 -1
  144. package/dist/react/FileDiff.js +4 -2
  145. package/dist/react/FileDiff.js.map +1 -1
  146. package/dist/react/MultiFileDiff.d.ts +7 -5
  147. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  148. package/dist/react/MultiFileDiff.js +4 -2
  149. package/dist/react/MultiFileDiff.js.map +1 -1
  150. package/dist/react/PatchDiff.d.ts +4 -1
  151. package/dist/react/PatchDiff.d.ts.map +1 -1
  152. package/dist/react/PatchDiff.js +4 -2
  153. package/dist/react/PatchDiff.js.map +1 -1
  154. package/dist/react/UnresolvedFile.d.ts +1 -0
  155. package/dist/react/UnresolvedFile.d.ts.map +1 -1
  156. package/dist/react/UnresolvedFile.js +2 -1
  157. package/dist/react/UnresolvedFile.js.map +1 -1
  158. package/dist/react/index.d.ts +3 -2
  159. package/dist/react/index.js +2 -1
  160. package/dist/react/types.d.ts +3 -0
  161. package/dist/react/types.d.ts.map +1 -1
  162. package/dist/react/utils/renderDiffChildren.d.ts +2 -0
  163. package/dist/react/utils/renderDiffChildren.d.ts.map +1 -1
  164. package/dist/react/utils/renderDiffChildren.js +16 -8
  165. package/dist/react/utils/renderDiffChildren.js.map +1 -1
  166. package/dist/react/utils/renderFileChildren.d.ts +2 -0
  167. package/dist/react/utils/renderFileChildren.d.ts.map +1 -1
  168. package/dist/react/utils/renderFileChildren.js +16 -8
  169. package/dist/react/utils/renderFileChildren.js.map +1 -1
  170. package/dist/react/utils/useFileDiffInstance.d.ts +3 -1
  171. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  172. package/dist/react/utils/useFileDiffInstance.js +30 -6
  173. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  174. package/dist/react/utils/useFileInstance.d.ts +4 -1
  175. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  176. package/dist/react/utils/useFileInstance.js +30 -5
  177. package/dist/react/utils/useFileInstance.js.map +1 -1
  178. package/dist/renderers/DiffHunksRenderer.d.ts +28 -2
  179. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  180. package/dist/renderers/DiffHunksRenderer.js +244 -22
  181. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  182. package/dist/renderers/FileRenderer.d.ts +8 -1
  183. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  184. package/dist/renderers/FileRenderer.js +155 -45
  185. package/dist/renderers/FileRenderer.js.map +1 -1
  186. package/dist/ssr/index.d.ts +2 -2
  187. package/dist/ssr/preloadDiffs.d.ts +11 -10
  188. package/dist/ssr/preloadDiffs.d.ts.map +1 -1
  189. package/dist/ssr/preloadDiffs.js +15 -7
  190. package/dist/ssr/preloadDiffs.js.map +1 -1
  191. package/dist/style.js +1 -1
  192. package/dist/types.d.ts +282 -5
  193. package/dist/types.d.ts.map +1 -1
  194. package/dist/utils/applyLineTextWithNewline.d.ts +5 -0
  195. package/dist/utils/applyLineTextWithNewline.d.ts.map +1 -0
  196. package/dist/utils/applyLineTextWithNewline.js +11 -0
  197. package/dist/utils/applyLineTextWithNewline.js.map +1 -0
  198. package/dist/utils/areHunkDataEqual.js +1 -1
  199. package/dist/utils/areHunkDataEqual.js.map +1 -1
  200. package/dist/utils/areManagedSnapshotsEqual.d.ts +2 -2
  201. package/dist/utils/areManagedSnapshotsEqual.d.ts.map +1 -1
  202. package/dist/utils/areManagedSnapshotsEqual.js +5 -0
  203. package/dist/utils/areManagedSnapshotsEqual.js.map +1 -1
  204. package/dist/utils/awaitWithTimeout.d.ts +5 -0
  205. package/dist/utils/awaitWithTimeout.d.ts.map +1 -0
  206. package/dist/utils/awaitWithTimeout.js +15 -0
  207. package/dist/utils/awaitWithTimeout.js.map +1 -0
  208. package/dist/utils/cleanLastNewline.js +6 -1
  209. package/dist/utils/cleanLastNewline.js.map +1 -1
  210. package/dist/utils/cloneFileDiffMetadata.d.ts +7 -0
  211. package/dist/utils/cloneFileDiffMetadata.d.ts.map +1 -0
  212. package/dist/utils/cloneFileDiffMetadata.js +16 -0
  213. package/dist/utils/cloneFileDiffMetadata.js.map +1 -0
  214. package/dist/utils/computeEstimatedDiffHeights.d.ts +3 -1
  215. package/dist/utils/computeEstimatedDiffHeights.d.ts.map +1 -1
  216. package/dist/utils/computeEstimatedDiffHeights.js +8 -1
  217. package/dist/utils/computeEstimatedDiffHeights.js.map +1 -1
  218. package/dist/utils/computeFileOffsets.d.ts +21 -0
  219. package/dist/utils/computeFileOffsets.d.ts.map +1 -0
  220. package/dist/utils/computeFileOffsets.js +51 -0
  221. package/dist/utils/computeFileOffsets.js.map +1 -0
  222. package/dist/utils/createCodeViewHeaderFooterHostElement.d.ts +5 -0
  223. package/dist/utils/createCodeViewHeaderFooterHostElement.d.ts.map +1 -0
  224. package/dist/utils/createCodeViewHeaderFooterHostElement.js +19 -0
  225. package/dist/utils/createCodeViewHeaderFooterHostElement.js.map +1 -0
  226. package/dist/utils/createFileHeaderElement.d.ts.map +1 -1
  227. package/dist/utils/createFileHeaderElement.js +5 -1
  228. package/dist/utils/createFileHeaderElement.js.map +1 -1
  229. package/dist/utils/createTransformerWithState.js +9 -0
  230. package/dist/utils/createTransformerWithState.js.map +1 -1
  231. package/dist/utils/editSessionHunks.d.ts +84 -0
  232. package/dist/utils/editSessionHunks.d.ts.map +1 -0
  233. package/dist/utils/editSessionHunks.js +366 -0
  234. package/dist/utils/editSessionHunks.js.map +1 -0
  235. package/dist/utils/getDiffFileInput.d.ts +14 -0
  236. package/dist/utils/getDiffFileInput.d.ts.map +1 -0
  237. package/dist/utils/getDiffFileInput.js +24 -0
  238. package/dist/utils/getDiffFileInput.js.map +1 -0
  239. package/dist/utils/getDiffHunksRendererOptions.js +1 -0
  240. package/dist/utils/getDiffHunksRendererOptions.js.map +1 -1
  241. package/dist/utils/getFiletypeFromFileName.d.ts.map +1 -1
  242. package/dist/utils/getFiletypeFromFileName.js +2 -0
  243. package/dist/utils/getFiletypeFromFileName.js.map +1 -1
  244. package/dist/utils/hydratePartialDiff.d.ts +10 -0
  245. package/dist/utils/hydratePartialDiff.d.ts.map +1 -0
  246. package/dist/utils/hydratePartialDiff.js +140 -0
  247. package/dist/utils/hydratePartialDiff.js.map +1 -0
  248. package/dist/utils/iterateOverDiff.js +3 -3
  249. package/dist/utils/iterateOverDiff.js.map +1 -1
  250. package/dist/utils/parseDiffFromFile.d.ts +1 -1
  251. package/dist/utils/parseDiffFromFile.d.ts.map +1 -1
  252. package/dist/utils/parseDiffFromFile.js +26 -5
  253. package/dist/utils/parseDiffFromFile.js.map +1 -1
  254. package/dist/utils/parsePatchFiles.d.ts.map +1 -1
  255. package/dist/utils/parsePatchFiles.js +2 -0
  256. package/dist/utils/parsePatchFiles.js.map +1 -1
  257. package/dist/utils/realignChangeContent.d.ts +28 -0
  258. package/dist/utils/realignChangeContent.d.ts.map +1 -0
  259. package/dist/utils/realignChangeContent.js +142 -0
  260. package/dist/utils/realignChangeContent.js.map +1 -0
  261. package/dist/utils/renderDiffWithHighlighter.js +1 -1
  262. package/dist/utils/renderFileWithHighlighter.d.ts.map +1 -1
  263. package/dist/utils/renderFileWithHighlighter.js +5 -14
  264. package/dist/utils/renderFileWithHighlighter.js.map +1 -1
  265. package/dist/utils/updateDiffHunks.d.ts +24 -0
  266. package/dist/utils/updateDiffHunks.d.ts.map +1 -0
  267. package/dist/utils/updateDiffHunks.js +235 -0
  268. package/dist/utils/updateDiffHunks.js.map +1 -0
  269. package/dist/utils/virtualDiffLayout.d.ts +40 -1
  270. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  271. package/dist/utils/virtualDiffLayout.js +96 -1
  272. package/dist/utils/virtualDiffLayout.js.map +1 -1
  273. package/dist/worker/WorkerPoolManager.d.ts +7 -2
  274. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  275. package/dist/worker/WorkerPoolManager.js +83 -18
  276. package/dist/worker/WorkerPoolManager.js.map +1 -1
  277. package/dist/worker/index.d.ts +2 -2
  278. package/dist/worker/types.d.ts +11 -1
  279. package/dist/worker/types.d.ts.map +1 -1
  280. package/dist/worker/worker-portable.js +46 -66
  281. package/dist/worker/worker-portable.js.map +1 -1
  282. package/dist/worker/worker.js +46 -66
  283. package/dist/worker/worker.js.map +1 -1
  284. package/package.json +12 -5
  285. package/dist/utils/iterateOverFile.d.ts +0 -50
  286. package/dist/utils/iterateOverFile.d.ts.map +0 -1
  287. package/dist/utils/iterateOverFile.js +0 -49
  288. package/dist/utils/iterateOverFile.js.map +0 -1
@@ -0,0 +1,2545 @@
1
+ import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
2
+ import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
3
+ import { isMoveCursorShortcut, isPrimaryModifier, isSafari } from "./platform.js";
4
+ import { resolveEditorCommandFromKeyboardEvent, resolveFindAgainShortcut } from "./command.js";
5
+ import { EditStack } from "./editStack.js";
6
+ import editor_default from "./editor2.js";
7
+ import { resolveBlockCommentEdits, resolveCommentConfig, resolveLineCommentEdits } from "./languages.js";
8
+ import { addEventListener, clampDomOffset, extend, getLineNumberAttr, h, round } from "./utils.js";
9
+ import { applyDocumentChangeToLineAnnotations, renderLineAnnotations } from "./lineAnnotations.js";
10
+ import { PopoverManager } from "./popover.js";
11
+ import { applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNexMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionClipboardTexts, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapSelectionsAfterEdits, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines } from "./selection.js";
12
+ import { MarkerRenderer, markerSeverityDatasetKey } from "./marker.js";
13
+ import { findBracketMatchRanges } from "./matchBrackets.js";
14
+ import { createSpriteElement } from "./sprite.js";
15
+ import { SearchPanelWidget } from "./searchPanel.js";
16
+ import { SelectionActionWidget } from "./selectionAction.js";
17
+ import { cloneEditorState, createStateStorage } from "./stateStorage.js";
18
+ import { TextDocument } from "./textDocument.js";
19
+ import { Metrics, getExpandedAsciiTextColumns, getUnicodeMeasurementOffsets, snapTextOffsetToUnicodeBoundary } from "./textMeasure.js";
20
+ import { EditorTokenizer, renderLineTokens } from "./tokenizer.js";
21
+ //#region src/editor/editor.ts
22
+ function getShadowRootRange(shadowRoot) {
23
+ const selection = shadowRoot.getSelection?.();
24
+ if (selection == null || selection.rangeCount === 0) return;
25
+ const range = selection.getRangeAt(0);
26
+ return {
27
+ collapsed: range.collapsed,
28
+ startContainer: range.startContainer,
29
+ startOffset: range.startOffset,
30
+ endContainer: range.endContainer,
31
+ endOffset: range.endOffset
32
+ };
33
+ }
34
+ function requirePersistedCacheKey(file) {
35
+ if (typeof file.cacheKey !== "string" || file.cacheKey.length === 0) throw new Error(`Editor persistState requires a non-empty file.cacheKey for "${file.name}". Provide a unique, stable cacheKey for every editable file.`);
36
+ return file.cacheKey;
37
+ }
38
+ function isPromise(value) {
39
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
40
+ }
41
+ const MAX_EDIT_WIDEN_WINDOW_MULTIPLE = 2;
42
+ const SELECTION_ACTION_POPOVER_PLACEMENT_KEY = "selection-action";
43
+ const MULTI_SELECTION_CLIPBOARD_TYPE = "application/vnd.pierre.diffs-selections+json";
44
+ var Editor = class {
45
+ #options;
46
+ #metrics = new Metrics();
47
+ #tokenizer;
48
+ #popoverManager;
49
+ #textDocumentCache = /* @__PURE__ */ new Map();
50
+ #stateStorage;
51
+ #stateStorageOption;
52
+ #pendingStateWrites = /* @__PURE__ */ new Map();
53
+ #pendingStateRestore;
54
+ #stateRestoreGeneration = 0;
55
+ #restoreStateOnNextSync = false;
56
+ #editorEventDisposes;
57
+ #globalEventDisposes;
58
+ #selectEventDisposes;
59
+ #detach;
60
+ #contentOffset;
61
+ #gutterWidthCache;
62
+ #contentWidthCache;
63
+ #lineYCache = /* @__PURE__ */ new Map();
64
+ #wrapLineOffsetsCache = /* @__PURE__ */ new Map();
65
+ #lineElementsCache = /* @__PURE__ */ new Map();
66
+ #lastAccessedCharX;
67
+ #globalStyleElement;
68
+ #editorStyleElement;
69
+ #themeStyleElement;
70
+ #spriteElement;
71
+ #fileContainer;
72
+ #gutterElement;
73
+ #contentElement;
74
+ #overlayElement;
75
+ #overlayElements;
76
+ #primaryCaretElement;
77
+ #resizeObserver;
78
+ #fileInstance;
79
+ #fileInfo;
80
+ #lineAnnotations;
81
+ #textDocument;
82
+ #renderRange;
83
+ #viewportWindowLines;
84
+ #markerRenderer;
85
+ #searchPanel;
86
+ #selectionAction;
87
+ #shouldIgnoreSelectionChange = false;
88
+ #suppressNativeSelectionSync = false;
89
+ #contentHasFocus = false;
90
+ #isComposing = false;
91
+ #isGutterMouseDown = false;
92
+ #isContentMouseDown = false;
93
+ #shiftKeyPressed = false;
94
+ #selectionStart;
95
+ #deletedSelectionText = "";
96
+ #reservedSelections;
97
+ #initSelections;
98
+ #selections;
99
+ #matches;
100
+ #scrollingToLine;
101
+ #scrollingToLineChar;
102
+ #scrollingToLineFixed = false;
103
+ #scrollingToLineNoFocus = false;
104
+ #retainSearchPanelFocus = false;
105
+ #fontRemeasureScheduled = false;
106
+ #themeSelectionRefreshFrame;
107
+ #onDeferTokenize = (lines, themeType) => {
108
+ this.#fileInstance?.updateRenderCache(lines, themeType, false, false);
109
+ if (this.#renderRange !== void 0 && this.#renderRange.totalLines !== Infinity) {
110
+ const { startingLine, totalLines } = this.#renderRange;
111
+ const endLine = Math.min(startingLine + totalLines, this.#textDocument?.lineCount ?? 0);
112
+ for (const [line, tokens] of lines) if (line >= startingLine && line < endLine) {
113
+ const lineElement = this.#getLineElement(line);
114
+ if (lineElement !== void 0) lineElement.replaceChildren(...renderLineTokens(tokens));
115
+ }
116
+ }
117
+ };
118
+ constructor(options = {}) {
119
+ this.#options = options;
120
+ }
121
+ setOptions(options) {
122
+ const previousStorageOption = this.#options.persistStateStorage ?? "inMemory";
123
+ const nextOptions = {
124
+ ...this.#options,
125
+ ...options
126
+ };
127
+ if (nextOptions.persistState === true && this.#fileInstance?.type === "file") {
128
+ const file = this.#fileInstance.__getCurrentFile?.() ?? this.#fileInfo;
129
+ if (file !== void 0) requirePersistedCacheKey(file);
130
+ }
131
+ this.#options = nextOptions;
132
+ if (this.#options.persistState !== true) {
133
+ this.#textDocumentCache.clear();
134
+ this.#stateRestoreGeneration++;
135
+ this.#restoreStateOnNextSync = false;
136
+ }
137
+ if ((this.#options.persistStateStorage ?? "inMemory") !== previousStorageOption) {
138
+ this.#stateRestoreGeneration++;
139
+ this.#stateStorage = void 0;
140
+ this.#stateStorageOption = void 0;
141
+ this.#pendingStateWrites.clear();
142
+ }
143
+ }
144
+ edit(fileInstance) {
145
+ if (this.#options.persistState === true && fileInstance.type === "file") {
146
+ const file = fileInstance.__getCurrentFile?.();
147
+ if (file !== void 0) requirePersistedCacheKey(file);
148
+ }
149
+ const { useTokenTransformer, enableGutterUtility, enableLineSelection, lineHoverHighlight = "disabled", ...rest } = fileInstance.options;
150
+ if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || lineHoverHighlight !== "disabled") {
151
+ fileInstance.setOptions({
152
+ ...rest,
153
+ useTokenTransformer: true,
154
+ enableGutterUtility: false,
155
+ enableLineSelection: false,
156
+ lineHoverHighlight: "disabled"
157
+ });
158
+ fileInstance.rerender();
159
+ }
160
+ this.#fileInstance = fileInstance;
161
+ this.#initialize();
162
+ this.#detach = fileInstance.attachEditor(this);
163
+ return () => this.cleanUp();
164
+ }
165
+ /**
166
+ * Apply edits to current attached file. Every edit joins the undo timeline:
167
+ * a programmatic edit must leave the document and its history exactly as
168
+ * the same edit typed by the user would (history equivalence — see
169
+ * TextDocument.applyResolvedEdits), so it is undoable like any other edit.
170
+ *
171
+ * @param updateHistory Whether to record caller selection snapshots for
172
+ * exact undo/redo restoration. Defaults to true. When false, live selections
173
+ * are remapped during replay and the text edit still joins the undo timeline.
174
+ */
175
+ applyEdits(edits, updateHistory = true) {
176
+ const textDocument = this.#textDocument;
177
+ if (textDocument == null) throw new Error("Editor is not attached");
178
+ const wasFocused = this.#contentHasFocus;
179
+ const selectionsBefore = this.#selections;
180
+ const selectionOffsetsBefore = selectionsBefore?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
181
+ const resolvedEditOffsets = selectionsBefore === void 0 ? void 0 : textDocument.resolveEdits(edits).sort((a, b) => a.start - b.start);
182
+ const change = textDocument.applyEdits(edits, updateHistory, selectionsBefore);
183
+ if (change === void 0) return;
184
+ let nextSelections;
185
+ if (selectionsBefore !== void 0 && selectionOffsetsBefore !== void 0 && resolvedEditOffsets !== void 0) {
186
+ nextSelections = remapSelectionsAfterEdits(textDocument, selectionsBefore, selectionOffsetsBefore, resolvedEditOffsets);
187
+ if (updateHistory) textDocument.setLastUndoSelectionsAfter(nextSelections);
188
+ }
189
+ this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change), { skipFocus: !wasFocused });
190
+ }
191
+ /** Whether there is an edit to undo. */
192
+ get canUndo() {
193
+ return this.#textDocument?.canUndo ?? false;
194
+ }
195
+ /** Whether there is an undone edit to redo. */
196
+ get canRedo() {
197
+ return this.#textDocument?.canRedo ?? false;
198
+ }
199
+ /** Undo the last edit. Does nothing when there is nothing to undo. */
200
+ undo() {
201
+ this.#runCommand("undo");
202
+ }
203
+ /** Redo the last undone edit. Does nothing when there is nothing to redo. */
204
+ redo() {
205
+ this.#runCommand("redo");
206
+ }
207
+ getFile() {
208
+ const fileInfo = this.#fileInfo;
209
+ const textDocument = this.#textDocument;
210
+ if (fileInfo === void 0 || textDocument === void 0) return;
211
+ const file = { ...fileInfo };
212
+ Object.defineProperty(file, "contents", {
213
+ enumerable: true,
214
+ get: () => textDocument.getText()
215
+ });
216
+ return file;
217
+ }
218
+ getText() {
219
+ return this.#textDocument?.getText() ?? "";
220
+ }
221
+ getState() {
222
+ const scrollContainer = this.#fileInstance?.getScrollContainer?.();
223
+ return {
224
+ selections: this.#selections,
225
+ view: scrollContainer !== void 0 ? {
226
+ scrollLeft: scrollContainer.scrollLeft,
227
+ scrollTop: scrollContainer.scrollTop
228
+ } : void 0
229
+ };
230
+ }
231
+ setState({ selections, view }) {
232
+ if (this.#fileInstance === void 0 || this.#textDocument === void 0) throw new Error("Editor is not attached");
233
+ this.#updateSelections(selections ?? []);
234
+ if (view !== void 0) {
235
+ const scrollContainer = this.#fileInstance.getScrollContainer?.();
236
+ if (scrollContainer !== void 0) scrollContainer.scrollTo({
237
+ left: view.scrollLeft,
238
+ top: view.scrollTop,
239
+ behavior: "instant"
240
+ });
241
+ return;
242
+ }
243
+ this.#scrollToPrimaryCaret();
244
+ }
245
+ setSelections(selections) {
246
+ const textDocument = this.#textDocument;
247
+ if (textDocument === void 0) throw new Error("Text document is not initialized");
248
+ const resolvedSelections = selections.map((selection) => {
249
+ let start = textDocument.normalizePosition(selection.start);
250
+ let end = textDocument.normalizePosition(selection.end);
251
+ let direction = selection.direction === "none" ? 0 : selection.direction === "backward" ? -1 : 1;
252
+ if (comparePosition(start, end) > 0) {
253
+ [start, end] = [end, start];
254
+ if (direction !== 0) direction = direction === 1 ? -1 : 1;
255
+ }
256
+ return {
257
+ direction,
258
+ start,
259
+ end
260
+ };
261
+ });
262
+ this.#updateSelections(resolvedSelections);
263
+ const primarySelection = resolvedSelections.at(-1);
264
+ if (primarySelection !== void 0) this.#revealLineIfCollapsed(getCaretPosition(primarySelection).line);
265
+ this.#scrollToPrimaryCaret();
266
+ }
267
+ setMarkers(markers) {
268
+ const textDocument = this.#textDocument;
269
+ if (textDocument === void 0) throw new Error("Text document is not initialized");
270
+ if (markers.length === 0) {
271
+ this.#markerRenderer?.cleanup();
272
+ this.#markerRenderer = void 0;
273
+ this.#updateSelections(this.#selections ?? []);
274
+ return;
275
+ }
276
+ this.#markerRenderer ??= new MarkerRenderer({
277
+ popoverManager: this.#getPopoverManager(),
278
+ getLineHeight: () => this.#metrics.lineHeight,
279
+ getOverlayElement: () => this.#overlayElement,
280
+ getGutterWidth: () => this.#getGutterWidth(),
281
+ getCharX: (line, character) => this.#getCharX(line, character),
282
+ getLineY: (line) => this.#getLineY(line),
283
+ isMouseDown: () => this.#isContentMouseDown || this.#isGutterMouseDown
284
+ });
285
+ this.#markerRenderer.setMarkers(markers, textDocument);
286
+ if (this.#contentElement !== void 0) this.#markerRenderer.listenHover(this.#contentElement);
287
+ this.#updateSelections(this.#selections ?? []);
288
+ }
289
+ focus(options) {
290
+ const preventScroll = options?.preventScroll ?? false;
291
+ const primarySelection = this.#selections?.at(-1);
292
+ if (primarySelection !== void 0) {
293
+ const pos = primarySelection.direction === -1 ? primarySelection.end : primarySelection.start;
294
+ this.#focus(pos, preventScroll);
295
+ } else this.#focus(void 0, preventScroll);
296
+ }
297
+ blur() {
298
+ this.#contentElement?.blur();
299
+ }
300
+ cleanUp(recycle = false) {
301
+ const shouldRestoreState = this.#isStatePersistenceEnabled;
302
+ this.#stateRestoreGeneration++;
303
+ this.#persistCurrentState();
304
+ this.#restoreStateOnNextSync = shouldRestoreState;
305
+ dequeueRender(this.#handleCustomPasteEvent);
306
+ this.#tokenizer?.cleanUp();
307
+ this.#tokenizer = void 0;
308
+ if (!recycle) {
309
+ this.#textDocument = void 0;
310
+ this.#fileInfo = void 0;
311
+ }
312
+ this.#globalEventDisposes?.forEach((dispose) => dispose());
313
+ this.#globalEventDisposes = void 0;
314
+ this.#editorEventDisposes?.forEach((dispose) => dispose());
315
+ this.#editorEventDisposes = void 0;
316
+ this.#selectEventDisposes?.forEach((dispose) => dispose());
317
+ this.#selectEventDisposes = void 0;
318
+ this.#detach?.(recycle);
319
+ this.#detach = void 0;
320
+ this.#gutterWidthCache = void 0;
321
+ this.#contentWidthCache = void 0;
322
+ this.#lineYCache.clear();
323
+ this.#wrapLineOffsetsCache.clear();
324
+ this.#lineElementsCache.clear();
325
+ this.#lastAccessedCharX = void 0;
326
+ this.#globalStyleElement?.remove();
327
+ this.#globalStyleElement = void 0;
328
+ this.#editorStyleElement?.remove();
329
+ this.#editorStyleElement = void 0;
330
+ this.#themeStyleElement?.remove();
331
+ this.#themeStyleElement = void 0;
332
+ this.#spriteElement?.remove();
333
+ this.#spriteElement = void 0;
334
+ this.#fileContainer = void 0;
335
+ this.#popoverManager?.cleanUp();
336
+ this.#popoverManager = void 0;
337
+ this.#gutterElement = void 0;
338
+ this.#contentElement?.removeAttribute("contentEditable");
339
+ this.#contentElement = void 0;
340
+ this.#contentHasFocus = false;
341
+ this.#overlayElement?.remove();
342
+ this.#overlayElement = void 0;
343
+ this.#resizeObserver?.disconnect();
344
+ this.#resizeObserver = void 0;
345
+ this.#fontRemeasureScheduled = false;
346
+ if (this.#themeSelectionRefreshFrame !== void 0) {
347
+ cancelAnimationFrame(this.#themeSelectionRefreshFrame);
348
+ this.#themeSelectionRefreshFrame = void 0;
349
+ }
350
+ this.#resetState();
351
+ }
352
+ /** @internal Capture outgoing state and substitute cached text before render. */
353
+ __prepareFile(file) {
354
+ if (this.#options.persistState !== true) return file;
355
+ const cacheKey = requirePersistedCacheKey(file);
356
+ const fileInfo = this.#fileInfo;
357
+ const languageId = file.lang ?? getFiletypeFromFileName(file.name);
358
+ if (fileInfo !== void 0 && (requirePersistedCacheKey(fileInfo) !== cacheKey || fileInfo.name !== file.name || this.#textDocument?.languageId !== languageId)) {
359
+ this.#stateRestoreGeneration++;
360
+ this.#persistCurrentState();
361
+ }
362
+ const textDocument = this.#getCachedTextDocument(file, cacheKey);
363
+ if (textDocument === void 0 || textDocument.getText() === file.contents) return file;
364
+ return {
365
+ ...file,
366
+ contents: textDocument.getText()
367
+ };
368
+ }
369
+ /** @internal */
370
+ __postponeBgTokenizeToNextFrame() {
371
+ const tokenizer = this.#tokenizer;
372
+ if (tokenizer !== void 0) {
373
+ tokenizer.pauseBackgroundTokenize();
374
+ requestAnimationFrame(() => {
375
+ tokenizer.resumeBackgroundTokenize();
376
+ });
377
+ }
378
+ }
379
+ /** @internal */
380
+ __syncRenderView = (highlighter, fileContainer, fileOrDiff, lineAnnotations, renderRange) => {
381
+ const shadowRoot = fileContainer.shadowRoot;
382
+ if (shadowRoot == null) {
383
+ console.error("[editor] Could not find the shadow root.");
384
+ return;
385
+ }
386
+ let codeElement;
387
+ let gutterEl;
388
+ let contentEl;
389
+ for (const el of shadowRoot.querySelectorAll("[data-code]")) if (el.dataset.deletions === void 0) {
390
+ codeElement = el;
391
+ for (const child of el.children) {
392
+ const el = child;
393
+ const { gutter, content } = el.dataset;
394
+ if (gutter !== void 0) gutterEl = el;
395
+ else if (content !== void 0) contentEl = el;
396
+ }
397
+ break;
398
+ }
399
+ if (codeElement === void 0 || contentEl === void 0) return;
400
+ this.#getPopoverManager().setViewportElements(fileContainer, codeElement);
401
+ if (this.#fileContainer !== fileContainer) {
402
+ this.#fileContainer = fileContainer;
403
+ if (this.#globalStyleElement !== void 0) fileContainer.appendChild(this.#globalStyleElement);
404
+ if (this.#editorStyleElement !== void 0) shadowRoot.appendChild(this.#editorStyleElement);
405
+ if (this.#themeStyleElement !== void 0) shadowRoot.appendChild(this.#themeStyleElement);
406
+ if (this.#spriteElement !== void 0) shadowRoot.prepend(this.#spriteElement);
407
+ }
408
+ const shouldRebuildDocument = this.#textDocument === void 0 || this.#fileInfo === void 0 || this.#fileInfo.name !== fileOrDiff.name || this.#fileInfo.lang !== fileOrDiff.lang || this.#fileInfo.cacheKey !== fileOrDiff.cacheKey;
409
+ const persistedCacheKey = this.#isStatePersistenceEnabled ? requirePersistedCacheKey(fileOrDiff) : void 0;
410
+ let persistedStateTarget;
411
+ if (shouldRebuildDocument) {
412
+ let contents = "";
413
+ if ("contents" in fileOrDiff) contents = fileOrDiff.contents;
414
+ else contents = fileOrDiff.additionLines.join("");
415
+ const editStack = new EditStack({ maxEntries: this.#options.historyMaxEntries });
416
+ const { name, lang, cacheKey } = fileOrDiff;
417
+ const languageId = lang ?? getFiletypeFromFileName(fileOrDiff.name);
418
+ const textDocument = (persistedCacheKey !== void 0 ? this.#getCachedTextDocument(fileOrDiff, persistedCacheKey) : void 0) ?? new TextDocument(fileOrDiff.name, contents, languageId, 0, editStack);
419
+ this.#fileInfo = {
420
+ name,
421
+ lang,
422
+ cacheKey
423
+ };
424
+ this.#textDocument = textDocument;
425
+ if (persistedCacheKey !== void 0) {
426
+ this.#textDocumentCache.set(persistedCacheKey, textDocument);
427
+ persistedStateTarget = {
428
+ cacheKey: persistedCacheKey,
429
+ textDocument
430
+ };
431
+ }
432
+ this.#tokenizer?.cleanUp();
433
+ this.#tokenizer = void 0;
434
+ this.#resetState();
435
+ this.#selections = this.#initSelections;
436
+ requestAnimationFrame(() => {
437
+ this.#options.onAttach?.(this, this.#fileInstance);
438
+ });
439
+ if (this.#textDocument !== void 0 && this.#options.__debug === true) console.log("[diffs/editor] text document rebuilt from", fileOrDiff.name);
440
+ }
441
+ if (persistedStateTarget === void 0 && this.#restoreStateOnNextSync && persistedCacheKey !== void 0 && this.#textDocument !== void 0) persistedStateTarget = {
442
+ cacheKey: persistedCacheKey,
443
+ textDocument: this.#textDocument
444
+ };
445
+ const textDocument = this.#textDocument;
446
+ if (this.#tokenizer == null && textDocument != null) this.#tokenizer = new EditorTokenizer({
447
+ highlighter,
448
+ textDocument,
449
+ codeOptions: this.#fileInstance?.options ?? {},
450
+ matchBrackets: this.#options.matchBrackets,
451
+ onDeferTokenize: this.#onDeferTokenize,
452
+ onThemeChange: () => this.#scheduleThemeSelectionRefresh(),
453
+ setStyle: (css) => {
454
+ this.#themeStyleElement.textContent = css;
455
+ },
456
+ __debug: this.#options.__debug
457
+ });
458
+ if (this.#contentElement !== contentEl) {
459
+ this.#gutterElement = gutterEl;
460
+ this.#contentElement = extend(contentEl, {
461
+ contentEditable: "true",
462
+ role: "textbox",
463
+ ariaMultiLine: "true",
464
+ autocapitalize: "off",
465
+ writingSuggestions: "off",
466
+ autocorrect: false,
467
+ spellcheck: false,
468
+ translate: false
469
+ });
470
+ if (this.#overlayElement !== void 0) contentEl.after(this.#overlayElement);
471
+ this.#metrics.init(contentEl);
472
+ this.#remeasureMetricsOnFontLoad();
473
+ this.#listenContentElement(contentEl, gutterEl);
474
+ if (this.#contentElement !== void 0 && this.#options.__debug === true) console.log("[diffs/editor] full re-render triggered !!!");
475
+ }
476
+ if (contentEl.ariaLabel !== fileOrDiff.name) contentEl.ariaLabel = fileOrDiff.name;
477
+ if (lineAnnotations !== void 0 && lineAnnotations.length > 0 || this.#isDiff) for (const child of this.#contentElement.children) {
478
+ const el = child;
479
+ const { lineAnnotation, lineType, separator } = el.dataset;
480
+ if (lineAnnotation !== void 0 || separator !== void 0 || lineType === "change-deletion") el.setAttribute("contenteditable", "false");
481
+ }
482
+ this.#resetCache();
483
+ this.#tokenizer?.syncTheme(this.#fileInstance?.options ?? {});
484
+ this.#lineAnnotations = lineAnnotations;
485
+ this.#renderRange = renderRange;
486
+ this.#viewportWindowLines = renderRange?.totalLines;
487
+ this.#tokenizer?.prebuildStateStack(renderRange);
488
+ this.#markerRenderer?.removePopup();
489
+ if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
490
+ if (this.#initSelections !== void 0 && this.#primaryCaretElement !== void 0) {
491
+ this.#initSelections = void 0;
492
+ this.#scrollToPrimaryCaret(false, "center");
493
+ } else if (this.#scrollingToLine !== void 0) this.#scrollToLine(this.#scrollingToLine, this.#scrollingToLineChar, this.#scrollingToLineNoFocus);
494
+ else if (this.#selections !== void 0 && this.#selections.length > 0 && !this.#retainSearchPanelFocus) this.focus({ preventScroll: true });
495
+ if (this.#retainSearchPanelFocus) this.#searchPanel?.focus();
496
+ if (this.#options.__debug === true && renderRange !== void 0) {
497
+ const { startingLine, totalLines } = renderRange;
498
+ console.log("[diffs/editor] render file:", fileOrDiff.name, "RenderRange:", startingLine + "-" + (startingLine + totalLines), "of", this.#textDocument?.lineCount, "lines");
499
+ }
500
+ if (persistedStateTarget !== void 0) {
501
+ this.#restoreStateOnNextSync = false;
502
+ this.#restorePersistedState(persistedStateTarget.cacheKey, persistedStateTarget.textDocument);
503
+ }
504
+ };
505
+ get #isStatePersistenceEnabled() {
506
+ return this.#options.persistState === true && this.#fileInstance?.type === "file";
507
+ }
508
+ #getCachedTextDocument(file, cacheKey) {
509
+ const textDocument = this.#textDocumentCache.get(cacheKey);
510
+ const languageId = file.lang ?? getFiletypeFromFileName(file.name);
511
+ return textDocument?.languageId === languageId ? textDocument : void 0;
512
+ }
513
+ #getStateStorage() {
514
+ const option = this.#options.persistStateStorage ?? "inMemory";
515
+ if (this.#stateStorage === void 0 || this.#stateStorageOption !== option) {
516
+ this.#stateStorage = createStateStorage(option);
517
+ this.#stateStorageOption = option;
518
+ }
519
+ return this.#stateStorage;
520
+ }
521
+ #persistCurrentState() {
522
+ const fileInfo = this.#fileInfo;
523
+ const textDocument = this.#textDocument;
524
+ if (!this.#isStatePersistenceEnabled || fileInfo === void 0 || textDocument === void 0) return;
525
+ const cacheKey = requirePersistedCacheKey(fileInfo);
526
+ this.#textDocumentCache.set(cacheKey, textDocument);
527
+ let storage;
528
+ try {
529
+ storage = this.#getStateStorage();
530
+ } catch {
531
+ return;
532
+ }
533
+ const state = cloneEditorState(this.getState());
534
+ const pendingRestore = this.#pendingStateRestore;
535
+ if (pendingRestore?.cacheKey === cacheKey && pendingRestore.textDocument === textDocument) {
536
+ this.#pendingStateRestore = void 0;
537
+ if (textDocument.version === pendingRestore.documentVersion && this.#selections === pendingRestore.selections && state.view?.scrollLeft === pendingRestore.view?.scrollLeft && state.view?.scrollTop === pendingRestore.view?.scrollTop) return;
538
+ this.#writeState(storage, cacheKey, state, pendingRestore.completion);
539
+ return;
540
+ }
541
+ this.#writeState(storage, cacheKey, state);
542
+ }
543
+ #writeState(storage, cacheKey, state, waitFor) {
544
+ const previousWrite = this.#pendingStateWrites.get(cacheKey);
545
+ if (waitFor !== void 0 || previousWrite !== void 0) {
546
+ let pending = waitFor ?? Promise.resolve();
547
+ if (previousWrite !== void 0) pending = pending.then(() => previousWrite);
548
+ this.#trackStateWrite(cacheKey, pending.then(() => storage.set(cacheKey, state)));
549
+ return;
550
+ }
551
+ let result;
552
+ try {
553
+ result = storage.set(cacheKey, state);
554
+ } catch {
555
+ return;
556
+ }
557
+ if (!isPromise(result)) return;
558
+ this.#trackStateWrite(cacheKey, result);
559
+ }
560
+ #trackStateWrite(cacheKey, result) {
561
+ const pending = result.catch(() => {});
562
+ this.#pendingStateWrites.set(cacheKey, pending);
563
+ pending.finally(() => {
564
+ if (this.#pendingStateWrites.get(cacheKey) === pending) this.#pendingStateWrites.delete(cacheKey);
565
+ });
566
+ }
567
+ #restorePersistedState(cacheKey, textDocument) {
568
+ const generation = ++this.#stateRestoreGeneration;
569
+ const documentVersion = textDocument.version;
570
+ const selections = this.#selections;
571
+ const view = this.getState().view;
572
+ const applyState = (state) => {
573
+ const currentView = this.getState().view;
574
+ if (state === void 0 || generation !== this.#stateRestoreGeneration || this.#textDocument !== textDocument || textDocument.version !== documentVersion || this.#selections !== selections || currentView?.scrollLeft !== view?.scrollLeft || currentView?.scrollTop !== view?.scrollTop || this.#fileInfo === void 0 || requirePersistedCacheKey(this.#fileInfo) !== cacheKey) return;
575
+ this.setState(cloneEditorState(state));
576
+ };
577
+ const readState = () => {
578
+ let result;
579
+ try {
580
+ result = this.#getStateStorage().get(cacheKey);
581
+ } catch {
582
+ return;
583
+ }
584
+ if (isPromise(result)) return result.then(applyState).catch(() => {});
585
+ else try {
586
+ applyState(result);
587
+ } catch {}
588
+ };
589
+ const pendingWrite = this.#pendingStateWrites.get(cacheKey);
590
+ const result = pendingWrite === void 0 ? readState() : pendingWrite.then(readState);
591
+ if (isPromise(result)) {
592
+ const pendingRestore = {
593
+ cacheKey,
594
+ textDocument,
595
+ documentVersion,
596
+ selections,
597
+ view,
598
+ completion: result.catch(() => {})
599
+ };
600
+ this.#pendingStateRestore = pendingRestore;
601
+ pendingRestore.completion.finally(() => {
602
+ if (this.#pendingStateRestore === pendingRestore) this.#pendingStateRestore = void 0;
603
+ });
604
+ }
605
+ }
606
+ #isLineRenderable(line) {
607
+ return this.#fileInstance?.isLineRenderable?.(line + 1) ?? true;
608
+ }
609
+ get #resolveRenderableLine() {
610
+ const fileInstance = this.#fileInstance;
611
+ if (fileInstance?.getNearestRenderableLine == null) return;
612
+ return (line, direction) => {
613
+ const nearest = fileInstance.getNearestRenderableLine(line + 1, direction);
614
+ return nearest == null ? void 0 : nearest - 1;
615
+ };
616
+ }
617
+ #revealLineIfCollapsed(line) {
618
+ if (!this.#isLineRenderable(line)) this.#fileInstance?.revealLine?.(line + 1);
619
+ }
620
+ get #diffSyle() {
621
+ return this.#fileInstance?.options.diffStyle ?? "split";
622
+ }
623
+ get #isDiff() {
624
+ return this.#fileInstance?.type === "file-diff";
625
+ }
626
+ get #isWrap() {
627
+ return this.#fileInstance?.options.overflow === "wrap";
628
+ }
629
+ #getPopoverManager() {
630
+ return this.#popoverManager ??= new PopoverManager({
631
+ hasActivePopover: () => this.#selectionAction !== void 0 || this.#markerRenderer?.isPopupVisible() === true,
632
+ updateActivePopover: () => {
633
+ this.#updateSelectionActionPopover();
634
+ this.#markerRenderer?.updatePopupPosition();
635
+ }
636
+ });
637
+ }
638
+ #resetCache() {
639
+ this.#lineYCache.clear();
640
+ this.#wrapLineOffsetsCache.clear();
641
+ this.#lineElementsCache.clear();
642
+ this.#lastAccessedCharX = void 0;
643
+ }
644
+ #resetState() {
645
+ this.#setSelectedLinesSafe(null);
646
+ this.#gutterWidthCache = void 0;
647
+ this.#contentWidthCache = void 0;
648
+ this.#shouldIgnoreSelectionChange = false;
649
+ this.#overlayElements?.forEach((el) => el.remove());
650
+ this.#overlayElements = void 0;
651
+ this.#selections = void 0;
652
+ this.#reservedSelections = void 0;
653
+ this.#scrollingToLine = void 0;
654
+ this.#markerRenderer?.cleanup();
655
+ this.#markerRenderer = void 0;
656
+ this.#searchPanel?.cleanup();
657
+ this.#searchPanel = void 0;
658
+ this.#selectionAction?.cleanup();
659
+ this.#selectionAction = void 0;
660
+ }
661
+ #initialize() {
662
+ this.#globalStyleElement = h("style", {
663
+ dataset: "editorGlobalCss",
664
+ textContent: `
665
+ [data-annotation-slot] {
666
+ user-select: none;
667
+ -webkit-user-select: none;
668
+ }
669
+ `
670
+ });
671
+ this.#editorStyleElement = h("style", {
672
+ dataset: "editorCss",
673
+ textContent: editor_default
674
+ });
675
+ this.#themeStyleElement = h("style", { dataset: "editorThemeCss" });
676
+ this.#spriteElement = createSpriteElement();
677
+ this.#overlayElement = h("div", { dataset: "editorOverlay" });
678
+ this.#globalEventDisposes = [
679
+ addEventListener(document, "selectionchange", () => {
680
+ const shadowRoot = this.#fileContainer?.shadowRoot;
681
+ if (this.#shouldIgnoreSelectionChange || this.#suppressNativeSelectionSync || shadowRoot == null || !this.#contentHasFocus) return;
682
+ if (this.#selections !== void 0 && this.#selections.length > 1 && !this.#isContentMouseDown) return;
683
+ const selectionRaw = document.getSelection();
684
+ if (selectionRaw == null) return;
685
+ const composedRange = typeof selectionRaw.getComposedRanges === "function" ? selectionRaw.getComposedRanges({ shadowRoots: [shadowRoot] })?.[0] : getShadowRootRange(shadowRoot);
686
+ if (composedRange === void 0 || !this.#rangeBelongsToEditor(composedRange)) return;
687
+ let selection = convertSelection(composedRange, 0);
688
+ if (selection === void 0) return;
689
+ if (this.#isContentMouseDown && this.#shiftKeyPressed && this.#selections !== void 0 && this.#selections.length > 0) {
690
+ const primarySelection = this.#selections.at(-1);
691
+ this.#updateSelections([extendSelection(primarySelection, selection)]);
692
+ return;
693
+ }
694
+ if (this.#isContentMouseDown) if (this.#selectionStart !== void 0) selection = createSelectionFrom(this.#selectionStart, selection);
695
+ else this.#selectionStart = selection;
696
+ else if (this.#selectionStart !== void 0) selection.direction = createSelectionFrom(this.#selectionStart, selection).direction;
697
+ else if (this.#selections !== void 0 && this.#selections.length === 1) {
698
+ const previous = this.#selections[0];
699
+ if (comparePosition(previous.start, selection.start) === 0 && comparePosition(previous.end, selection.end) === 0) selection.direction = previous.direction;
700
+ }
701
+ if (this.#reservedSelections !== void 0) this.#updateSelections([...this.#reservedSelections.filter((reservedSelection) => !selectionIntersects(reservedSelection, selection)), selection]);
702
+ else this.#updateSelections([selection]);
703
+ }, { passive: true }),
704
+ addEventListener(document, "pointerup", (e) => {
705
+ if (e.pointerType !== "mouse") return;
706
+ this.#selectEventDisposes?.forEach((dispose) => dispose());
707
+ this.#selectEventDisposes = void 0;
708
+ if (this.#isGutterMouseDown) {
709
+ this.#isGutterMouseDown = false;
710
+ this.#focus();
711
+ }
712
+ this.#shouldIgnoreSelectionChange = false;
713
+ this.#isContentMouseDown = false;
714
+ this.#shiftKeyPressed = false;
715
+ this.#selectionStart = void 0;
716
+ this.#reservedSelections = void 0;
717
+ if (this.#options.enabledSelectionAction === true && this.#selections !== void 0 && this.#selections.length > 0 && !isCollapsedSelection(this.#selections.at(-1))) this.#updateSelections(this.#selections);
718
+ }, { passive: true }),
719
+ addEventListener(document, "keydown", (e) => {
720
+ if (e.key === "Shift") this.#selectionStart = this.#selections?.at(-1);
721
+ }, { passive: true }),
722
+ addEventListener(document, "keyup", (e) => {
723
+ if (e.key === "Shift") this.#selectionStart = void 0;
724
+ }, { passive: true })
725
+ ];
726
+ }
727
+ #replaceSelectEventListeners(disposes) {
728
+ this.#selectEventDisposes?.forEach((dispose) => dispose());
729
+ this.#selectEventDisposes = disposes;
730
+ }
731
+ #listenContentElement(contentEl, gutterEl) {
732
+ const { onFocus, onBlur } = this.#options;
733
+ const targetIsContentElement = (e) => {
734
+ const target = e.composedPath()[0];
735
+ return target !== void 0 && (target === contentEl || contentEl.contains(target));
736
+ };
737
+ this.#editorEventDisposes?.forEach((dispose) => dispose());
738
+ this.#editorEventDisposes = [
739
+ addEventListener(contentEl, "focus", () => {
740
+ this.#contentHasFocus = true;
741
+ onFocus?.();
742
+ if (!this.#isContentMouseDown && !this.#shouldIgnoreSelectionChange && this.#selections !== void 0 && this.#selections.length > 0) this.#setWindowSelection(this.#selections.at(-1));
743
+ }, { passive: true }),
744
+ addEventListener(contentEl, "blur", () => {
745
+ this.#contentHasFocus = false;
746
+ onBlur?.();
747
+ }, { passive: true }),
748
+ addEventListener(contentEl, "pointerdown", (e) => {
749
+ this.#suppressNativeSelectionSync = false;
750
+ if (e.pointerType !== "mouse") return;
751
+ if (this.#isDeletedLineTarget(e)) {
752
+ this.#setDeletedTextSelectionActive(true);
753
+ if (this.#selections !== void 0) this.#updateSelections([]);
754
+ return;
755
+ }
756
+ this.#setDeletedTextSelectionActive(false);
757
+ this.#markerRenderer?.removePopup();
758
+ if (isSafari() && this.#lineAnnotations !== void 0 && this.#lineAnnotations.length > 0) {
759
+ const annotationDisposes = [...contentEl.querySelectorAll("[data-line-annotation]")].map((el) => [addEventListener(el, "mouseenter", () => {
760
+ this.#shouldIgnoreSelectionChange = true;
761
+ }), addEventListener(el, "mouseleave", () => {
762
+ this.#shouldIgnoreSelectionChange = false;
763
+ })]).flat();
764
+ this.#replaceSelectEventListeners(annotationDisposes);
765
+ }
766
+ this.#isContentMouseDown = true;
767
+ this.#selectionStart = void 0;
768
+ if (e.button === 0 && isPrimaryModifier(e)) this.#reservedSelections = this.#selections?.map((selection) => ({ ...selection }));
769
+ if (e.shiftKey) {
770
+ const primarySelection = this.#selections?.at(-1);
771
+ if (primarySelection !== void 0) {
772
+ const pos = primarySelection.direction === -1 ? primarySelection.end : primarySelection.start;
773
+ this.#setWindowSelection({
774
+ start: pos,
775
+ end: pos,
776
+ direction: 0
777
+ });
778
+ }
779
+ this.#shiftKeyPressed = true;
780
+ }
781
+ }, { passive: true }),
782
+ addEventListener(contentEl, "keydown", (e) => {
783
+ if (e.key === "Escape") {
784
+ e.preventDefault();
785
+ this.#searchPanel?.close();
786
+ this.#searchPanel = void 0;
787
+ this.#retainSearchPanelFocus = false;
788
+ this.#selectionAction?.cleanup();
789
+ this.#selectionAction = void 0;
790
+ this.#runCommand("simplifySelection");
791
+ return;
792
+ }
793
+ if (!targetIsContentElement(e)) return;
794
+ this.#suppressNativeSelectionSync = false;
795
+ const mvShortcut = isMoveCursorShortcut(e);
796
+ const textDocument = this.#textDocument;
797
+ if (this.#selections !== void 0 && this.#selections.length > 0 && mvShortcut !== void 0 && textDocument !== void 0) {
798
+ const cursorMoveOptions = {
799
+ getSoftLineOffsets: this.#isWrap ? (line) => this.#wrapLineText(line) : void 0,
800
+ resolveRenderableLine: this.#resolveRenderableLine
801
+ };
802
+ if (e.shiftKey) this.#updateSelections(mapSelectionShift(textDocument, this.#selections, mvShortcut, cursorMoveOptions));
803
+ else this.#updateSelections(mapCursorMove(textDocument, this.#selections, mvShortcut, cursorMoveOptions));
804
+ this.#scrollToPrimaryCaret();
805
+ e.preventDefault();
806
+ return;
807
+ }
808
+ if ((e.key.length === 1 ? e.key.toLowerCase() : e.key) === "v" && isPrimaryModifier(e)) {
809
+ if (e.repeat && this.#isDiff) {
810
+ e.preventDefault();
811
+ return;
812
+ }
813
+ if (this.#options.clipboard !== void 0) {
814
+ e.preventDefault();
815
+ queueRender(this.#handleCustomPasteEvent);
816
+ return;
817
+ }
818
+ }
819
+ if (this.#searchPanel !== void 0) {
820
+ const findAgain = resolveFindAgainShortcut(e);
821
+ if (findAgain !== void 0) {
822
+ e.preventDefault();
823
+ this.#searchPanel.navigate(findAgain === "previous");
824
+ return;
825
+ }
826
+ }
827
+ const command = resolveEditorCommandFromKeyboardEvent(e);
828
+ if (command !== void 0) {
829
+ e.preventDefault();
830
+ this.#runCommand(command);
831
+ }
832
+ }),
833
+ addEventListener(contentEl, "copy", (e) => {
834
+ if (!targetIsContentElement(e)) return;
835
+ e.preventDefault();
836
+ if (this.#isDeletedTextSelectionActive()) e.clipboardData?.setData("text", this.#deletedTextForClipboard());
837
+ else this.#writeSelectionClipboardData(e.clipboardData, this.#getSelectionText(), this.#getSelectionClipboardTexts());
838
+ }),
839
+ addEventListener(contentEl, "cut", (e) => {
840
+ if (!targetIsContentElement(e)) return;
841
+ e.preventDefault();
842
+ if (this.#isDeletedTextSelectionActive()) e.clipboardData?.setData("text", this.#deletedTextForClipboard());
843
+ else {
844
+ const selectionTexts = this.#getSelectionClipboardTexts();
845
+ this.#writeSelectionClipboardData(e.clipboardData, this.#cutSelectionText(), selectionTexts);
846
+ }
847
+ }),
848
+ addEventListener(contentEl, "paste", (e) => {
849
+ if (!targetIsContentElement(e)) return;
850
+ e.preventDefault();
851
+ const clipboardData = e.clipboardData;
852
+ const textDocument = this.#textDocument;
853
+ if (clipboardData === null || textDocument === void 0) return;
854
+ let text = clipboardData.getData("text");
855
+ const selectionCount = this.#selections?.length ?? 0;
856
+ if (selectionCount > 1) {
857
+ const multiSelectionText = clipboardData.getData(MULTI_SELECTION_CLIPBOARD_TYPE);
858
+ if (multiSelectionText !== void 0) try {
859
+ const selectionTexts = JSON.parse(multiSelectionText);
860
+ if (Array.isArray(selectionTexts) && selectionTexts.length === selectionCount) text = selectionTexts;
861
+ } catch {}
862
+ }
863
+ this.#replaceSelectionText(Array.isArray(text) ? text.map((t) => textDocument.normalizeEol(t)) : textDocument.normalizeEol(text), void 0, true, "document");
864
+ }),
865
+ addEventListener(contentEl, "beforeinput", (e) => {
866
+ if (!targetIsContentElement(e)) return;
867
+ if (e.inputType === "insertCompositionText") return;
868
+ e.preventDefault();
869
+ this.#handleInput(e.inputType, e.data);
870
+ }),
871
+ addEventListener(contentEl, "drop", (e) => {
872
+ if (!targetIsContentElement(e)) return;
873
+ e.preventDefault();
874
+ }),
875
+ addEventListener(contentEl, "compositionstart", (e) => {
876
+ if (!targetIsContentElement(e)) return;
877
+ this.#isComposing = true;
878
+ this.#shouldIgnoreSelectionChange = true;
879
+ }, { passive: true }),
880
+ addEventListener(contentEl, "compositionend", (e) => {
881
+ if (!targetIsContentElement(e)) return;
882
+ this.#shouldIgnoreSelectionChange = false;
883
+ const wasComposing = this.#isComposing;
884
+ this.#isComposing = false;
885
+ if (e.data !== "" || !wasComposing) this.#handleInput("insertText", e.data);
886
+ }, { passive: true })
887
+ ];
888
+ const deletionsCode = this.#fileContainer?.shadowRoot?.querySelector("[data-deletions]");
889
+ if (deletionsCode != null) this.#editorEventDisposes.push(addEventListener(deletionsCode, "pointerdown", (e) => {
890
+ const target = e.composedPath()[0];
891
+ const gutterRow = target instanceof HTMLElement ? target.closest("[data-column-number]") : null;
892
+ if (gutterRow instanceof HTMLElement && this.#beginDeletionGutterSelection(gutterRow, deletionsCode, e.pointerType === "mouse")) return;
893
+ this.#setDeletedTextSelectionActive(true);
894
+ if (this.#selections !== void 0) this.#updateSelections([]);
895
+ }, { passive: true }));
896
+ if (gutterEl !== void 0) {
897
+ const resolveGutterTarget = (eventTarget, includeContentLine = false) => {
898
+ let target = eventTarget;
899
+ if (target?.dataset.lineNumberContent !== void 0) target = target.parentElement ?? void 0;
900
+ else if (includeContentLine && target?.tagName === "SPAN") target = target.closest("[data-line]");
901
+ return target;
902
+ };
903
+ const resolveEditableLine = (target) => {
904
+ if (target === void 0) return;
905
+ const lineType = target.dataset.lineType;
906
+ const lineNumber = getLineNumberAttr(target) ?? getLineNumberAttr(target, "columnNumber");
907
+ if (lineNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) return;
908
+ return lineNumber - 1;
909
+ };
910
+ this.#editorEventDisposes.push(addEventListener(gutterEl, "pointerdown", (e) => {
911
+ const gutterRow = resolveGutterTarget(e.composedPath()[0]);
912
+ if (gutterRow?.dataset.lineType === "change-deletion") {
913
+ const code = gutterRow.closest("[data-code]");
914
+ if (code != null) this.#beginDeletionGutterSelection(gutterRow, code, e.pointerType === "mouse");
915
+ return;
916
+ }
917
+ if (e.pointerType !== "mouse") return;
918
+ const textDocument = this.#textDocument;
919
+ const lineIndex = resolveEditableLine(gutterRow);
920
+ if (lineIndex === void 0 || textDocument === void 0) return;
921
+ this.#markerRenderer?.removePopup();
922
+ const selection = this.#spanLineSelection(lineIndex, lineIndex, textDocument);
923
+ this.#isGutterMouseDown = true;
924
+ this.#selectionStart = selection;
925
+ this.#updateSelections([selection]);
926
+ this.#setWindowSelection(selection);
927
+ this.#focus();
928
+ this.#replaceSelectEventListeners([addEventListener(document, "mousemove", (e) => {
929
+ if (!this.#isGutterMouseDown) return;
930
+ const textDocument = this.#textDocument;
931
+ const lineIndex = resolveEditableLine(resolveGutterTarget(e.composedPath()[0], true));
932
+ if (lineIndex === void 0 || textDocument === void 0) return;
933
+ const anchorLine = this.#selectionStart?.start.line ?? lineIndex;
934
+ const selection = this.#spanLineSelection(anchorLine, lineIndex, textDocument);
935
+ this.#selectionStart ??= selection;
936
+ this.#updateSelections([selection]);
937
+ this.#focus(selection.direction === -1 ? selection.start : selection.end);
938
+ }, { passive: true })]);
939
+ }, { passive: true }));
940
+ }
941
+ this.#markerRenderer?.listenHover(contentEl);
942
+ this.#resizeObserver?.disconnect();
943
+ this.#resizeObserver = new ResizeObserver(this.#handleLayoutResize);
944
+ this.#resizeObserver.observe(contentEl);
945
+ this.#resizeObserver.observe(contentEl.parentElement);
946
+ this.#computeContentOffset(contentEl);
947
+ }
948
+ #handleCustomPasteEvent = async () => {
949
+ const clipboard = this.#options.clipboard;
950
+ if (clipboard !== void 0) {
951
+ let text = await clipboard.readText();
952
+ const selectionCount = this.#selections?.length ?? 0;
953
+ if (selectionCount > 1) {
954
+ const multiSelectionText = await clipboard.readText(MULTI_SELECTION_CLIPBOARD_TYPE);
955
+ try {
956
+ const selectionTexts = JSON.parse(multiSelectionText);
957
+ if (Array.isArray(selectionTexts) && selectionTexts.length === selectionCount) text = selectionTexts;
958
+ } catch {}
959
+ }
960
+ const textDocument = this.#textDocument;
961
+ if (textDocument !== void 0) this.#replaceSelectionText(Array.isArray(text) ? text.map((t) => textDocument.normalizeEol(t)) : textDocument.normalizeEol(text), void 0, true, "document");
962
+ }
963
+ };
964
+ #computeContentOffset(contentEl) {
965
+ if (this.#isDiff && this.#diffSyle === "split" && this.#isWrap) {
966
+ this.#contentOffset = {
967
+ top: contentEl.offsetTop,
968
+ left: contentEl.offsetLeft - this.#getGutterWidth()
969
+ };
970
+ if (this.#options.__debug === true) console.log("[diffs/editor] content offset:", this.#contentOffset);
971
+ }
972
+ }
973
+ get #activeContentOffset() {
974
+ if (this.#isDiff && this.#diffSyle === "split" && this.#isWrap) return this.#contentOffset;
975
+ }
976
+ #runCommand(command) {
977
+ const textDocument = this.#textDocument;
978
+ if (textDocument === void 0) return;
979
+ switch (command) {
980
+ case "openSearchPanel":
981
+ this.#openSearchPanel("find");
982
+ break;
983
+ case "openSearchReplacePanel":
984
+ this.#openSearchPanel("replace");
985
+ break;
986
+ case "findNextMatch": {
987
+ const selections = this.#selections;
988
+ if (selections === void 0) break;
989
+ if (selections.some(isCollapsedSelection)) {
990
+ const expanded = selections.map((sel) => {
991
+ if (isCollapsedSelection(sel)) return expandCollapsedSelectionToWord(textDocument, sel);
992
+ return sel;
993
+ });
994
+ this.#updateSelections(expanded);
995
+ this.focus();
996
+ } else {
997
+ const nextMatch = findNexMatch(textDocument, selections);
998
+ if (nextMatch !== void 0) {
999
+ this.#updateSelections(nextMatch);
1000
+ const primaryMatch = nextMatch.at(-1);
1001
+ if (primaryMatch !== void 0) this.#revealLineIfCollapsed(getCaretPosition(primaryMatch).line);
1002
+ this.#scrollToPrimaryCaret();
1003
+ }
1004
+ }
1005
+ break;
1006
+ }
1007
+ case "moveLineUp":
1008
+ case "moveLineDown":
1009
+ this.#moveSelectedLines(command === "moveLineUp" ? -1 : 1);
1010
+ break;
1011
+ case "copyLineUp":
1012
+ case "copyLineDown":
1013
+ this.#copySelectedLines(command === "copyLineUp" ? -1 : 1);
1014
+ break;
1015
+ case "simplifySelection": {
1016
+ const selections = this.#selections;
1017
+ const primarySelection = selections?.at(-1);
1018
+ if (selections === void 0 || primarySelection === void 0) break;
1019
+ if (selections.length > 1) {
1020
+ this.#updateSelections([primarySelection]);
1021
+ this.#focus(getCaretPosition(primarySelection));
1022
+ } else if (!isCollapsedSelection(primarySelection)) {
1023
+ const caret = getCaretPosition(primarySelection);
1024
+ this.#updateSelections([{
1025
+ start: caret,
1026
+ end: caret,
1027
+ direction: 0
1028
+ }]);
1029
+ this.#focus(caret);
1030
+ }
1031
+ break;
1032
+ }
1033
+ case "insertBlankLine":
1034
+ this.#insertBlankLine();
1035
+ break;
1036
+ case "deleteHardLineForward":
1037
+ this.#deleteHardLineForward();
1038
+ break;
1039
+ case "toggleComment":
1040
+ case "toggleBlockComment": {
1041
+ const selections = this.#selections;
1042
+ if (selections === void 0) break;
1043
+ const { lineComment, blockComment } = resolveCommentConfig(textDocument.languageId, this.#options.languageCommentConfig);
1044
+ if (command === "toggleComment" && lineComment !== null) {
1045
+ this.#applyCommandEdits(resolveLineCommentEdits(textDocument, selections, lineComment));
1046
+ break;
1047
+ }
1048
+ const linewise = command === "toggleComment";
1049
+ const result = resolveBlockCommentEdits(textDocument, selections, blockComment, linewise);
1050
+ if (result !== void 0) this.#applyCommandEdits(result.edits, linewise ? void 0 : (document) => result.nextSelectionOffsets.map(([start, end, direction]) => ({
1051
+ start: document.positionAt(start),
1052
+ end: document.positionAt(end),
1053
+ direction
1054
+ })));
1055
+ break;
1056
+ }
1057
+ case "indent":
1058
+ case "outdent":
1059
+ case "indentLess":
1060
+ case "indentMore":
1061
+ if (this.#selections !== void 0) {
1062
+ const edits = [];
1063
+ const nextSelections = [];
1064
+ const editedLines = /* @__PURE__ */ new Set();
1065
+ const sameLineIndents = [];
1066
+ for (const selection of this.#selections) {
1067
+ const startLine = selection.start.line;
1068
+ const outdent = command === "outdent" || command === "indentLess";
1069
+ const lineBased = command === "indentLess" || command === "indentMore";
1070
+ if (startLine !== selection.end.line || outdent || lineBased) {
1071
+ const ret = resolveIndentEdits(textDocument, selection, this.#metrics.tabSize, outdent);
1072
+ for (const edit of ret[0]) {
1073
+ const line = edit.range.start.line;
1074
+ if (!editedLines.has(line)) {
1075
+ editedLines.add(line);
1076
+ edits.push(edit);
1077
+ }
1078
+ }
1079
+ nextSelections.push(ret[1]);
1080
+ } else {
1081
+ const text = textDocument.charAt({
1082
+ line: startLine,
1083
+ character: 0
1084
+ }) === " " ? " " : " ".repeat(this.#metrics.tabSize);
1085
+ edits.push({
1086
+ range: selection,
1087
+ newText: text
1088
+ });
1089
+ sameLineIndents.push({
1090
+ line: startLine,
1091
+ startCharacter: selection.start.character,
1092
+ addedLength: text.length - (selection.end.character - selection.start.character),
1093
+ selectionIndex: nextSelections.length
1094
+ });
1095
+ const nextPosition = {
1096
+ line: selection.start.line,
1097
+ character: selection.start.character + text.length
1098
+ };
1099
+ nextSelections.push({
1100
+ start: nextPosition,
1101
+ end: nextPosition,
1102
+ direction: 0
1103
+ });
1104
+ }
1105
+ }
1106
+ for (const indent of sameLineIndents) {
1107
+ let shift = 0;
1108
+ for (const other of sameLineIndents) if (other.line === indent.line && other.startCharacter < indent.startCharacter) shift += other.addedLength;
1109
+ if (shift !== 0) {
1110
+ const current = nextSelections[indent.selectionIndex];
1111
+ const position = {
1112
+ line: indent.line,
1113
+ character: current.start.character + shift
1114
+ };
1115
+ nextSelections[indent.selectionIndex] = {
1116
+ start: position,
1117
+ end: position,
1118
+ direction: 0
1119
+ };
1120
+ }
1121
+ }
1122
+ const change = textDocument.applyEdits(edits, true, this.#selections, nextSelections);
1123
+ if (change !== void 0) this.#applyChange(change, nextSelections);
1124
+ }
1125
+ break;
1126
+ case "selectAll": {
1127
+ const fullSelection = getDocumentFullSelection(textDocument);
1128
+ this.#updateSelections([fullSelection]);
1129
+ this.focus();
1130
+ const renderedLines = this.#getRenderedEditableLineRange();
1131
+ if (renderedLines !== void 0) {
1132
+ this.#suppressNativeSelectionSync = true;
1133
+ this.#setWindowSelection({
1134
+ start: {
1135
+ line: renderedLines.first,
1136
+ character: 0
1137
+ },
1138
+ end: {
1139
+ line: renderedLines.last,
1140
+ character: textDocument.getLineLength(renderedLines.last)
1141
+ },
1142
+ direction: 1
1143
+ });
1144
+ }
1145
+ break;
1146
+ }
1147
+ case "moveCursorToDocStart":
1148
+ case "moveCursorToDocEnd":
1149
+ {
1150
+ const boundarySelection = getDocumentBoundarySelection(textDocument, command === "moveCursorToDocEnd", this.#isDiff);
1151
+ this.#updateSelections([boundarySelection]);
1152
+ this.#revealLineIfCollapsed(getCaretPosition(boundarySelection).line);
1153
+ this.#scrollToPrimaryCaret();
1154
+ }
1155
+ break;
1156
+ case "expandSelectionDocStart":
1157
+ case "expandSelectionDocEnd":
1158
+ {
1159
+ const atEnd = command === "expandSelectionDocEnd";
1160
+ const selections = this.#selections;
1161
+ if (selections !== void 0) {
1162
+ const boundarySelection = getDocumentBoundarySelection(textDocument, atEnd, this.#isDiff);
1163
+ this.#updateSelections(extendSelections(selections, boundarySelection));
1164
+ this.#revealLineIfCollapsed(getCaretPosition(boundarySelection).line);
1165
+ this.#scrollToPrimaryCaret();
1166
+ }
1167
+ }
1168
+ break;
1169
+ case "undo":
1170
+ case "redo":
1171
+ this.#applyHistoryChange(command);
1172
+ break;
1173
+ }
1174
+ }
1175
+ #applyHistoryChange(command) {
1176
+ const textDocument = this.#textDocument;
1177
+ if (textDocument === void 0) return;
1178
+ if (command === "undo" && !textDocument.canUndo || command === "redo" && !textDocument.canRedo) return;
1179
+ const selections = this.#selections;
1180
+ const selectionOffsets = selections?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
1181
+ const result = command === "undo" ? textDocument.undo() : textDocument.redo();
1182
+ if (result === void 0) return;
1183
+ const [change, recordedSelections, lineAnnotations, selectionEdits] = result;
1184
+ const nextSelections = recordedSelections ?? (selections !== void 0 && selectionOffsets !== void 0 && selectionEdits !== void 0 ? remapSelectionsAfterEdits(textDocument, selections, selectionOffsets, selectionEdits) : void 0);
1185
+ this.#applyChange(change, nextSelections, lineAnnotations);
1186
+ }
1187
+ /** Applies one undoable command batch and records its resulting selections. */
1188
+ #applyCommandEdits(edits, resolveNextSelections) {
1189
+ const textDocument = this.#textDocument;
1190
+ const selections = this.#selections;
1191
+ if (textDocument === void 0 || selections === void 0) return;
1192
+ const remapSelections = resolveNextSelections === void 0;
1193
+ const selectionOffsets = remapSelections ? selections.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]) : void 0;
1194
+ const resolvedEdits = remapSelections ? edits.map((edit) => {
1195
+ const start = textDocument.offsetAt(edit.range.start);
1196
+ const end = textDocument.offsetAt(edit.range.end);
1197
+ return {
1198
+ start: Math.min(start, end),
1199
+ end: Math.max(start, end),
1200
+ text: edit.newText
1201
+ };
1202
+ }).sort((a, b) => {
1203
+ const startOrder = a.start - b.start;
1204
+ return startOrder !== 0 ? startOrder : a.end - b.end;
1205
+ }) : void 0;
1206
+ const change = textDocument.applyEdits(edits, true, selections, void 0, true);
1207
+ if (change === void 0) return;
1208
+ const nextSelections = remapSelections ? remapSelectionsAfterEdits(textDocument, selections, selectionOffsets, resolvedEdits) : resolveNextSelections(textDocument);
1209
+ textDocument.setLastUndoSelectionsAfter(nextSelections);
1210
+ this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
1211
+ }
1212
+ /** Copies selected line blocks and keeps the selection in the requested copy. */
1213
+ #copySelectedLines(direction) {
1214
+ const textDocument = this.#textDocument;
1215
+ const selections = this.#selections;
1216
+ if (textDocument === void 0 || selections === void 0) return;
1217
+ const blocks = getSelectedLineBlocks(selections);
1218
+ if (blocks.length === 0) return;
1219
+ const copiedLinesBefore = [];
1220
+ let copiedLineCount = 0;
1221
+ const edits = [];
1222
+ for (const block of blocks) {
1223
+ copiedLinesBefore.push(copiedLineCount);
1224
+ const blockLineCount = block.endLine - block.startLine + 1;
1225
+ copiedLineCount += blockLineCount;
1226
+ const text = textDocument.getText({
1227
+ start: {
1228
+ line: block.startLine,
1229
+ character: 0
1230
+ },
1231
+ end: {
1232
+ line: block.endLine,
1233
+ character: textDocument.getLineLength(block.endLine)
1234
+ }
1235
+ });
1236
+ if (direction > 0) {
1237
+ const position = {
1238
+ line: block.startLine,
1239
+ character: 0
1240
+ };
1241
+ edits.push({
1242
+ range: {
1243
+ start: position,
1244
+ end: position
1245
+ },
1246
+ newText: text + textDocument.eol
1247
+ });
1248
+ } else if (block.endLine < textDocument.lineCount - 1) {
1249
+ const position = {
1250
+ line: block.endLine + 1,
1251
+ character: 0
1252
+ };
1253
+ edits.push({
1254
+ range: {
1255
+ start: position,
1256
+ end: position
1257
+ },
1258
+ newText: text + textDocument.eol
1259
+ });
1260
+ } else {
1261
+ const position = {
1262
+ line: block.endLine,
1263
+ character: textDocument.getLineLength(block.endLine)
1264
+ };
1265
+ edits.push({
1266
+ range: {
1267
+ start: position,
1268
+ end: position
1269
+ },
1270
+ newText: textDocument.eol + text
1271
+ });
1272
+ }
1273
+ }
1274
+ const nextSelections = selections.map((selection) => {
1275
+ const line = selection.start.line;
1276
+ let low = 0;
1277
+ let high = blocks.length - 1;
1278
+ while (low <= high) {
1279
+ const middle = low + high >>> 1;
1280
+ const block = blocks[middle];
1281
+ if (line < block.startLine) high = middle - 1;
1282
+ else if (line > block.endLine) low = middle + 1;
1283
+ else {
1284
+ low = middle;
1285
+ break;
1286
+ }
1287
+ }
1288
+ const blockIndex = Math.max(0, low <= high ? low : high);
1289
+ const block = blocks[blockIndex];
1290
+ const shift = copiedLinesBefore[blockIndex] + (direction > 0 ? block.endLine - block.startLine + 1 : 0);
1291
+ return {
1292
+ start: {
1293
+ ...selection.start,
1294
+ line: selection.start.line + shift
1295
+ },
1296
+ end: {
1297
+ ...selection.end,
1298
+ line: selection.end.line + shift
1299
+ },
1300
+ direction: selection.direction
1301
+ };
1302
+ });
1303
+ this.#applyCommandEdits(edits, () => nextSelections);
1304
+ }
1305
+ /** Inserts an indented blank line after each selection's final line. */
1306
+ #insertBlankLine() {
1307
+ const textDocument = this.#textDocument;
1308
+ const selections = this.#selections;
1309
+ if (textDocument === void 0 || selections === void 0) return;
1310
+ const selectionLines = selections.map((selection) => getCaretPosition(selection).line);
1311
+ const targetLines = Array.from(new Set(selectionLines)).sort((a, b) => a - b);
1312
+ const targetIndex = /* @__PURE__ */ new Map();
1313
+ const indents = /* @__PURE__ */ new Map();
1314
+ const edits = [];
1315
+ for (let index = 0; index < targetLines.length; index++) {
1316
+ const line = targetLines[index];
1317
+ const lineText = textDocument.getLineText(line);
1318
+ const indent = /^\s*/.exec(lineText)?.[0] ?? "";
1319
+ targetIndex.set(line, index);
1320
+ indents.set(line, indent);
1321
+ const position = {
1322
+ line,
1323
+ character: textDocument.getLineLength(line)
1324
+ };
1325
+ edits.push({
1326
+ range: {
1327
+ start: position,
1328
+ end: position
1329
+ },
1330
+ newText: textDocument.eol + indent
1331
+ });
1332
+ }
1333
+ const nextSelections = selections.map((_, index) => {
1334
+ const line = selectionLines[index];
1335
+ const position = {
1336
+ line: line + 1 + targetIndex.get(line),
1337
+ character: indents.get(line).length
1338
+ };
1339
+ return {
1340
+ start: position,
1341
+ end: position,
1342
+ direction: 0
1343
+ };
1344
+ });
1345
+ this.#applyCommandEdits(edits, () => nextSelections);
1346
+ }
1347
+ #moveSelectedLines(direction) {
1348
+ const textDocument = this.#textDocument;
1349
+ const selections = this.#selections;
1350
+ if (textDocument === void 0 || selections === void 0) return;
1351
+ const blocks = getSelectedLineBlocks(selections);
1352
+ if (blocks.length === 0 || direction < 0 && blocks[0].startLine === 0 || direction > 0 && blocks.at(-1).endLine >= textDocument.lineCount - 1) return;
1353
+ const lineCount = textDocument.lineCount;
1354
+ const lineRangeEnd = (line) => line < lineCount - 1 ? {
1355
+ line: line + 1,
1356
+ character: 0
1357
+ } : {
1358
+ line,
1359
+ character: textDocument.getLineLength(line)
1360
+ };
1361
+ const getLinesText = (lines, appendFinalLineBreak) => {
1362
+ const text = lines.map((line) => textDocument.getLineText(line)).join(textDocument.eol);
1363
+ return appendFinalLineBreak ? text + textDocument.eol : text;
1364
+ };
1365
+ const edits = [];
1366
+ if (direction < 0) for (const block of blocks) {
1367
+ const previousLine = block.startLine - 1;
1368
+ const blockLines = [];
1369
+ for (let line = block.startLine; line <= block.endLine; line++) blockLines.push(line);
1370
+ edits.push({
1371
+ range: {
1372
+ start: {
1373
+ line: previousLine,
1374
+ character: 0
1375
+ },
1376
+ end: lineRangeEnd(block.endLine)
1377
+ },
1378
+ newText: getLinesText([...blockLines, previousLine], block.endLine < lineCount - 1)
1379
+ });
1380
+ }
1381
+ else for (let index = blocks.length - 1; index >= 0; index--) {
1382
+ const block = blocks[index];
1383
+ const nextLine = block.endLine + 1;
1384
+ const blockLines = [];
1385
+ for (let line = block.startLine; line <= block.endLine; line++) blockLines.push(line);
1386
+ edits.push({
1387
+ range: {
1388
+ start: {
1389
+ line: block.startLine,
1390
+ character: 0
1391
+ },
1392
+ end: lineRangeEnd(nextLine)
1393
+ },
1394
+ newText: getLinesText([nextLine, ...blockLines], nextLine < lineCount - 1)
1395
+ });
1396
+ }
1397
+ const lastBlock = blocks.at(-1);
1398
+ const lastLineLengthAfterMove = direction > 0 && lastBlock.endLine === lineCount - 2 ? textDocument.getLineLength(lastBlock.endLine) : textDocument.getLineLength(lineCount - 1);
1399
+ const nextSelections = selections.map((selection) => shiftSelectionLines(selection, direction, lineCount, (line) => line === lineCount - 1 ? lastLineLengthAfterMove : textDocument.getLineLength(line)));
1400
+ const change = textDocument.applyEdits(edits, true, selections, nextSelections, true);
1401
+ if (change !== void 0) this.#applyChange(change, nextSelections);
1402
+ }
1403
+ #handleLayoutResize = () => {
1404
+ const lineAnnotations = this.#lineAnnotations?.length ?? 0;
1405
+ const prevGutterWidth = this.#gutterWidthCache;
1406
+ const prevContentWidth = this.#contentWidthCache;
1407
+ this.#gutterWidthCache = void 0;
1408
+ this.#contentWidthCache = void 0;
1409
+ const gutterWidthChanged = this.#getGutterWidth() !== prevGutterWidth;
1410
+ const contentWidthChanged = this.#getContentWidth() !== prevContentWidth;
1411
+ if (!gutterWidthChanged && !contentWidthChanged) return;
1412
+ this.#lineElementsCache.clear();
1413
+ this.#lastAccessedCharX = void 0;
1414
+ this.#metrics.clearTextWidthCache();
1415
+ if (contentWidthChanged && (this.#isWrap || lineAnnotations > 0)) {
1416
+ this.#lineYCache.clear();
1417
+ this.#wrapLineOffsetsCache.clear();
1418
+ }
1419
+ if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) {
1420
+ this.#updateSelections(this.#selections ?? []);
1421
+ if (this.#selections !== void 0) this.focus();
1422
+ }
1423
+ this.#markerRenderer?.removePopup();
1424
+ this.#computeContentOffset(this.#contentElement);
1425
+ };
1426
+ #remeasureMetricsOnFontLoad() {
1427
+ if (this.#fontRemeasureScheduled) return;
1428
+ const fonts = document.fonts;
1429
+ if (fonts === void 0) return;
1430
+ this.#fontRemeasureScheduled = true;
1431
+ fonts.ready.then(() => {
1432
+ if (this.#contentElement === void 0 || !this.#metrics.remeasureCharacterWidth()) return;
1433
+ this.#gutterWidthCache = void 0;
1434
+ this.#contentWidthCache = void 0;
1435
+ this.#resetCache();
1436
+ if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
1437
+ this.#markerRenderer?.removePopup();
1438
+ });
1439
+ }
1440
+ #rerender(change, newLineAnnotations, renderRange = this.#renderRange, shouldUpdateBuffer) {
1441
+ const tokenizer = this.#tokenizer;
1442
+ const fileInstance = this.#fileInstance;
1443
+ const textDocument = this.#textDocument;
1444
+ const gutterEl = this.#gutterElement;
1445
+ const contentEl = this.#contentElement;
1446
+ if (tokenizer === void 0 || fileInstance === void 0 || textDocument === void 0 || contentEl === void 0) return;
1447
+ tokenizer.stopBackgroundTokenize();
1448
+ const t = performance.now();
1449
+ const dirtyLines = tokenizer.tokenize(change, renderRange);
1450
+ const t2 = performance.now();
1451
+ if (dirtyLines.size > 0) {
1452
+ const children = contentEl.children;
1453
+ const dirtyLineIndexes = /* @__PURE__ */ new Set();
1454
+ for (const lineIndex of dirtyLines.keys()) if (this.#isLineRenderable(lineIndex)) dirtyLineIndexes.add(lineIndex);
1455
+ for (let i = 0; i < children.length; i++) {
1456
+ const child = children[i];
1457
+ if (child !== void 0) {
1458
+ const lineNumber = getLineNumberAttr(child);
1459
+ const lineType = child.dataset.lineType;
1460
+ if (lineNumber === void 0 || lineType === "change-deletion") continue;
1461
+ const lineIndex = lineNumber - 1;
1462
+ if (dirtyLineIndexes.has(lineIndex)) {
1463
+ const tokens = dirtyLines.get(lineIndex);
1464
+ child.replaceChildren(...renderLineTokens(tokens));
1465
+ dirtyLineIndexes.delete(lineIndex);
1466
+ if (dirtyLineIndexes.size === 0) break;
1467
+ }
1468
+ }
1469
+ }
1470
+ if (dirtyLineIndexes.size > 0) {
1471
+ let lastRenderedLineNumber = 0;
1472
+ for (let i = children.length - 1; i >= 0; i--) {
1473
+ const child = children[i];
1474
+ const lineNumber = getLineNumberAttr(child);
1475
+ if (lineNumber !== void 0 && child.dataset.lineType !== "change-deletion") {
1476
+ lastRenderedLineNumber = lineNumber;
1477
+ break;
1478
+ }
1479
+ }
1480
+ for (const lineIndex of dirtyLineIndexes) {
1481
+ if (lineIndex < lastRenderedLineNumber) continue;
1482
+ const tokens = dirtyLines.get(lineIndex);
1483
+ const lineNumber = String(lineIndex + 1);
1484
+ h("div", {
1485
+ dataset: {
1486
+ line: lineNumber,
1487
+ lineType: "context",
1488
+ lineIndex: lineIndex.toString()
1489
+ },
1490
+ children: renderLineTokens(tokens)
1491
+ }, contentEl);
1492
+ if (gutterEl !== void 0) h("div", {
1493
+ dataset: {
1494
+ lineType: "context",
1495
+ columnNumber: lineNumber,
1496
+ lineIndex: lineIndex.toString()
1497
+ },
1498
+ children: [h("span", {
1499
+ dataset: { lineNumberContent: "" },
1500
+ textContent: lineNumber
1501
+ })]
1502
+ }, gutterEl);
1503
+ }
1504
+ }
1505
+ }
1506
+ if (change.lineDelta < 0) for (const children of [contentEl.children, gutterEl?.children ?? []]) for (let i = children.length - 1; i >= 0; i--) {
1507
+ const child = children[i];
1508
+ const lineNumber = getLineNumberAttr(child) ?? getLineNumberAttr(child, "columnNumber");
1509
+ const lineType = child.dataset.lineType;
1510
+ if (lineNumber === void 0 || lineType === "change-deletion") continue;
1511
+ if (lineNumber - 1 < change.lineCount) break;
1512
+ child.remove();
1513
+ }
1514
+ const didLineCountChange = change.lineDelta !== 0;
1515
+ if (didLineCountChange) {
1516
+ let gridRow = contentEl.children.length;
1517
+ for (const child of contentEl.children) {
1518
+ const { bufferSize } = child.dataset;
1519
+ if (bufferSize !== void 0) gridRow += parseInt(bufferSize) - 1;
1520
+ }
1521
+ contentEl.style.gridRow = "span " + gridRow;
1522
+ if (gutterEl !== void 0) gutterEl.style.gridRow = "span " + gridRow;
1523
+ }
1524
+ fileInstance.updateRenderCache(dirtyLines, tokenizer.themeType, !didLineCountChange, didLineCountChange);
1525
+ if (didLineCountChange) fileInstance.applyDocumentChange(textDocument, newLineAnnotations, shouldUpdateBuffer);
1526
+ if (this.#isDiff && (this.#diffSyle === "unified" || didLineCountChange)) this.#resetCache();
1527
+ if (newLineAnnotations !== void 0) {
1528
+ this.#lineAnnotations = newLineAnnotations;
1529
+ renderLineAnnotations(newLineAnnotations, contentEl, gutterEl);
1530
+ }
1531
+ 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)`);
1532
+ }
1533
+ #handleInput(inputType, data) {
1534
+ switch (inputType) {
1535
+ case "insertText": {
1536
+ const text = data ?? "";
1537
+ const textDocument = this.#textDocument;
1538
+ const selections = this.#selections;
1539
+ const autoSurroundTexts = textDocument !== void 0 && selections !== void 0 ? getAutoSurroundReplacementTexts(textDocument, selections, text, this.#options.autoSurround) : void 0;
1540
+ this.#replaceSelectionText(autoSurroundTexts ?? text);
1541
+ break;
1542
+ }
1543
+ case "insertCompositionText": break;
1544
+ case "insertLineBreak":
1545
+ case "insertParagraph":
1546
+ this.#replaceSelectionText(this.#textDocument?.eol ?? "\n");
1547
+ break;
1548
+ case "deleteContentBackward":
1549
+ this.#deleteSelectionText();
1550
+ break;
1551
+ case "deleteContentForward":
1552
+ this.#deleteSelectionText(true);
1553
+ break;
1554
+ case "deleteSoftLineBackward":
1555
+ case "deleteHardLineBackward":
1556
+ this.#deleteSoftLineBackward();
1557
+ break;
1558
+ case "deleteWordBackward":
1559
+ this.#deleteWordBackward();
1560
+ break;
1561
+ case "insertTranspose":
1562
+ this.#insertTranspose();
1563
+ break;
1564
+ default:
1565
+ console.warn(`[diffs] Unknown input type: ${inputType}`, data);
1566
+ break;
1567
+ }
1568
+ }
1569
+ #focus(position, preventScroll = true) {
1570
+ this.#contentHasFocus = true;
1571
+ if (position !== void 0) {
1572
+ this.#shouldIgnoreSelectionChange = true;
1573
+ this.#setWindowSelection({
1574
+ start: position,
1575
+ end: position,
1576
+ direction: 0
1577
+ });
1578
+ requestAnimationFrame(() => {
1579
+ this.#contentElement?.focus({ preventScroll });
1580
+ requestAnimationFrame(() => {
1581
+ this.#shouldIgnoreSelectionChange = false;
1582
+ });
1583
+ });
1584
+ } else this.#contentElement?.focus({ preventScroll });
1585
+ }
1586
+ #setWindowSelection(selection) {
1587
+ const winSelection = window.getSelection();
1588
+ if (winSelection === null) return;
1589
+ let { start, end, direction } = selection;
1590
+ if (comparePosition(start, end) > 0) [start, end] = [end, start];
1591
+ const startLineElement = this.#getLineElement(start.line);
1592
+ const endLineElement = this.#getLineElement(end.line);
1593
+ if (startLineElement === void 0 || endLineElement === void 0) return;
1594
+ let [anchorNode, anchorOffset] = getSelectionAnchor(startLineElement, start.character);
1595
+ let [focusNode, focusOffset] = getSelectionAnchor(endLineElement, end.character);
1596
+ if (direction === -1) [anchorNode, anchorOffset, focusNode, focusOffset] = [
1597
+ focusNode,
1598
+ focusOffset,
1599
+ anchorNode,
1600
+ anchorOffset
1601
+ ];
1602
+ try {
1603
+ winSelection.setBaseAndExtent(anchorNode, clampDomOffset(anchorNode, anchorOffset), focusNode, clampDomOffset(focusNode, focusOffset));
1604
+ } catch (err) {
1605
+ console.error("[diffs/editor] failed to update window selection:", err);
1606
+ }
1607
+ }
1608
+ #scrollToPrimaryCaret(noFocus = false, scrollPosition = "nearest") {
1609
+ const primarySelection = this.#selections?.at(-1);
1610
+ if (primarySelection === void 0) return;
1611
+ const primaryCaretElement = this.#primaryCaretElement;
1612
+ if (primaryCaretElement !== void 0) {
1613
+ primaryCaretElement.scrollIntoView({
1614
+ block: scrollPosition,
1615
+ inline: "nearest"
1616
+ });
1617
+ if (!noFocus) this.#focus(primarySelection.direction === -1 ? primarySelection.end : primarySelection.start);
1618
+ } else {
1619
+ const pos = getCaretPosition(primarySelection);
1620
+ this.#scrollToLine(pos.line, pos.character, noFocus);
1621
+ }
1622
+ }
1623
+ #getScrollMargin() {
1624
+ const top = this.#searchPanel !== void 0 ? 48 : 0;
1625
+ const start = this.#getGutterWidth() + this.#metrics.ch;
1626
+ return `${top}px ${this.#metrics.ch}px 0 ${start}px`;
1627
+ }
1628
+ #scrollToLine(line, char = 0, noFocus = false) {
1629
+ this.__postponeBgTokenizeToNextFrame();
1630
+ const virtualCaret = h("div", { style: {
1631
+ position: "absolute",
1632
+ left: "0",
1633
+ width: "2px",
1634
+ height: this.#metrics.lineHeight + "px",
1635
+ scrollMargin: this.#getScrollMargin()
1636
+ } });
1637
+ if (this.#getLineElement(line) !== void 0) {
1638
+ const [left, wrapLine] = this.#getCharX(line, char);
1639
+ const lineY = this.#getLineY(line) + wrapLine * this.#metrics.lineHeight;
1640
+ virtualCaret.style.top = lineY + "px";
1641
+ virtualCaret.style.left = left + "px";
1642
+ this.#overlayElement?.appendChild(virtualCaret);
1643
+ virtualCaret.scrollIntoView({
1644
+ block: "center",
1645
+ inline: "nearest"
1646
+ });
1647
+ if (!noFocus) this.#focus({
1648
+ line,
1649
+ character: char
1650
+ });
1651
+ this.#scrollingToLine = void 0;
1652
+ this.#scrollingToLineChar = void 0;
1653
+ this.#scrollingToLineFixed = false;
1654
+ this.#scrollingToLineNoFocus = false;
1655
+ } else {
1656
+ const modelLinePosition = this.#fileInstance?.getLinePosition?.(line + 1);
1657
+ if (modelLinePosition !== void 0) {
1658
+ virtualCaret.style.top = modelLinePosition.top + "px";
1659
+ this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
1660
+ virtualCaret.scrollIntoView({
1661
+ block: "center",
1662
+ inline: "nearest"
1663
+ });
1664
+ if (modelLinePosition.height > 0 && this.#isLineRenderable(line)) {
1665
+ this.#scrollingToLine = line;
1666
+ this.#scrollingToLineChar = char;
1667
+ this.#scrollingToLineNoFocus = noFocus;
1668
+ } else {
1669
+ this.#scrollingToLine = void 0;
1670
+ this.#scrollingToLineChar = void 0;
1671
+ this.#scrollingToLineNoFocus = false;
1672
+ }
1673
+ } else {
1674
+ let yFix = 0;
1675
+ if (this.#scrollingToLine === line && this.#contentElement !== void 0) for (let i = this.#contentElement.childElementCount - 1; i >= 0; i--) {
1676
+ const child = this.#contentElement.children[i];
1677
+ const lineType = child.dataset.lineType;
1678
+ const lineNumber = getLineNumberAttr(child);
1679
+ if (lineType !== void 0 && isLineEditable(lineType) && lineNumber !== void 0) {
1680
+ yFix = (line - (lineNumber - 1)) * this.#metrics.lineHeight;
1681
+ break;
1682
+ }
1683
+ }
1684
+ const approximateLineY = ((this.#lineAnnotations ?? []).filter((annotation) => annotation.lineNumber < line).length + line) * this.#metrics.lineHeight + yFix;
1685
+ virtualCaret.style.top = approximateLineY + "px";
1686
+ this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
1687
+ virtualCaret.scrollIntoView({
1688
+ block: "center",
1689
+ inline: "nearest"
1690
+ });
1691
+ if (this.#scrollingToLine === line && (yFix === 0 || this.#scrollingToLineFixed) || !this.#isLineRenderable(line)) {
1692
+ this.#scrollingToLine = void 0;
1693
+ this.#scrollingToLineChar = void 0;
1694
+ this.#scrollingToLineFixed = false;
1695
+ this.#scrollingToLineNoFocus = false;
1696
+ } else {
1697
+ this.#scrollingToLine = line;
1698
+ this.#scrollingToLineChar = char;
1699
+ this.#scrollingToLineFixed = yFix !== 0;
1700
+ this.#scrollingToLineNoFocus = noFocus;
1701
+ }
1702
+ }
1703
+ }
1704
+ virtualCaret.remove();
1705
+ }
1706
+ #spanLineSelection(anchorLine, focusLine, textDocument) {
1707
+ const lineStart = (line) => ({
1708
+ line,
1709
+ character: 0
1710
+ });
1711
+ const lineEnd = (line) => ({
1712
+ line,
1713
+ character: textDocument.getLineText(line).length
1714
+ });
1715
+ if (focusLine < anchorLine) return {
1716
+ start: lineStart(focusLine),
1717
+ end: lineEnd(anchorLine),
1718
+ direction: -1
1719
+ };
1720
+ return {
1721
+ start: lineStart(anchorLine),
1722
+ end: lineEnd(focusLine),
1723
+ direction: 1
1724
+ };
1725
+ }
1726
+ #isDeletedLineTarget(event) {
1727
+ const target = event.composedPath()[0];
1728
+ return target instanceof HTMLElement && target.closest("[data-line]")?.getAttribute("data-line-type") === "change-deletion";
1729
+ }
1730
+ #selectDeletedLines(anchorContent, focusContent, deletionsCode) {
1731
+ const rows = [...deletionsCode.querySelectorAll("[data-content] > [data-line]")];
1732
+ const anchorIndex = rows.indexOf(anchorContent);
1733
+ const focusIndex = rows.indexOf(focusContent);
1734
+ const [topContent, bottomContent] = focusIndex < anchorIndex ? [focusContent, anchorContent] : [anchorContent, focusContent];
1735
+ const winSelection = window.getSelection();
1736
+ if (winSelection !== null) {
1737
+ const range = document.createRange();
1738
+ range.setStart(topContent, 0);
1739
+ range.setEnd(bottomContent, bottomContent.childNodes.length);
1740
+ winSelection.removeAllRanges();
1741
+ winSelection.addRange(range);
1742
+ }
1743
+ this.#setDeletedTextSelectionActive(true);
1744
+ this.#updateSelections([]);
1745
+ [...deletionsCode.querySelectorAll("[data-gutter] > [data-column-number]")][focusIndex]?.setAttribute("data-selected-line", "single");
1746
+ const lo = Math.min(anchorIndex, focusIndex);
1747
+ const hi = Math.max(anchorIndex, focusIndex);
1748
+ this.#deletedSelectionText = lo < 0 ? "" : rows.slice(lo, hi + 1).filter((row) => row.getAttribute("data-line-type") === "change-deletion").map((row) => row.textContent ?? "").join("\n");
1749
+ }
1750
+ #contentRowForGutterRow(gutterRow, contentColumn) {
1751
+ const gutterColumn = gutterRow.parentElement;
1752
+ if (gutterColumn == null || contentColumn == null) return;
1753
+ const index = [...gutterColumn.children].indexOf(gutterRow);
1754
+ const row = contentColumn.children[index];
1755
+ return row instanceof HTMLElement ? row : void 0;
1756
+ }
1757
+ #beginDeletionGutterSelection(gutterRow, code, isMouse) {
1758
+ const contentColumn = code.querySelector("[data-content]");
1759
+ const anchorContent = this.#contentRowForGutterRow(gutterRow, contentColumn);
1760
+ if (anchorContent === void 0) return false;
1761
+ this.#selectDeletedLines(anchorContent, anchorContent, code);
1762
+ if (isMouse) this.#replaceSelectEventListeners([addEventListener(document, "mousemove", (moveEvent) => {
1763
+ const moveTarget = moveEvent.composedPath()[0];
1764
+ const moveGutter = moveTarget instanceof HTMLElement ? moveTarget.closest("[data-column-number]") : null;
1765
+ if (moveGutter instanceof HTMLElement && code.contains(moveGutter)) {
1766
+ const focusContent = this.#contentRowForGutterRow(moveGutter, contentColumn);
1767
+ if (focusContent !== void 0) this.#selectDeletedLines(anchorContent, focusContent, code);
1768
+ }
1769
+ }, { passive: true })]);
1770
+ return true;
1771
+ }
1772
+ #isDeletedTextSelectionActive() {
1773
+ return this.#fileContainer?.shadowRoot?.querySelector("pre")?.hasAttribute("data-deleted-text-selection") ?? false;
1774
+ }
1775
+ #deletedTextForClipboard() {
1776
+ return this.#deletedSelectionText !== "" ? this.#deletedSelectionText : window.getSelection()?.toString() ?? "";
1777
+ }
1778
+ #setDeletedTextSelectionActive(active) {
1779
+ const pre = this.#fileContainer?.shadowRoot?.querySelector("pre");
1780
+ if (pre == null) return;
1781
+ if (active) {
1782
+ pre.setAttribute("data-deleted-text-selection", "");
1783
+ for (const highlighted of pre.querySelectorAll("[data-selected-line]")) highlighted.removeAttribute("data-selected-line");
1784
+ this.#deletedSelectionText = "";
1785
+ } else {
1786
+ pre.removeAttribute("data-deleted-text-selection");
1787
+ this.#deletedSelectionText = "";
1788
+ }
1789
+ }
1790
+ #setSelectedLinesSafe(range, lineNumberOnly = false) {
1791
+ try {
1792
+ this.#fileInstance?.setSelectedLines(range, {
1793
+ notify: false,
1794
+ activeLineSide: "additions",
1795
+ lineNumberOnly
1796
+ });
1797
+ } catch {}
1798
+ }
1799
+ #scheduleThemeSelectionRefresh() {
1800
+ if (this.#themeSelectionRefreshFrame !== void 0) return;
1801
+ this.#themeSelectionRefreshFrame = requestAnimationFrame(() => {
1802
+ this.#themeSelectionRefreshFrame = void 0;
1803
+ if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
1804
+ });
1805
+ }
1806
+ #updateSelections(selections) {
1807
+ this.__postponeBgTokenizeToNextFrame();
1808
+ this.#primaryCaretElement = void 0;
1809
+ this.#setSelectedLinesSafe(null);
1810
+ if (selections.length === 0 && this.#matches === void 0 && this.#markerRenderer === void 0) {
1811
+ this.#selections = void 0;
1812
+ this.#overlayElements?.forEach((el) => el.remove());
1813
+ this.#overlayElements?.clear();
1814
+ this.#selectionAction?.cleanup();
1815
+ this.#selectionAction = void 0;
1816
+ return;
1817
+ }
1818
+ const fragment = document.createDocumentFragment();
1819
+ const renderCtx = {
1820
+ fragment,
1821
+ elements: /* @__PURE__ */ new Map()
1822
+ };
1823
+ if (selections.length > 0) {
1824
+ const normalizedSelections = mergeOverlappingSelections(selections);
1825
+ const primarySelection = normalizedSelections.at(-1);
1826
+ this.#selections = normalizedSelections;
1827
+ const hasNonEmptySelection = normalizedSelections.some((selection) => !isCollapsedSelection(selection));
1828
+ const caretLine = getCaretPosition(primarySelection).line + 1;
1829
+ this.#setSelectedLinesSafe({
1830
+ start: caretLine,
1831
+ end: caretLine
1832
+ }, hasNonEmptySelection);
1833
+ for (const selection of normalizedSelections) {
1834
+ if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, "selection", selection);
1835
+ this.#renderCaret(renderCtx, selection, selection === primarySelection);
1836
+ }
1837
+ const bracketMatchRanges = this.#options.matchBrackets !== false && this.#textDocument !== void 0 && this.#tokenizer !== void 0 && isCollapsedSelection(primarySelection) ? findBracketMatchRanges(this.#textDocument, this.#tokenizer, primarySelection.start) : void 0;
1838
+ if (bracketMatchRanges !== void 0) for (const range of bracketMatchRanges) this.#renderSelection(renderCtx, "bracketMatch", range);
1839
+ }
1840
+ const textDocument = this.#textDocument;
1841
+ if (this.#matches !== void 0 && textDocument !== void 0) {
1842
+ const primarySelection = this.#selections?.at(-1);
1843
+ const primaryStartOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.start) : -1;
1844
+ const primaryEndOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.end) : -1;
1845
+ for (const [startOffset, endOffset] of this.#matches) {
1846
+ const range = {
1847
+ start: textDocument.positionAt(startOffset),
1848
+ end: textDocument.positionAt(endOffset)
1849
+ };
1850
+ const isFocused = primaryStartOffset === startOffset && primaryEndOffset === endOffset;
1851
+ this.#renderSelection(renderCtx, "match", range, isFocused ? "focus" : void 0);
1852
+ }
1853
+ }
1854
+ if (this.#markerRenderer !== void 0 && textDocument !== void 0) for (const marker of this.#markerRenderer.markers) this.#renderSelection(renderCtx, "marker", marker, markerSeverityDatasetKey(marker.severity));
1855
+ this.#overlayElement?.appendChild(fragment);
1856
+ this.#overlayElements?.forEach((el) => el.remove());
1857
+ this.#overlayElements?.clear();
1858
+ this.#overlayElements = renderCtx.elements;
1859
+ this.#updateSelectionActionPopover();
1860
+ }
1861
+ #renderSelection(renderCtx, type, range, extraDataset) {
1862
+ if (this.#textDocument === void 0) return;
1863
+ const { start, end } = range;
1864
+ for (let line = start.line; line <= end.line; line++) {
1865
+ if (!this.#isLineVisible(line)) continue;
1866
+ const isLastLine = line === end.line;
1867
+ const startChar = line === start.line ? start.character : 0;
1868
+ const endChar = isLastLine ? end.character : this.#textDocument.getLineLength(line);
1869
+ if (this.#isWrap) {
1870
+ const contentWidth = this.#getContentWidth();
1871
+ const lineText = this.#textDocument.getLineText(line);
1872
+ if (2 * this.#metrics.ch + this.#metrics.measureTextWidth(lineText) > contentWidth) {
1873
+ this.#renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset);
1874
+ continue;
1875
+ }
1876
+ }
1877
+ let left = 0;
1878
+ let width = 0;
1879
+ let paddingEnd = 0;
1880
+ if (startChar === 0) left = this.#getGutterWidth() + this.#metrics.ch + (this.#activeContentOffset?.left ?? 0);
1881
+ else left = this.#getCharX(line, startChar)[0];
1882
+ if (!isLastLine && type === "selection") paddingEnd = this.#metrics.ch;
1883
+ if (startChar === endChar) width = paddingEnd;
1884
+ else width = this.#getCharX(line, endChar)[0] - left + paddingEnd;
1885
+ this.#renderSelectionBlock(renderCtx, type, line, 0, left, width, extraDataset);
1886
+ }
1887
+ }
1888
+ #renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset) {
1889
+ const wrapOffsets = this.#wrapLineText(line);
1890
+ const segmentCount = wrapOffsets.length - 1;
1891
+ const offsetLeft = this.#getGutterWidth() + this.#metrics.ch + (this.#activeContentOffset?.left ?? 0);
1892
+ for (let wrapLine = 0; wrapLine < segmentCount; wrapLine++) {
1893
+ const segmentStart = wrapOffsets[wrapLine];
1894
+ const segmentEnd = wrapOffsets[wrapLine + 1];
1895
+ const wrapStartChar = Math.max(startChar, segmentStart);
1896
+ const wrapEndChar = Math.min(endChar, segmentEnd);
1897
+ if (wrapStartChar > wrapEndChar) continue;
1898
+ const segmentStartWidth = this.#segmentTextWidth(lineText, segmentStart, wrapStartChar);
1899
+ const segmentLeft = offsetLeft + segmentStartWidth;
1900
+ let paddingEnd = 0;
1901
+ if (!isLastLine && wrapLine === segmentCount - 1 && type === "selection") paddingEnd = this.#metrics.ch;
1902
+ const segmentWidth = wrapStartChar === wrapEndChar ? paddingEnd : this.#segmentTextWidth(lineText, segmentStart, wrapEndChar) - segmentStartWidth + paddingEnd;
1903
+ this.#renderSelectionBlock(renderCtx, type, line, wrapLine, segmentLeft, segmentWidth, extraDataset);
1904
+ }
1905
+ }
1906
+ #segmentTextWidth(lineText, segmentStart, character) {
1907
+ if (character <= segmentStart) return 0;
1908
+ const segmentText = lineText.slice(segmentStart, character);
1909
+ const asciiColumns = getExpandedAsciiTextColumns(segmentText, this.#metrics.tabSize);
1910
+ return asciiColumns !== -1 ? asciiColumns * this.#metrics.ch : this.#metrics.measureTextWidth(segmentText);
1911
+ }
1912
+ #renderSelectionBlock(renderCtx, type, line, wrapLine, left, width, extraDataset) {
1913
+ if (width === 0) return;
1914
+ const { ch, lineHeight } = this.#metrics;
1915
+ const y = this.#getLineY(line) + wrapLine * lineHeight;
1916
+ const cacheKey = `${type}-${line}/${wrapLine}-${left}-${width} ${extraDataset ?? ""}`;
1917
+ const overlayEls = this.#overlayElements;
1918
+ const rounded = (this.#options.roundedSelection ?? true) && type === "selection";
1919
+ const addRoundedCorner = (line, wrapLine, left, radius) => {
1920
+ const top = this.#getLineY(line) + wrapLine * lineHeight;
1921
+ const lineElement = this.#getLineElement(line);
1922
+ let cornerBg = "initial";
1923
+ if (this.#isDiff && lineElement?.dataset.lineType === "change-addition") cornerBg = getComputedStyle(lineElement).getPropertyValue("--diffs-line-bg");
1924
+ const css = `width:${ch}px;transform:translateX(${left}px) translateY(${top}px);--diffs-selection-corner-bg:${cornerBg}`;
1925
+ const dataset = {
1926
+ selectionCorner: "",
1927
+ [radius]: ""
1928
+ };
1929
+ const cacheKeyPrefix = `${type}-block-${line}/${wrapLine}-${left}-1ch`;
1930
+ let cacheKey = cacheKeyPrefix + "-" + radius;
1931
+ if (radius === "rbl") {
1932
+ const prevCornerKey = cacheKeyPrefix + "-rtl";
1933
+ const prevCorner = renderCtx.elements.get(prevCornerKey);
1934
+ if (prevCorner !== void 0) {
1935
+ prevCorner.remove();
1936
+ renderCtx.elements.delete(prevCornerKey);
1937
+ cacheKey += "-rtl";
1938
+ dataset.rtl = "";
1939
+ }
1940
+ }
1941
+ let cornerEl = renderCtx.elements.get(cacheKey);
1942
+ if (cornerEl !== void 0) return;
1943
+ if (overlayEls?.has(cacheKey) === true) {
1944
+ cornerEl = overlayEls.get(cacheKey);
1945
+ cornerEl.style.cssText = css;
1946
+ overlayEls.delete(cacheKey);
1947
+ } else cornerEl = h("div", {
1948
+ dataset: "selectionRange",
1949
+ style: { cssText: css },
1950
+ children: [h("div", { dataset })]
1951
+ }, renderCtx.fragment);
1952
+ renderCtx.elements.set(cacheKey, cornerEl);
1953
+ };
1954
+ const addRadiusStyle = (element) => {
1955
+ const end = left + width;
1956
+ const dataset = element.dataset;
1957
+ const previousSelectionRange = renderCtx.previousSelectionRange;
1958
+ if (previousSelectionRange === void 0 || previousSelectionRange.line !== line || previousSelectionRange.wrapLine !== wrapLine) renderCtx.previousSelectionRange = {
1959
+ element,
1960
+ line,
1961
+ wrapLine,
1962
+ left,
1963
+ width
1964
+ };
1965
+ if (previousSelectionRange === void 0 || end <= previousSelectionRange.left) [
1966
+ "rtl",
1967
+ "rtr",
1968
+ "rbl",
1969
+ "rbr"
1970
+ ].forEach((key) => {
1971
+ dataset[key] = "";
1972
+ });
1973
+ else {
1974
+ const prevLine = previousSelectionRange.line;
1975
+ const prevWrapLine = previousSelectionRange.wrapLine;
1976
+ const prevLeft = previousSelectionRange.left;
1977
+ const prevDataset = previousSelectionRange.element.dataset;
1978
+ const prevEnd = prevLeft + previousSelectionRange.width;
1979
+ if (prevLeft > left) addRoundedCorner(prevLine, prevWrapLine, prevLeft - ch, "rbr");
1980
+ delete prevDataset.rbl;
1981
+ delete dataset.rtl;
1982
+ delete dataset.rtr;
1983
+ if (end >= prevEnd) delete prevDataset.rbr;
1984
+ if (end > prevEnd) {
1985
+ addRoundedCorner(prevLine, prevWrapLine, prevEnd, "rbl");
1986
+ dataset.rtr = "";
1987
+ }
1988
+ if (end < prevEnd) addRoundedCorner(line, wrapLine, end, "rtl");
1989
+ if (left < prevLeft) dataset.rtl = "";
1990
+ dataset.rbl = "";
1991
+ dataset.rbr = "";
1992
+ }
1993
+ };
1994
+ let rangeEl = renderCtx.elements.get(cacheKey);
1995
+ if (rangeEl !== void 0) {
1996
+ if (rounded) addRadiusStyle(rangeEl);
1997
+ return;
1998
+ }
1999
+ if (overlayEls?.has(cacheKey) === true) {
2000
+ rangeEl = overlayEls.get(cacheKey);
2001
+ overlayEls.delete(cacheKey);
2002
+ } else rangeEl = h("div", { dataset: extraDataset ? [type + "Range", extraDataset] : type + "Range" }, renderCtx.fragment);
2003
+ rangeEl.style.width = `${width}px`;
2004
+ rangeEl.style.transform = `translateX(${left}px) translateY(${y}px)`;
2005
+ if (rounded) addRadiusStyle(rangeEl);
2006
+ renderCtx.elements.set(cacheKey, rangeEl);
2007
+ }
2008
+ #renderCaret(renderCtx, selection, isPrimary) {
2009
+ const { line, character } = getCaretPosition(selection);
2010
+ if (!this.#isLineVisible(line)) return;
2011
+ const [left, wrapLine] = this.#getCharX(line, character);
2012
+ const cacheKey = "caret-" + line + "/" + wrapLine + ":" + character;
2013
+ if (renderCtx.elements.has(cacheKey)) return;
2014
+ const caretEl = h("div", {
2015
+ dataset: "caret",
2016
+ style: { transform: `translateX(${left - 1}px) translateY(${this.#getLineY(line) + wrapLine * this.#metrics.lineHeight}px)` }
2017
+ }, renderCtx.fragment);
2018
+ renderCtx.elements.set(cacheKey, caretEl);
2019
+ if (isPrimary) {
2020
+ caretEl.style.scrollMargin = this.#getScrollMargin();
2021
+ this.#primaryCaretElement = caretEl;
2022
+ }
2023
+ }
2024
+ #updateSelectionActionPopover() {
2025
+ const primarySelection = this.#selections?.at(-1);
2026
+ const overlayElement = this.#overlayElement;
2027
+ const textDocument = this.#textDocument;
2028
+ const renderSelectionAction = this.#options.renderSelectionAction;
2029
+ const cleanup = () => {
2030
+ this.#selectionAction?.cleanup();
2031
+ this.#selectionAction = void 0;
2032
+ };
2033
+ if (this.#options.enabledSelectionAction !== true || renderSelectionAction === void 0 || primarySelection === void 0 || isCollapsedSelection(primarySelection) || this.#isContentMouseDown || overlayElement === void 0 || textDocument === void 0) {
2034
+ cleanup();
2035
+ return;
2036
+ }
2037
+ const head = getCaretPosition(primarySelection);
2038
+ if (!this.#isLineVisible(head.line)) {
2039
+ cleanup();
2040
+ return;
2041
+ }
2042
+ if (this.#selectionAction === void 0) {
2043
+ const getActiveSelection = () => this.#selections?.at(-1) ?? primarySelection;
2044
+ const selectionActionElement = renderSelectionAction({
2045
+ textDocument,
2046
+ get selection() {
2047
+ return getActiveSelection();
2048
+ },
2049
+ applyEdits: (edits) => this.applyEdits(edits),
2050
+ getSelectionText: () => this.#textDocument?.getText(getActiveSelection()) ?? "",
2051
+ replaceSelectionText: (text) => {
2052
+ this.#replaceSelectionText(text, [getActiveSelection()]);
2053
+ },
2054
+ close: () => {
2055
+ cleanup();
2056
+ this.#scrollToPrimaryCaret();
2057
+ }
2058
+ });
2059
+ this.#selectionAction = new SelectionActionWidget(selectionActionElement, overlayElement, () => this.#updateSelectionActionPopover());
2060
+ this.#getPopoverManager().resetPlacement(SELECTION_ACTION_POPOVER_PLACEMENT_KEY);
2061
+ }
2062
+ const lineHeight = this.#metrics.lineHeight;
2063
+ const isBackward = primarySelection.direction === -1;
2064
+ const preferred = {
2065
+ placeAbove: isBackward,
2066
+ anchor: head
2067
+ };
2068
+ const fallback = isBackward ? {
2069
+ placeAbove: false,
2070
+ anchor: primarySelection.end
2071
+ } : {
2072
+ placeAbove: true,
2073
+ anchor: primarySelection.start
2074
+ };
2075
+ const popoverHeight = this.#selectionAction.height;
2076
+ const candidateGeometry = (candidate) => {
2077
+ const [left, candidateWrapLine] = this.#getCharX(candidate.anchor.line, candidate.anchor.character);
2078
+ const rowTop = this.#getLineY(candidate.anchor.line) + candidateWrapLine * lineHeight;
2079
+ const anchorTop = candidate.placeAbove ? rowTop : rowTop + lineHeight;
2080
+ const top = candidate.placeAbove ? anchorTop - popoverHeight : anchorTop;
2081
+ return {
2082
+ top,
2083
+ bottom: top + popoverHeight,
2084
+ left,
2085
+ anchorTop
2086
+ };
2087
+ };
2088
+ const preferredGeometry = candidateGeometry(preferred);
2089
+ const fallbackGeometry = candidateGeometry(fallback);
2090
+ const lineCount = textDocument.lineCount;
2091
+ const atDocumentEdge = isBackward ? head.line < 3 : head.line >= lineCount - 3;
2092
+ const canUseFallback = this.#isLineVisible(fallback.anchor.line);
2093
+ const popoverManager = this.#getPopoverManager();
2094
+ const viewport = popoverManager.getPlacementBounds();
2095
+ const useFallback = canUseFallback && popoverManager.choosePlacement({
2096
+ preferred: preferredGeometry,
2097
+ fallback: fallbackGeometry,
2098
+ viewport,
2099
+ popoverHeight,
2100
+ atDocumentEdge,
2101
+ placementKey: SELECTION_ACTION_POPOVER_PLACEMENT_KEY
2102
+ }) === "fallback";
2103
+ if (!canUseFallback) popoverManager.setPlacement("preferred", SELECTION_ACTION_POPOVER_PLACEMENT_KEY);
2104
+ const { placeAbove } = useFallback ? fallback : preferred;
2105
+ const { left, anchorTop } = useFallback ? fallbackGeometry : preferredGeometry;
2106
+ this.#selectionAction.reposition(left, anchorTop, this.#getGutterWidth(), placeAbove, viewport);
2107
+ }
2108
+ #openSearchPanel(mode) {
2109
+ if (this.#searchPanel !== void 0) {
2110
+ this.#searchPanel.setMode(mode);
2111
+ return;
2112
+ }
2113
+ this.#renderSearchPanel(mode);
2114
+ }
2115
+ #renderSearchPanel(mode) {
2116
+ this.#searchPanel?.cleanup();
2117
+ const textDocument = this.#textDocument;
2118
+ const preElement = this.#fileContainer?.shadowRoot?.querySelector("pre");
2119
+ const selections = this.#selections;
2120
+ if (textDocument === void 0 || preElement == null) return;
2121
+ let defaultQuery = "";
2122
+ let initialMatch = void 0;
2123
+ if (selections !== void 0 && selections.length > 0) {
2124
+ let primarySelection = selections.at(-1);
2125
+ if (isCollapsedSelection(primarySelection)) {
2126
+ primarySelection = expandCollapsedSelectionToWord(textDocument, primarySelection);
2127
+ this.#updateSelections([...selections.slice(0, -1), primarySelection]);
2128
+ const selectionText = textDocument.getText(primarySelection);
2129
+ if (selectionText !== "" && !selectionText.includes("\n")) {
2130
+ defaultQuery = selectionText;
2131
+ initialMatch = [textDocument.offsetAt(primarySelection.start), textDocument.offsetAt(primarySelection.end)];
2132
+ }
2133
+ }
2134
+ }
2135
+ const scrollToMatch = ([startOffset, endOffset], retainFocus) => {
2136
+ const nextSelection = createSelectionFromAnchorAndFocusOffsets(textDocument, startOffset, endOffset);
2137
+ this.#updateSelections([nextSelection]);
2138
+ this.#revealLineIfCollapsed(getCaretPosition(nextSelection).line);
2139
+ this.#scrollToPrimaryCaret(true);
2140
+ this.#retainSearchPanelFocus = retainFocus;
2141
+ };
2142
+ const searchPanel = new SearchPanelWidget({
2143
+ textDocument,
2144
+ containerElement: preElement,
2145
+ defaultQuery,
2146
+ mode,
2147
+ initialMatch,
2148
+ scrollToMatch,
2149
+ applyReplace: (edits) => {
2150
+ if (edits.length === 0) return;
2151
+ const change = textDocument.applyEdits(edits.map((edit) => ({
2152
+ range: {
2153
+ start: textDocument.positionAt(edit.start),
2154
+ end: textDocument.positionAt(edit.end)
2155
+ },
2156
+ newText: edit.text
2157
+ })), true, this.#selections);
2158
+ if (change !== void 0) this.#applyChange(change, void 0, this.#applyChangeToLineAnnotations(change), { skipSearchRefresh: true });
2159
+ },
2160
+ onUpdate: (allMatches, options) => {
2161
+ if (allMatches.length === 0) {
2162
+ this.#matches = void 0;
2163
+ this.#updateSelections(this.#selections ?? []);
2164
+ return;
2165
+ }
2166
+ this.#matches = allMatches;
2167
+ if (options?.syncSelection === false) {
2168
+ this.#updateSelections(this.#selections ?? []);
2169
+ const primarySelection = this.#selections?.at(-1);
2170
+ if (primarySelection !== void 0) {
2171
+ const startOffset = textDocument.offsetAt(primarySelection.start);
2172
+ const endOffset = textDocument.offsetAt(primarySelection.end);
2173
+ for (const match of allMatches) if (match[0] === startOffset && match[1] === endOffset) return match;
2174
+ }
2175
+ return;
2176
+ }
2177
+ const primarySelection = this.#selections?.at(-1);
2178
+ let searchOffset = 0;
2179
+ let nextMatch;
2180
+ if (primarySelection !== void 0) searchOffset = textDocument.offsetAt(primarySelection.start);
2181
+ for (const m of allMatches) if (m[0] >= searchOffset) {
2182
+ nextMatch = m;
2183
+ break;
2184
+ }
2185
+ if (nextMatch !== void 0) scrollToMatch(nextMatch, true);
2186
+ else this.#updateSelections(this.#selections ?? []);
2187
+ return nextMatch;
2188
+ },
2189
+ onClose: () => {
2190
+ this.#searchPanel = void 0;
2191
+ this.#retainSearchPanelFocus = false;
2192
+ this.#matches = void 0;
2193
+ this.#updateSelections(this.#selections ?? []);
2194
+ }
2195
+ });
2196
+ this.#searchPanel = searchPanel;
2197
+ this.#retainSearchPanelFocus = false;
2198
+ }
2199
+ #getSelectionText() {
2200
+ const textDocument = this.#textDocument;
2201
+ const selections = this.#selections;
2202
+ if (textDocument === void 0 || selections === void 0) return "";
2203
+ return getSelectionText(textDocument, selections);
2204
+ }
2205
+ #getSelectionClipboardTexts() {
2206
+ const textDocument = this.#textDocument;
2207
+ const selections = this.#selections;
2208
+ if (textDocument === void 0 || selections === void 0) return [];
2209
+ return getSelectionClipboardTexts(textDocument, selections);
2210
+ }
2211
+ /** Writes both the portable text and the selection pairing metadata. */
2212
+ #writeSelectionClipboardData(clipboardData, text, selectionTexts) {
2213
+ if (clipboardData === null) return;
2214
+ clipboardData.setData("text", text);
2215
+ if (selectionTexts.length > 1) clipboardData.setData(MULTI_SELECTION_CLIPBOARD_TYPE, JSON.stringify(selectionTexts));
2216
+ }
2217
+ #cutSelectionText() {
2218
+ const textDocument = this.#textDocument;
2219
+ const selections = this.#selections;
2220
+ if (textDocument === void 0 || selections === void 0 || selections.length === 0) return "";
2221
+ if (selections.some((selection) => isCollapsedSelection(selection))) {
2222
+ const cut = resolveSelectionCut(textDocument, selections);
2223
+ this.#applySelectionCutEdits(cut.edits, cut.nextSelectionOffsets);
2224
+ return cut.text;
2225
+ }
2226
+ const text = getSelectionText(textDocument, selections);
2227
+ this.#replaceSelectionText("", void 0, true);
2228
+ return text;
2229
+ }
2230
+ #applySelectionCutEdits(edits, nextSelectionOffsets) {
2231
+ const textDocument = this.#textDocument;
2232
+ const selections = this.#selections;
2233
+ if (textDocument === void 0 || selections === void 0 || edits.length === 0) return;
2234
+ const change = textDocument.applyResolvedEdits(edits, true, selections, void 0, true);
2235
+ if (change === void 0) return;
2236
+ const nextSelections = nextSelectionOffsets.map((offset) => {
2237
+ const caret = textDocument.positionAt(offset);
2238
+ return {
2239
+ start: caret,
2240
+ end: caret,
2241
+ direction: 0
2242
+ };
2243
+ });
2244
+ textDocument.setLastUndoSelectionsAfter(nextSelections);
2245
+ this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2246
+ }
2247
+ #replaceSelectionText(text, selections = this.#selections, undoBoundary = false, textOrder = "selection") {
2248
+ if (selections === void 0) return;
2249
+ const textDocument = this.#textDocument;
2250
+ const primarySelection = selections.at(-1);
2251
+ if (textDocument === void 0 || primarySelection === void 0) return;
2252
+ const { nextSelections, change } = Array.isArray(text) && text.length === selections.length ? applyTextReplaceToSelections(textDocument, selections, text, this.#lineAnnotations, undoBoundary, textOrder) : applyTextChangeToSelections(textDocument, selections, {
2253
+ start: textDocument.offsetAt(primarySelection.start),
2254
+ end: textDocument.offsetAt(primarySelection.end),
2255
+ text: Array.isArray(text) ? text.join(textDocument.eol) : text
2256
+ }, this.#lineAnnotations, void 0, undoBoundary);
2257
+ if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2258
+ }
2259
+ #deleteSelectionText(forward = false) {
2260
+ const selections = this.#selections;
2261
+ const textDocument = this.#textDocument;
2262
+ if (selections === void 0 || textDocument === void 0) return;
2263
+ const { nextSelections, change } = applyDeleteCharacterToSelections(textDocument, selections, forward, this.#lineAnnotations, this.#metrics.tabSize);
2264
+ if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2265
+ }
2266
+ #deleteSoftLineBackward() {
2267
+ const selections = this.#selections;
2268
+ const textDocument = this.#textDocument;
2269
+ if (selections === void 0 || textDocument === void 0) return;
2270
+ const { nextSelections, change } = applyDeleteSoftLineBackwardToSelections(textDocument, selections, this.#isWrap ? (line, character) => {
2271
+ const wrapOffsets = this.#wrapLineText(line);
2272
+ for (let w = 0; w + 1 < wrapOffsets.length; w++) {
2273
+ const segmentStart = wrapOffsets[w];
2274
+ const segmentEnd = wrapOffsets[w + 1];
2275
+ if (character >= segmentStart && character <= segmentEnd) return segmentStart;
2276
+ }
2277
+ return 0;
2278
+ } : void 0, this.#lineAnnotations);
2279
+ if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2280
+ }
2281
+ #deleteWordBackward() {
2282
+ const selections = this.#selections;
2283
+ const textDocument = this.#textDocument;
2284
+ if (selections === void 0 || textDocument === void 0) return;
2285
+ const { nextSelections, change } = applyDeleteWordBackwardToSelections(textDocument, selections, this.#lineAnnotations);
2286
+ if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2287
+ }
2288
+ #deleteHardLineForward() {
2289
+ const selections = this.#selections;
2290
+ const textDocument = this.#textDocument;
2291
+ if (selections === void 0 || textDocument === void 0) return;
2292
+ const { nextSelections, change } = applyDeleteHardLineForwardToSelections(textDocument, selections, this.#lineAnnotations);
2293
+ if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2294
+ }
2295
+ #insertTranspose() {
2296
+ const selections = this.#selections;
2297
+ const textDocument = this.#textDocument;
2298
+ if (selections === void 0 || textDocument === void 0) return;
2299
+ const { nextSelections, change } = applyTransposeToSelections(textDocument, selections, this.#lineAnnotations);
2300
+ if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2301
+ }
2302
+ #applyChange(change, newSelections, newLineAnnotations, options) {
2303
+ const fileRef = this.getFile();
2304
+ const onChange = this.#options.onChange;
2305
+ if (fileRef !== void 0 && onChange !== void 0) onChange(fileRef, newLineAnnotations ?? this.#lineAnnotations);
2306
+ if (change.lineDelta !== 0 || this.#isWrap) {
2307
+ for (const line of this.#lineYCache.keys()) if (line >= change.startLine) this.#lineYCache.delete(line);
2308
+ }
2309
+ if (this.#isWrap) {
2310
+ for (const line of this.#wrapLineOffsetsCache.keys()) if (line >= change.startLine) this.#wrapLineOffsetsCache.delete(line);
2311
+ }
2312
+ this.#lastAccessedCharX = void 0;
2313
+ let renderRange = this.#renderRange;
2314
+ let shouldUpdateBuffer;
2315
+ if (renderRange !== void 0 && newSelections !== void 0 && newSelections.length > 0) {
2316
+ const primarySelection = newSelections.at(-1);
2317
+ const renderRangeEndLine = renderRange.startingLine + renderRange.totalLines;
2318
+ if (primarySelection.end.line >= renderRangeEndLine) {
2319
+ const widenedTotalLines = primarySelection.end.line - renderRange.startingLine + 1;
2320
+ const maxWidenLines = this.#viewportWindowLines === void 0 || this.#viewportWindowLines === Infinity ? Infinity : this.#viewportWindowLines * MAX_EDIT_WIDEN_WINDOW_MULTIPLE;
2321
+ if (change.startLine <= renderRangeEndLine && widenedTotalLines <= maxWidenLines && this.#isLineRenderable(primarySelection.end.line)) {
2322
+ if (primarySelection.end.line > renderRangeEndLine) shouldUpdateBuffer = true;
2323
+ renderRange = {
2324
+ ...renderRange,
2325
+ totalLines: widenedTotalLines
2326
+ };
2327
+ this.#renderRange = renderRange;
2328
+ } else shouldUpdateBuffer = true;
2329
+ }
2330
+ }
2331
+ this.#rerender(change, newLineAnnotations, renderRange, shouldUpdateBuffer);
2332
+ if (options?.skipSearchRefresh !== true && this.#searchPanel !== void 0 && this.#matches !== void 0) this.#searchPanel.updateMatches({ syncSelection: false });
2333
+ if (newSelections !== void 0) {
2334
+ this.#updateSelections(newSelections);
2335
+ if (options?.skipFocus !== true) {
2336
+ const revealTarget = newSelections.at(-1);
2337
+ if (revealTarget !== void 0) this.#revealLineIfCollapsed(getCaretPosition(revealTarget).line);
2338
+ if (this.#primaryCaretElement !== void 0) requestAnimationFrame(() => {
2339
+ this.#primaryCaretElement?.scrollIntoView({
2340
+ block: "nearest",
2341
+ inline: "nearest"
2342
+ });
2343
+ });
2344
+ else if (newSelections.length > 0) {
2345
+ const pos = getCaretPosition(newSelections.at(-1));
2346
+ this.#scrollToLine(pos.line, pos.character);
2347
+ }
2348
+ this.focus({ preventScroll: true });
2349
+ }
2350
+ }
2351
+ }
2352
+ #applyChangeToLineAnnotations(change) {
2353
+ if (this.#lineAnnotations !== void 0) {
2354
+ const nextLineAnnotations = applyDocumentChangeToLineAnnotations(change, this.#lineAnnotations);
2355
+ if (nextLineAnnotations !== void 0) {
2356
+ this.#textDocument?.setLastUndoLineAnnotations(this.#lineAnnotations, nextLineAnnotations);
2357
+ return nextLineAnnotations;
2358
+ }
2359
+ }
2360
+ }
2361
+ #getRenderedEditableLineRange() {
2362
+ const contentElement = this.#contentElement;
2363
+ if (contentElement === void 0) return;
2364
+ let first;
2365
+ let last;
2366
+ for (const child of contentElement.children) {
2367
+ const el = child;
2368
+ const lineType = el.dataset.lineType;
2369
+ const lineNumber = getLineNumberAttr(el);
2370
+ if (lineNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) continue;
2371
+ const line = lineNumber - 1;
2372
+ if (first === void 0 || line < first) first = line;
2373
+ if (last === void 0 || line > last) last = line;
2374
+ }
2375
+ return first === void 0 || last === void 0 ? void 0 : {
2376
+ first,
2377
+ last
2378
+ };
2379
+ }
2380
+ #getLineElement(line) {
2381
+ let lineElement = this.#lineElementsCache.get(line);
2382
+ if (lineElement !== void 0) return lineElement ?? void 0;
2383
+ const contentElement = this.#contentElement;
2384
+ if (contentElement === void 0) return;
2385
+ if (this.#renderRange !== void 0) {
2386
+ const { startingLine } = this.#renderRange;
2387
+ const { children } = contentElement;
2388
+ for (let i = line - startingLine; i <= children.length; i++) {
2389
+ const child = children[i];
2390
+ if (child === void 0) break;
2391
+ const lineNumber = getLineNumberAttr(child);
2392
+ const lineType = child.dataset.lineType;
2393
+ if (lineNumber !== void 0 && lineNumber === line + 1 && lineType !== void 0 && isLineEditable(lineType)) {
2394
+ lineElement = child;
2395
+ break;
2396
+ }
2397
+ }
2398
+ }
2399
+ lineElement ??= contentElement.querySelector(`[data-line="${line + 1}"]` + (this.#diffSyle === "unified" ? ":not([data-line-type=\"change-deletion\"])" : ""));
2400
+ if (lineElement !== void 0) this.#lineElementsCache.set(line, lineElement);
2401
+ return lineElement ?? void 0;
2402
+ }
2403
+ #getGutterWidth() {
2404
+ if (this.#gutterElement === void 0) return 0;
2405
+ if (this.#gutterWidthCache === void 0) {
2406
+ const diffsColumnNumberWidth = this.#contentElement?.parentElement?.style.getPropertyValue("--diffs-column-number-width");
2407
+ if (diffsColumnNumberWidth !== void 0 && diffsColumnNumberWidth.length > 2 && diffsColumnNumberWidth.endsWith("px")) this.#gutterWidthCache = parseInt(diffsColumnNumberWidth.slice(0, -2), 10);
2408
+ else this.#gutterWidthCache = this.#gutterElement.offsetWidth;
2409
+ }
2410
+ return this.#gutterWidthCache;
2411
+ }
2412
+ #getContentWidth() {
2413
+ if (this.#contentElement === void 0) return 0;
2414
+ if (this.#contentWidthCache === void 0) {
2415
+ const diffsColumnContentWidth = this.#contentElement.parentElement?.style.getPropertyValue("--diffs-column-content-width");
2416
+ if (diffsColumnContentWidth !== void 0 && diffsColumnContentWidth.length > 2 && diffsColumnContentWidth.endsWith("px")) this.#contentWidthCache = parseFloat(diffsColumnContentWidth.slice(0, -2));
2417
+ else this.#contentWidthCache = this.#contentElement.offsetWidth;
2418
+ }
2419
+ return this.#contentWidthCache;
2420
+ }
2421
+ #getLineY(line) {
2422
+ const cachedY = this.#lineYCache.get(line);
2423
+ if (cachedY !== void 0) return cachedY;
2424
+ const lineElement = this.#getLineElement(line);
2425
+ if (lineElement === void 0) return -1;
2426
+ let y = lineElement.offsetTop + this.#metrics.paddingTop;
2427
+ y += this.#activeContentOffset?.top ?? 0;
2428
+ this.#lineYCache.set(line, y);
2429
+ return y;
2430
+ }
2431
+ #getCharX(line, char) {
2432
+ if (this.#lastAccessedCharX !== void 0 && this.#lastAccessedCharX[0] === line && this.#lastAccessedCharX[1] === char) return [this.#lastAccessedCharX[2], this.#lastAccessedCharX[3]];
2433
+ const lineText = this.#textDocument?.getLineText(line);
2434
+ const offsetLeft = this.#getGutterWidth() + this.#metrics.ch;
2435
+ if (lineText === void 0 || lineText.length === 0 || char <= 0) return [offsetLeft + (this.#activeContentOffset?.left ?? 0), 0];
2436
+ const boundedCharacter = snapTextOffsetToUnicodeBoundary(lineText, Math.min(char, lineText.length));
2437
+ const textBeforeCharacter = lineText.slice(0, boundedCharacter);
2438
+ const asciiColumns = getExpandedAsciiTextColumns(textBeforeCharacter, this.#metrics.tabSize);
2439
+ let left = 0;
2440
+ let wrapLine = 0;
2441
+ if (asciiColumns !== -1) left = offsetLeft + asciiColumns * this.#metrics.ch;
2442
+ else left = offsetLeft + this.#metrics.measureTextWidth(textBeforeCharacter);
2443
+ if (this.#isWrap) {
2444
+ const contentWidth = this.#getContentWidth();
2445
+ if (2 * this.#metrics.ch + this.#metrics.measureTextWidth(lineText) > contentWidth) {
2446
+ const wrapOffsets = this.#wrapLineText(line);
2447
+ for (let w = 0; w + 1 < wrapOffsets.length; w++) {
2448
+ const segmentStart = wrapOffsets[w];
2449
+ if (boundedCharacter <= wrapOffsets[w + 1]) {
2450
+ wrapLine = w;
2451
+ const prefixInSegment = lineText.slice(segmentStart, boundedCharacter);
2452
+ const segmentAsciiColumns = getExpandedAsciiTextColumns(prefixInSegment, this.#metrics.tabSize);
2453
+ if (segmentAsciiColumns !== -1) left = offsetLeft + segmentAsciiColumns * this.#metrics.ch;
2454
+ else left = offsetLeft + this.#metrics.measureTextWidth(prefixInSegment);
2455
+ break;
2456
+ }
2457
+ }
2458
+ }
2459
+ left += this.#activeContentOffset?.left ?? 0;
2460
+ }
2461
+ if (this.#lastAccessedCharX !== void 0) {
2462
+ this.#lastAccessedCharX[0] = line;
2463
+ this.#lastAccessedCharX[1] = char;
2464
+ this.#lastAccessedCharX[2] = left;
2465
+ this.#lastAccessedCharX[3] = wrapLine;
2466
+ } else this.#lastAccessedCharX = [
2467
+ line,
2468
+ char,
2469
+ left,
2470
+ wrapLine
2471
+ ];
2472
+ return [left, wrapLine];
2473
+ }
2474
+ #wrapLineText(line) {
2475
+ const cachedOffsets = this.#wrapLineOffsetsCache.get(line);
2476
+ if (cachedOffsets !== void 0) return cachedOffsets;
2477
+ const lineText = this.#textDocument?.getLineText(line);
2478
+ if (lineText === void 0 || lineText.length === 0) {
2479
+ const offsets = new Uint32Array([0]);
2480
+ this.#wrapLineOffsetsCache.set(line, offsets);
2481
+ return offsets;
2482
+ }
2483
+ const div = h("div", {
2484
+ style: {
2485
+ position: "absolute",
2486
+ top: "0",
2487
+ left: "0",
2488
+ width: "100%",
2489
+ boxSizing: "border-box",
2490
+ visibility: "hidden",
2491
+ pointerEvents: "none",
2492
+ whiteSpace: "pre-wrap",
2493
+ wordBreak: "break-word",
2494
+ font: "inherit",
2495
+ paddingInline: "1ch",
2496
+ tabSize: this.#metrics.tabSize.toString()
2497
+ },
2498
+ textContent: lineText
2499
+ }, this.#contentElement);
2500
+ const textNode = div.firstChild;
2501
+ const range = document.createRange();
2502
+ const starts = [];
2503
+ try {
2504
+ const unicodeOffsets = getUnicodeMeasurementOffsets(lineText);
2505
+ const wrapLineStartLeft = div.getBoundingClientRect().left + this.#metrics.ch;
2506
+ let previousOffset = 0;
2507
+ let lastTop = Number.NEGATIVE_INFINITY;
2508
+ for (let i = 0, offsetIndex = 0; i < lineText.length;) {
2509
+ const nextOffset = unicodeOffsets === void 0 ? i + 1 : unicodeOffsets[offsetIndex + 1];
2510
+ range.setStart(textNode, i);
2511
+ range.setEnd(textNode, nextOffset);
2512
+ const { left, top } = range.getBoundingClientRect();
2513
+ if (top > lastTop) {
2514
+ const startsPastLineStart = isSafari() && starts.length > 0 && left - wrapLineStartLeft > this.#metrics.ch / 2;
2515
+ starts.push(startsPastLineStart ? previousOffset : i);
2516
+ lastTop = top;
2517
+ }
2518
+ previousOffset = i;
2519
+ i = nextOffset;
2520
+ offsetIndex++;
2521
+ }
2522
+ const offsets = new Uint32Array(starts.length + 1);
2523
+ for (let i = 0; i < starts.length; i++) offsets[i] = starts[i];
2524
+ offsets[starts.length] = lineText.length;
2525
+ this.#wrapLineOffsetsCache.set(line, offsets);
2526
+ return offsets;
2527
+ } finally {
2528
+ div.remove();
2529
+ }
2530
+ }
2531
+ #rangeBelongsToEditor({ startContainer, endContainer }) {
2532
+ const contentEl = this.#contentElement;
2533
+ if (contentEl === void 0) return false;
2534
+ return contentEl.contains(startContainer) && contentEl.contains(endContainer);
2535
+ }
2536
+ #isLineVisible(line) {
2537
+ const lineCount = this.#textDocument?.lineCount ?? 0;
2538
+ if (line < 0 || line >= lineCount) return false;
2539
+ return this.#getLineElement(line) !== void 0;
2540
+ }
2541
+ };
2542
+ //#endregion
2543
+ export { Editor };
2544
+
2545
+ //# sourceMappingURL=editor.js.map