@pierre/diffs 1.3.0-beta.9 → 1.3.0-rc.2

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 (241) hide show
  1. package/README.md +9 -0
  2. package/dist/components/CodeView.d.ts +12 -13
  3. package/dist/components/CodeView.d.ts.map +1 -1
  4. package/dist/components/CodeView.js +50 -58
  5. package/dist/components/CodeView.js.map +1 -1
  6. package/dist/components/File.d.ts +10 -3
  7. package/dist/components/File.d.ts.map +1 -1
  8. package/dist/components/File.js +41 -10
  9. package/dist/components/File.js.map +1 -1
  10. package/dist/components/FileDiff.d.ts +37 -6
  11. package/dist/components/FileDiff.d.ts.map +1 -1
  12. package/dist/components/FileDiff.js +202 -19
  13. package/dist/components/FileDiff.js.map +1 -1
  14. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  15. package/dist/components/UnresolvedFile.js +7 -2
  16. package/dist/components/UnresolvedFile.js.map +1 -1
  17. package/dist/components/VirtualizedFile.d.ts +2 -1
  18. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  19. package/dist/components/VirtualizedFile.js +9 -5
  20. package/dist/components/VirtualizedFile.js.map +1 -1
  21. package/dist/components/VirtualizedFileDiff.d.ts +14 -0
  22. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  23. package/dist/components/VirtualizedFileDiff.js +61 -3
  24. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  25. package/dist/components/Virtualizer.d.ts +14 -2
  26. package/dist/components/Virtualizer.d.ts.map +1 -1
  27. package/dist/components/Virtualizer.js +27 -2
  28. package/dist/components/Virtualizer.js.map +1 -1
  29. package/dist/edit/index.d.ts +5 -0
  30. package/dist/edit/index.js +3 -0
  31. package/dist/editor/command.d.ts +1 -1
  32. package/dist/editor/command.d.ts.map +1 -1
  33. package/dist/editor/command.js +11 -0
  34. package/dist/editor/command.js.map +1 -1
  35. package/dist/editor/editStack.d.ts +6 -2
  36. package/dist/editor/editStack.d.ts.map +1 -1
  37. package/dist/editor/editStack.js +53 -12
  38. package/dist/editor/editStack.js.map +1 -1
  39. package/dist/editor/editor.d.ts +51 -23
  40. package/dist/editor/editor.d.ts.map +1 -1
  41. package/dist/editor/editor.js +977 -241
  42. package/dist/editor/editor.js.map +1 -1
  43. package/dist/editor/editor2.js +1 -1
  44. package/dist/editor/languages.d.ts +26 -0
  45. package/dist/editor/languages.d.ts.map +1 -0
  46. package/dist/editor/languages.js +363 -0
  47. package/dist/editor/languages.js.map +1 -0
  48. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  49. package/dist/editor/lineAnnotations.js +25 -26
  50. package/dist/editor/lineAnnotations.js.map +1 -1
  51. package/dist/editor/marker.d.ts +5 -3
  52. package/dist/editor/marker.d.ts.map +1 -1
  53. package/dist/editor/marker.js +111 -91
  54. package/dist/editor/marker.js.map +1 -1
  55. package/dist/editor/matchBrackets.d.ts +3 -2
  56. package/dist/editor/matchBrackets.d.ts.map +1 -1
  57. package/dist/editor/matchBrackets.js +2 -10
  58. package/dist/editor/matchBrackets.js.map +1 -1
  59. package/dist/editor/pieceTable.d.ts +2 -1
  60. package/dist/editor/pieceTable.d.ts.map +1 -1
  61. package/dist/editor/pieceTable.js +103 -39
  62. package/dist/editor/pieceTable.js.map +1 -1
  63. package/dist/editor/platform.js +2 -0
  64. package/dist/editor/platform.js.map +1 -1
  65. package/dist/editor/popover.d.ts +25 -5
  66. package/dist/editor/popover.d.ts.map +1 -1
  67. package/dist/editor/popover.js +60 -14
  68. package/dist/editor/popover.js.map +1 -1
  69. package/dist/editor/searchPanel.d.ts.map +1 -1
  70. package/dist/editor/searchPanel.js +9 -2
  71. package/dist/editor/searchPanel.js.map +1 -1
  72. package/dist/editor/selection.d.ts +31 -11
  73. package/dist/editor/selection.d.ts.map +1 -1
  74. package/dist/editor/selection.js +198 -70
  75. package/dist/editor/selection.js.map +1 -1
  76. package/dist/editor/selectionAction.d.ts +4 -3
  77. package/dist/editor/selectionAction.d.ts.map +1 -1
  78. package/dist/editor/selectionAction.js +9 -5
  79. package/dist/editor/selectionAction.js.map +1 -1
  80. package/dist/editor/stateStorage.d.ts +15 -0
  81. package/dist/editor/stateStorage.d.ts.map +1 -0
  82. package/dist/editor/stateStorage.js +65 -0
  83. package/dist/editor/stateStorage.js.map +1 -0
  84. package/dist/editor/textDocument.d.ts +12 -77
  85. package/dist/editor/textDocument.d.ts.map +1 -1
  86. package/dist/editor/textDocument.js +76 -34
  87. package/dist/editor/textDocument.js.map +1 -1
  88. package/dist/editor/textMeasure.d.ts +7 -12
  89. package/dist/editor/textMeasure.d.ts.map +1 -1
  90. package/dist/editor/textMeasure.js +40 -32
  91. package/dist/editor/textMeasure.js.map +1 -1
  92. package/dist/editor/{tokenzier.d.ts → tokenizer.d.ts} +3 -3
  93. package/dist/editor/tokenizer.d.ts.map +1 -0
  94. package/dist/editor/{tokenzier.js → tokenizer.js} +170 -50
  95. package/dist/editor/tokenizer.js.map +1 -0
  96. package/dist/editor/utils.js +1 -1
  97. package/dist/editor/utils.js.map +1 -1
  98. package/dist/index.d.ts +4 -3
  99. package/dist/index.js +6 -5
  100. package/dist/managers/InteractionManager.d.ts +18 -7
  101. package/dist/managers/InteractionManager.d.ts.map +1 -1
  102. package/dist/managers/InteractionManager.js +154 -99
  103. package/dist/managers/InteractionManager.js.map +1 -1
  104. package/dist/managers/UniversalRenderingManager.d.ts.map +1 -1
  105. package/dist/managers/UniversalRenderingManager.js +1 -2
  106. package/dist/managers/UniversalRenderingManager.js.map +1 -1
  107. package/dist/react/CodeView.d.ts +13 -13
  108. package/dist/react/CodeView.d.ts.map +1 -1
  109. package/dist/react/CodeView.js +37 -28
  110. package/dist/react/CodeView.js.map +1 -1
  111. package/dist/react/EditContext.d.ts +20 -0
  112. package/dist/react/EditContext.d.ts.map +1 -0
  113. package/dist/react/EditContext.js +20 -0
  114. package/dist/react/EditContext.js.map +1 -0
  115. package/dist/react/File.d.ts +2 -1
  116. package/dist/react/File.d.ts.map +1 -1
  117. package/dist/react/File.js +3 -2
  118. package/dist/react/File.js.map +1 -1
  119. package/dist/react/FileDiff.d.ts +2 -2
  120. package/dist/react/FileDiff.d.ts.map +1 -1
  121. package/dist/react/FileDiff.js +3 -2
  122. package/dist/react/FileDiff.js.map +1 -1
  123. package/dist/react/MultiFileDiff.d.ts +2 -2
  124. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  125. package/dist/react/MultiFileDiff.js +3 -2
  126. package/dist/react/MultiFileDiff.js.map +1 -1
  127. package/dist/react/PatchDiff.d.ts +2 -2
  128. package/dist/react/PatchDiff.d.ts.map +1 -1
  129. package/dist/react/PatchDiff.js +3 -2
  130. package/dist/react/PatchDiff.js.map +1 -1
  131. package/dist/react/UnresolvedFile.d.ts +1 -1
  132. package/dist/react/UnresolvedFile.js.map +1 -1
  133. package/dist/react/index.d.ts +4 -4
  134. package/dist/react/index.js +3 -3
  135. package/dist/react/types.d.ts +9 -1
  136. package/dist/react/types.d.ts.map +1 -1
  137. package/dist/react/utils/useFileDiffInstance.d.ts +5 -3
  138. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  139. package/dist/react/utils/useFileDiffInstance.js +24 -34
  140. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  141. package/dist/react/utils/useFileInstance.d.ts +5 -3
  142. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  143. package/dist/react/utils/useFileInstance.js +23 -33
  144. package/dist/react/utils/useFileInstance.js.map +1 -1
  145. package/dist/react/utils/useUnresolvedFileInstance.js +4 -4
  146. package/dist/react/utils/useUnresolvedFileInstance.js.map +1 -1
  147. package/dist/renderers/DiffHunksRenderer.d.ts +41 -1
  148. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  149. package/dist/renderers/DiffHunksRenderer.js +167 -29
  150. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  151. package/dist/renderers/FileRenderer.d.ts +20 -1
  152. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  153. package/dist/renderers/FileRenderer.js +119 -29
  154. package/dist/renderers/FileRenderer.js.map +1 -1
  155. package/dist/ssr/index.d.ts +2 -2
  156. package/dist/ssr/preloadDiffs.d.ts.map +1 -1
  157. package/dist/ssr/preloadDiffs.js +3 -1
  158. package/dist/ssr/preloadDiffs.js.map +1 -1
  159. package/dist/ssr/preloadFile.d.ts.map +1 -1
  160. package/dist/ssr/preloadFile.js +2 -0
  161. package/dist/ssr/preloadFile.js.map +1 -1
  162. package/dist/style.js +1 -1
  163. package/dist/types.d.ts +192 -42
  164. package/dist/types.d.ts.map +1 -1
  165. package/dist/utils/annotationHelpers.d.ts +20 -0
  166. package/dist/utils/annotationHelpers.d.ts.map +1 -0
  167. package/dist/utils/annotationHelpers.js +29 -0
  168. package/dist/utils/annotationHelpers.js.map +1 -0
  169. package/dist/utils/computeFileOffsets.d.ts.map +1 -1
  170. package/dist/utils/computeFileOffsets.js +34 -5
  171. package/dist/utils/computeFileOffsets.js.map +1 -1
  172. package/dist/utils/editSessionHunks.d.ts +66 -0
  173. package/dist/utils/editSessionHunks.d.ts.map +1 -0
  174. package/dist/utils/editSessionHunks.js +437 -0
  175. package/dist/utils/editSessionHunks.js.map +1 -0
  176. package/dist/utils/getDiffHunksRendererOptions.d.ts.map +1 -1
  177. package/dist/utils/getDiffHunksRendererOptions.js +2 -1
  178. package/dist/utils/getDiffHunksRendererOptions.js.map +1 -1
  179. package/dist/utils/getFileRendererOptions.d.ts.map +1 -1
  180. package/dist/utils/getFileRendererOptions.js +2 -1
  181. package/dist/utils/getFileRendererOptions.js.map +1 -1
  182. package/dist/utils/getHunkSideBoundaries.d.ts +7 -0
  183. package/dist/utils/getHunkSideBoundaries.d.ts.map +1 -0
  184. package/dist/utils/getHunkSideBoundaries.js +12 -0
  185. package/dist/utils/getHunkSideBoundaries.js.map +1 -0
  186. package/dist/utils/getTotalLineCountFromHunks.d.ts.map +1 -1
  187. package/dist/utils/getTotalLineCountFromHunks.js +2 -1
  188. package/dist/utils/getTotalLineCountFromHunks.js.map +1 -1
  189. package/dist/utils/hydratePartialDiff.d.ts.map +1 -1
  190. package/dist/utils/hydratePartialDiff.js +4 -3
  191. package/dist/utils/hydratePartialDiff.js.map +1 -1
  192. package/dist/utils/isGutterUtilityPath.d.ts +5 -0
  193. package/dist/utils/isGutterUtilityPath.d.ts.map +1 -0
  194. package/dist/utils/isGutterUtilityPath.js +12 -0
  195. package/dist/utils/isGutterUtilityPath.js.map +1 -0
  196. package/dist/utils/iterateOverDiff.d.ts.map +1 -1
  197. package/dist/utils/iterateOverDiff.js +17 -12
  198. package/dist/utils/iterateOverDiff.js.map +1 -1
  199. package/dist/utils/parseMergeConflictDiffFromFile.d.ts.map +1 -1
  200. package/dist/utils/parseMergeConflictDiffFromFile.js +9 -6
  201. package/dist/utils/parseMergeConflictDiffFromFile.js.map +1 -1
  202. package/dist/utils/parsePatchFiles.d.ts.map +1 -1
  203. package/dist/utils/parsePatchFiles.js +6 -3
  204. package/dist/utils/parsePatchFiles.js.map +1 -1
  205. package/dist/utils/realignChangeContent.d.ts +33 -0
  206. package/dist/utils/realignChangeContent.d.ts.map +1 -0
  207. package/dist/utils/realignChangeContent.js +148 -0
  208. package/dist/utils/realignChangeContent.js.map +1 -0
  209. package/dist/utils/renderDiffWithHighlighter.js +1 -1
  210. package/dist/utils/resolveRegion.d.ts.map +1 -1
  211. package/dist/utils/resolveRegion.js +18 -2
  212. package/dist/utils/resolveRegion.js.map +1 -1
  213. package/dist/utils/shouldUseTokenTransformer.d.ts +7 -3
  214. package/dist/utils/shouldUseTokenTransformer.d.ts.map +1 -1
  215. package/dist/utils/shouldUseTokenTransformer.js +1 -1
  216. package/dist/utils/shouldUseTokenTransformer.js.map +1 -1
  217. package/dist/utils/updateDiffHunks.d.ts +8 -2
  218. package/dist/utils/updateDiffHunks.d.ts.map +1 -1
  219. package/dist/utils/updateDiffHunks.js +9 -7
  220. package/dist/utils/updateDiffHunks.js.map +1 -1
  221. package/dist/utils/virtualDiffLayout.d.ts +42 -2
  222. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  223. package/dist/utils/virtualDiffLayout.js +101 -7
  224. package/dist/utils/virtualDiffLayout.js.map +1 -1
  225. package/dist/worker/WorkerPoolManager.d.ts +2 -1
  226. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  227. package/dist/worker/WorkerPoolManager.js +3 -0
  228. package/dist/worker/WorkerPoolManager.js.map +1 -1
  229. package/dist/worker/worker-portable.js +60 -20
  230. package/dist/worker/worker-portable.js.map +1 -1
  231. package/dist/worker/worker.js +61 -19
  232. package/dist/worker/worker.js.map +1 -1
  233. package/package.json +10 -7
  234. package/dist/editor/index.d.ts +0 -3
  235. package/dist/editor/index.js +0 -3
  236. package/dist/editor/tokenzier.d.ts.map +0 -1
  237. package/dist/editor/tokenzier.js.map +0 -1
  238. package/dist/react/EditorContext.d.ts +0 -15
  239. package/dist/react/EditorContext.d.ts.map +0 -1
  240. package/dist/react/EditorContext.js +0 -23
  241. package/dist/react/EditorContext.js.map +0 -1
@@ -1,21 +1,24 @@
1
1
  import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
2
+ import { isGutterUtilityPath } from "../utils/isGutterUtilityPath.js";
2
3
  import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
3
4
  import { isMoveCursorShortcut, isPrimaryModifier, isSafari } from "./platform.js";
4
5
  import { resolveEditorCommandFromKeyboardEvent, resolveFindAgainShortcut } from "./command.js";
5
- import { EditStack } from "./editStack.js";
6
6
  import editor_default from "./editor2.js";
7
+ import { EditStack } from "./editStack.js";
8
+ import { resolveBlockCommentEdits, resolveCommentConfig, resolveLineCommentEdits } from "./languages.js";
7
9
  import { addEventListener, clampDomOffset, extend, getLineNumberAttr, h, round } from "./utils.js";
8
10
  import { applyDocumentChangeToLineAnnotations, renderLineAnnotations } from "./lineAnnotations.js";
9
11
  import { PopoverManager } from "./popover.js";
10
- import { applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNexMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapSelectionsAfterEdits, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines } from "./selection.js";
12
+ import { applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNextMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionClipboardTexts, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapSelectionsAfterEdits, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines } from "./selection.js";
11
13
  import { MarkerRenderer, markerSeverityDatasetKey } from "./marker.js";
12
14
  import { findBracketMatchRanges } from "./matchBrackets.js";
13
15
  import { createSpriteElement } from "./sprite.js";
14
16
  import { SearchPanelWidget } from "./searchPanel.js";
15
17
  import { SelectionActionWidget } from "./selectionAction.js";
18
+ import { cloneEditorState, createStateStorage } from "./stateStorage.js";
16
19
  import { TextDocument } from "./textDocument.js";
17
20
  import { Metrics, getExpandedAsciiTextColumns, getUnicodeMeasurementOffsets, snapTextOffsetToUnicodeBoundary } from "./textMeasure.js";
18
- import { EditorTokenizer, renderLineTokens } from "./tokenzier.js";
21
+ import { EditorTokenizer, renderLineTokens } from "./tokenizer.js";
19
22
  //#region src/editor/editor.ts
20
23
  function getShadowRootRange(shadowRoot) {
21
24
  const selection = shadowRoot.getSelection?.();
@@ -29,16 +32,38 @@ function getShadowRootRange(shadowRoot) {
29
32
  endOffset: range.endOffset
30
33
  };
31
34
  }
35
+ function requirePersistedCacheKey(file) {
36
+ 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.`);
37
+ return file.cacheKey;
38
+ }
39
+ function isPromise(value) {
40
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
41
+ }
32
42
  const MAX_EDIT_WIDEN_WINDOW_MULTIPLE = 2;
43
+ const MAX_WRAP_OFFSETS_CACHE_LINES = 1e4;
44
+ const SELECTION_ACTION_POPOVER_PLACEMENT_KEY = "selection-action";
45
+ const MULTI_SELECTION_CLIPBOARD_TYPE = "application/vnd.pierre.diffs-selections+json";
33
46
  var Editor = class {
34
47
  #options;
35
48
  #metrics = new Metrics();
36
49
  #tokenizer;
37
50
  #popoverManager;
51
+ #textDocumentCache = /* @__PURE__ */ new Map();
52
+ #stateStorage;
53
+ #stateStorageOption;
54
+ #pendingStateWrites = /* @__PURE__ */ new Map();
55
+ #pendingStateRestore;
56
+ #stateRestoreGeneration = 0;
57
+ #restoreStateOnNextSync = false;
38
58
  #editorEventDisposes;
39
59
  #globalEventDisposes;
40
60
  #selectEventDisposes;
41
61
  #detach;
62
+ #attachState = {
63
+ generation: 0,
64
+ callback: void 0,
65
+ delivered: false
66
+ };
42
67
  #contentOffset;
43
68
  #gutterWidthCache;
44
69
  #contentWidthCache;
@@ -66,9 +91,11 @@ var Editor = class {
66
91
  #markerRenderer;
67
92
  #searchPanel;
68
93
  #selectionAction;
94
+ #canMountSelectionAction = true;
69
95
  #shouldIgnoreSelectionChange = false;
70
96
  #suppressNativeSelectionSync = false;
71
97
  #contentHasFocus = false;
98
+ #replacementFocusRequest;
72
99
  #isComposing = false;
73
100
  #isGutterMouseDown = false;
74
101
  #isContentMouseDown = false;
@@ -87,13 +114,13 @@ var Editor = class {
87
114
  #fontRemeasureScheduled = false;
88
115
  #themeSelectionRefreshFrame;
89
116
  #onDeferTokenize = (lines, themeType) => {
90
- this.#fileInstance?.updateRenderCache(lines, themeType, false);
117
+ this.#fileInstance?.updateRenderCache(lines, themeType);
91
118
  if (this.#renderRange !== void 0 && this.#renderRange.totalLines !== Infinity) {
92
119
  const { startingLine, totalLines } = this.#renderRange;
93
120
  const endLine = Math.min(startingLine + totalLines, this.#textDocument?.lineCount ?? 0);
94
121
  for (const [line, tokens] of lines) if (line >= startingLine && line < endLine) {
95
122
  const lineElement = this.#getLineElement(line);
96
- if (lineElement !== void 0) lineElement.replaceChildren(...renderLineTokens(tokens, themeType));
123
+ if (lineElement !== void 0) lineElement.replaceChildren(...renderLineTokens(tokens));
97
124
  }
98
125
  }
99
126
  };
@@ -101,47 +128,56 @@ var Editor = class {
101
128
  this.#options = options;
102
129
  }
103
130
  setOptions(options) {
104
- this.#options = {
131
+ const previousStorageOption = this.#options.persistStateStorage ?? "inMemory";
132
+ const nextOptions = {
105
133
  ...this.#options,
106
134
  ...options
107
135
  };
136
+ if (nextOptions.persistState === true && this.#fileInstance?.type === "file") {
137
+ const file = this.#fileInstance.__getCurrentFile?.() ?? this.#fileInfo;
138
+ if (file !== void 0) requirePersistedCacheKey(file);
139
+ }
140
+ this.#options = nextOptions;
141
+ if (this.#options.persistState !== true) {
142
+ this.#textDocumentCache.clear();
143
+ this.#stateRestoreGeneration++;
144
+ this.#restoreStateOnNextSync = false;
145
+ }
146
+ if ((this.#options.persistStateStorage ?? "inMemory") !== previousStorageOption) {
147
+ this.#stateRestoreGeneration++;
148
+ this.#stateStorage = void 0;
149
+ this.#stateStorageOption = void 0;
150
+ this.#pendingStateWrites.clear();
151
+ }
108
152
  }
109
153
  edit(fileInstance) {
110
- const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, lineHoverHighlight = "disabled", ...rest } = fileInstance.options;
111
- if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || expandUnchanged !== true && fileInstance.type === "file-diff" || lineHoverHighlight !== "disabled") {
112
- fileInstance.setOptions({
113
- ...rest,
114
- useTokenTransformer: true,
115
- enableGutterUtility: false,
116
- enableLineSelection: false,
117
- expandUnchanged: true,
118
- lineHoverHighlight: "disabled"
119
- });
120
- fileInstance.rerender();
154
+ if (this.#options.persistState === true && fileInstance.type === "file") {
155
+ const file = fileInstance.__getCurrentFile?.();
156
+ if (file !== void 0) requirePersistedCacheKey(file);
121
157
  }
158
+ this.#invalidateOnAttach();
122
159
  this.#fileInstance = fileInstance;
123
160
  this.#initialize();
124
161
  this.#detach = fileInstance.attachEditor(this);
125
162
  return () => this.cleanUp();
126
163
  }
127
164
  /**
128
- * Apply edits to current attached file.
165
+ * Apply edits to current attached file. Every edit joins the undo timeline:
166
+ * a programmatic edit must leave the document and its history exactly as
167
+ * the same edit typed by the user would (history equivalence — see
168
+ * TextDocument.applyResolvedEdits), so it is undoable like any other edit.
169
+ *
170
+ * @param updateHistory Whether to record caller selection snapshots for
171
+ * exact undo/redo restoration. Defaults to true. When false, live selections
172
+ * are remapped during replay and the text edit still joins the undo timeline.
129
173
  */
130
- applyEdits(edits, updateHistory = false) {
174
+ applyEdits(edits, updateHistory = true) {
131
175
  const textDocument = this.#textDocument;
132
176
  if (textDocument == null) throw new Error("Editor is not attached");
133
177
  const wasFocused = this.#contentHasFocus;
134
178
  const selectionsBefore = this.#selections;
135
179
  const selectionOffsetsBefore = selectionsBefore?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
136
- const resolvedEditOffsets = selectionsBefore === void 0 ? void 0 : edits.map((edit) => {
137
- const a = textDocument.offsetAt(edit.range.start);
138
- const b = textDocument.offsetAt(edit.range.end);
139
- return {
140
- start: Math.min(a, b),
141
- end: Math.max(a, b),
142
- text: edit.newText
143
- };
144
- }).sort((a, b) => a.start - b.start);
180
+ const resolvedEditOffsets = selectionsBefore === void 0 ? void 0 : textDocument.resolveEdits(edits).sort((a, b) => a.start - b.start);
145
181
  const change = textDocument.applyEdits(edits, updateHistory, selectionsBefore);
146
182
  if (change === void 0) return;
147
183
  let nextSelections;
@@ -167,45 +203,58 @@ var Editor = class {
167
203
  redo() {
168
204
  this.#runCommand("redo");
169
205
  }
206
+ getFile() {
207
+ const fileInfo = this.#fileInfo;
208
+ const textDocument = this.#textDocument;
209
+ if (fileInfo === void 0 || textDocument === void 0) return;
210
+ const file = { ...fileInfo };
211
+ Object.defineProperty(file, "contents", {
212
+ enumerable: true,
213
+ get: () => textDocument.getText()
214
+ });
215
+ return file;
216
+ }
217
+ getText() {
218
+ return this.#textDocument?.getText() ?? "";
219
+ }
170
220
  getState() {
171
- const fileRef = this.#getFileRef();
172
- if (fileRef === void 0) throw new Error("Editor is not attached");
221
+ const fileInstance = this.#fileInstance;
173
222
  return {
174
- file: {
175
- ...fileRef,
176
- cacheKey: "edited-at-" + Date.now()
177
- },
178
223
  selections: this.#selections,
179
- lineAnnotations: this.#lineAnnotations,
180
- renderRange: this.#renderRange
224
+ view: fileInstance != null ? { scrollLeft: fileInstance.getCodeScrollLeft() } : void 0
181
225
  };
182
226
  }
183
- setState({ file, lineAnnotations, renderRange, selections }) {
184
- this.#resetCache();
185
- this.#resetState();
186
- this.#initSelections = selections;
187
- this.#fileInstance?.render({
188
- file: {
189
- ...file,
190
- cacheKey: "edited-at-" + Date.now()
191
- },
192
- lineAnnotations,
193
- renderRange
194
- });
227
+ setState({ selections, view }) {
228
+ if (this.#fileInstance === void 0 || this.#textDocument === void 0) throw new Error("Editor is not attached");
229
+ this.#canMountSelectionAction = false;
230
+ this.#updateSelections(selections ?? []);
231
+ if (view != null) {
232
+ this.#fileInstance.setCodeScrollLeft(view.scrollLeft);
233
+ return;
234
+ }
235
+ this.#scrollToPrimaryCaret();
195
236
  }
196
237
  setSelections(selections) {
197
238
  const textDocument = this.#textDocument;
198
239
  if (textDocument === void 0) throw new Error("Text document is not initialized");
199
240
  const resolvedSelections = selections.map((selection) => {
200
- const start = textDocument.normalizePosition(selection.start);
201
- const end = textDocument.normalizePosition(selection.end);
241
+ let start = textDocument.normalizePosition(selection.start);
242
+ let end = textDocument.normalizePosition(selection.end);
243
+ let direction = selection.direction === "none" ? 0 : selection.direction === "backward" ? -1 : 1;
244
+ if (comparePosition(start, end) > 0) {
245
+ [start, end] = [end, start];
246
+ if (direction !== 0) direction = direction === 1 ? -1 : 1;
247
+ }
202
248
  return {
203
- direction: selection.direction === "none" ? 0 : selection.direction === "backward" ? -1 : 1,
249
+ direction,
204
250
  start,
205
251
  end
206
252
  };
207
253
  });
254
+ this.#canMountSelectionAction = false;
208
255
  this.#updateSelections(resolvedSelections);
256
+ const primarySelection = resolvedSelections.at(-1);
257
+ if (primarySelection !== void 0) this.#revealLineIfCollapsed(getCaretPosition(primarySelection).line);
209
258
  this.#scrollToPrimaryCaret();
210
259
  }
211
260
  setMarkers(markers) {
@@ -232,6 +281,19 @@ var Editor = class {
232
281
  }
233
282
  focus(options) {
234
283
  const preventScroll = options?.preventScroll ?? false;
284
+ const lineNumber = options?.lineNumber;
285
+ if (lineNumber === "first-visible" || typeof lineNumber === "number") {
286
+ const textDocument = this.#textDocument;
287
+ if (textDocument == null || this.#fileInstance == null) return;
288
+ const targetLineNumber = lineNumber === "first-visible" ? this.#getFirstVisibleLineNumber(options?.offset) : lineNumber;
289
+ if (targetLineNumber == null) return;
290
+ const position = textDocument.normalizePosition({
291
+ line: targetLineNumber - 1,
292
+ character: lineNumber === "first-visible" ? 0 : options?.character ?? 0
293
+ });
294
+ this.#focusAtPosition(position, preventScroll);
295
+ return;
296
+ }
235
297
  const primarySelection = this.#selections?.at(-1);
236
298
  if (primarySelection !== void 0) {
237
299
  const pos = primarySelection.direction === -1 ? primarySelection.end : primarySelection.start;
@@ -239,9 +301,19 @@ var Editor = class {
239
301
  } else this.#focus(void 0, preventScroll);
240
302
  }
241
303
  blur() {
304
+ this.#replacementFocusRequest = void 0;
305
+ this.#contentHasFocus = false;
306
+ this.#shouldIgnoreSelectionChange = false;
242
307
  this.#contentElement?.blur();
243
308
  }
244
309
  cleanUp(recycle = false) {
310
+ this.#invalidateOnAttach();
311
+ if (!recycle) this.#attachState.delivered = false;
312
+ const hadFileInstance = this.#fileInstance != null;
313
+ const shouldRestoreState = this.#isStatePersistenceEnabled;
314
+ this.#stateRestoreGeneration++;
315
+ this.#persistCurrentState();
316
+ if (hadFileInstance) this.#restoreStateOnNextSync = shouldRestoreState;
245
317
  dequeueRender(this.#handleCustomPasteEvent);
246
318
  this.#tokenizer?.cleanUp();
247
319
  this.#tokenizer = void 0;
@@ -255,7 +327,7 @@ var Editor = class {
255
327
  this.#editorEventDisposes = void 0;
256
328
  this.#selectEventDisposes?.forEach((dispose) => dispose());
257
329
  this.#selectEventDisposes = void 0;
258
- this.#detach?.();
330
+ this.#detach?.(recycle);
259
331
  this.#detach = void 0;
260
332
  this.#gutterWidthCache = void 0;
261
333
  this.#contentWidthCache = void 0;
@@ -277,6 +349,7 @@ var Editor = class {
277
349
  this.#gutterElement = void 0;
278
350
  this.#contentElement?.removeAttribute("contentEditable");
279
351
  this.#contentElement = void 0;
352
+ this.#replacementFocusRequest = void 0;
280
353
  this.#contentHasFocus = false;
281
354
  this.#overlayElement?.remove();
282
355
  this.#overlayElement = void 0;
@@ -288,6 +361,24 @@ var Editor = class {
288
361
  this.#themeSelectionRefreshFrame = void 0;
289
362
  }
290
363
  this.#resetState();
364
+ this.#fileInstance = void 0;
365
+ }
366
+ /** @internal Capture outgoing state and substitute cached text before render. */
367
+ __prepareFile(file) {
368
+ if (this.#options.persistState !== true) return file;
369
+ const cacheKey = requirePersistedCacheKey(file);
370
+ const fileInfo = this.#fileInfo;
371
+ const languageId = file.lang ?? getFiletypeFromFileName(file.name);
372
+ if (fileInfo !== void 0 && (requirePersistedCacheKey(fileInfo) !== cacheKey || fileInfo.name !== file.name || this.#textDocument?.languageId !== languageId)) {
373
+ this.#stateRestoreGeneration++;
374
+ this.#persistCurrentState();
375
+ }
376
+ const textDocument = this.#getCachedTextDocument(file, cacheKey);
377
+ if (textDocument === void 0 || textDocument.getText() === file.contents) return file;
378
+ return {
379
+ ...file,
380
+ contents: textDocument.getText()
381
+ };
291
382
  }
292
383
  /** @internal */
293
384
  __postponeBgTokenizeToNextFrame() {
@@ -300,7 +391,15 @@ var Editor = class {
300
391
  }
301
392
  }
302
393
  /** @internal */
394
+ __captureFocusForDOMReplacement() {
395
+ const contentElement = this.#contentElement;
396
+ const shadowActiveElement = this.#fileContainer?.shadowRoot?.activeElement;
397
+ if (contentElement != null && (this.#contentHasFocus || shadowActiveElement === contentElement || shadowActiveElement != null && contentElement.contains(shadowActiveElement))) this.#replacementFocusRequest = {};
398
+ }
399
+ /** @internal */
303
400
  __syncRenderView = (highlighter, fileContainer, fileOrDiff, lineAnnotations, renderRange) => {
401
+ const fileInstance = this.#fileInstance;
402
+ if (fileInstance == null) return;
304
403
  const shadowRoot = fileContainer.shadowRoot;
305
404
  if (shadowRoot == null) {
306
405
  console.error("[editor] Could not find the shadow root.");
@@ -319,7 +418,10 @@ var Editor = class {
319
418
  }
320
419
  break;
321
420
  }
322
- if (codeElement === void 0 || contentEl === void 0) return;
421
+ if (codeElement === void 0 || contentEl === void 0) {
422
+ this.#replacementFocusRequest = void 0;
423
+ return;
424
+ }
323
425
  this.#getPopoverManager().setViewportElements(fileContainer, codeElement);
324
426
  if (this.#fileContainer !== fileContainer) {
325
427
  this.#fileContainer = fileContainer;
@@ -328,28 +430,42 @@ var Editor = class {
328
430
  if (this.#themeStyleElement !== void 0) shadowRoot.appendChild(this.#themeStyleElement);
329
431
  if (this.#spriteElement !== void 0) shadowRoot.prepend(this.#spriteElement);
330
432
  }
331
- if (this.#textDocument === void 0 || this.#fileInfo === void 0 || this.#fileInfo.name !== fileOrDiff.name || this.#fileInfo.lang !== fileOrDiff.lang || this.#fileInfo.cacheKey !== fileOrDiff.cacheKey) {
433
+ 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;
434
+ const persistedCacheKey = this.#isStatePersistenceEnabled ? requirePersistedCacheKey(fileOrDiff) : void 0;
435
+ let persistedStateTarget;
436
+ if (shouldRebuildDocument) {
437
+ this.#invalidateOnAttach();
332
438
  let contents = "";
333
439
  if ("contents" in fileOrDiff) contents = fileOrDiff.contents;
334
440
  else contents = fileOrDiff.additionLines.join("");
335
441
  const editStack = new EditStack({ maxEntries: this.#options.historyMaxEntries });
336
- const textDocument = new TextDocument(fileOrDiff.name, contents, fileOrDiff.lang ?? getFiletypeFromFileName(fileOrDiff.name), 0, editStack);
337
442
  const { name, lang, cacheKey } = fileOrDiff;
443
+ const languageId = lang ?? getFiletypeFromFileName(fileOrDiff.name);
444
+ const textDocument = (persistedCacheKey !== void 0 ? this.#getCachedTextDocument(fileOrDiff, persistedCacheKey) : void 0) ?? new TextDocument(fileOrDiff.name, contents, languageId, 0, editStack);
338
445
  this.#fileInfo = {
339
446
  name,
340
447
  lang,
341
448
  cacheKey
342
449
  };
343
450
  this.#textDocument = textDocument;
451
+ if (persistedCacheKey !== void 0) {
452
+ this.#textDocumentCache.set(persistedCacheKey, textDocument);
453
+ persistedStateTarget = {
454
+ cacheKey: persistedCacheKey,
455
+ textDocument
456
+ };
457
+ }
344
458
  this.#tokenizer?.cleanUp();
345
459
  this.#tokenizer = void 0;
346
460
  this.#resetState();
461
+ this.#wrapLineOffsetsCache.clear();
347
462
  this.#selections = this.#initSelections;
348
- requestAnimationFrame(() => {
349
- this.#options.onAttach?.(this, this.#fileInstance);
350
- });
351
463
  if (this.#textDocument !== void 0 && this.#options.__debug === true) console.log("[diffs/editor] text document rebuilt from", fileOrDiff.name);
352
464
  }
465
+ if (persistedStateTarget === void 0 && this.#restoreStateOnNextSync && persistedCacheKey !== void 0 && this.#textDocument !== void 0) persistedStateTarget = {
466
+ cacheKey: persistedCacheKey,
467
+ textDocument: this.#textDocument
468
+ };
353
469
  const textDocument = this.#textDocument;
354
470
  if (this.#tokenizer == null && textDocument != null) this.#tokenizer = new EditorTokenizer({
355
471
  highlighter,
@@ -381,11 +497,12 @@ var Editor = class {
381
497
  this.#listenContentElement(contentEl, gutterEl);
382
498
  if (this.#contentElement !== void 0 && this.#options.__debug === true) console.log("[diffs/editor] full re-render triggered !!!");
383
499
  }
500
+ if (this.#replacementFocusRequest != null) this.#replacementFocusRequest.target = contentEl;
384
501
  if (contentEl.ariaLabel !== fileOrDiff.name) contentEl.ariaLabel = fileOrDiff.name;
385
- if (lineAnnotations !== void 0 && lineAnnotations.length > 0 || this.#isDiff && this.#diffSyle === "unified") for (const child of this.#contentElement.children) {
502
+ if (lineAnnotations !== void 0 && lineAnnotations.length > 0 || this.#isDiff) for (const child of this.#contentElement.children) {
386
503
  const el = child;
387
- const { lineAnnotation, lineType } = el.dataset;
388
- if (lineAnnotation !== void 0 || lineType === "change-deletion") el.setAttribute("contenteditable", "false");
504
+ const { lineAnnotation, lineType, separator } = el.dataset;
505
+ if (lineAnnotation !== void 0 || separator !== void 0 || lineType === "change-deletion") el.setAttribute("contenteditable", "false");
389
506
  }
390
507
  this.#resetCache();
391
508
  this.#tokenizer?.syncTheme(this.#fileInstance?.options ?? {});
@@ -393,19 +510,140 @@ var Editor = class {
393
510
  this.#renderRange = renderRange;
394
511
  this.#viewportWindowLines = renderRange?.totalLines;
395
512
  this.#tokenizer?.prebuildStateStack(renderRange);
396
- this.#markerRenderer?.removePopup();
513
+ this.#markerRenderer?.removePopover();
397
514
  if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
398
515
  if (this.#initSelections !== void 0 && this.#primaryCaretElement !== void 0) {
399
516
  this.#initSelections = void 0;
400
517
  this.#scrollToPrimaryCaret(false, "center");
401
518
  } else if (this.#scrollingToLine !== void 0) this.#scrollToLine(this.#scrollingToLine, this.#scrollingToLineChar, this.#scrollingToLineNoFocus);
402
- else if (this.#selections !== void 0 && this.#selections.length > 0 && !this.#retainSearchPanelFocus) this.focus({ preventScroll: true });
519
+ else if (this.#replacementFocusRequest !== void 0) this.#restoreReplacementFocus();
520
+ else if (this.#selections !== void 0 && this.#selections.length > 0 && this.#contentHasFocus && !this.#retainSearchPanelFocus) this.focus({ preventScroll: true });
403
521
  if (this.#retainSearchPanelFocus) this.#searchPanel?.focus();
404
522
  if (this.#options.__debug === true && renderRange !== void 0) {
405
523
  const { startingLine, totalLines } = renderRange;
406
524
  console.log("[diffs/editor] render file:", fileOrDiff.name, "RenderRange:", startingLine + "-" + (startingLine + totalLines), "of", this.#textDocument?.lineCount, "lines");
407
525
  }
526
+ if (persistedStateTarget !== void 0) {
527
+ this.#restoreStateOnNextSync = false;
528
+ this.#restorePersistedState(persistedStateTarget.cacheKey, persistedStateTarget.textDocument);
529
+ }
530
+ this.#scheduleOnAttach(fileInstance);
408
531
  };
532
+ get #isStatePersistenceEnabled() {
533
+ return this.#options.persistState === true && this.#fileInstance?.type === "file";
534
+ }
535
+ #getCachedTextDocument(file, cacheKey) {
536
+ const textDocument = this.#textDocumentCache.get(cacheKey);
537
+ const languageId = file.lang ?? getFiletypeFromFileName(file.name);
538
+ return textDocument?.languageId === languageId ? textDocument : void 0;
539
+ }
540
+ #getStateStorage() {
541
+ const option = this.#options.persistStateStorage ?? "inMemory";
542
+ if (this.#stateStorage === void 0 || this.#stateStorageOption !== option) {
543
+ this.#stateStorage = createStateStorage(option);
544
+ this.#stateStorageOption = option;
545
+ }
546
+ return this.#stateStorage;
547
+ }
548
+ #persistCurrentState() {
549
+ const fileInfo = this.#fileInfo;
550
+ const textDocument = this.#textDocument;
551
+ if (!this.#isStatePersistenceEnabled || fileInfo === void 0 || textDocument === void 0) return;
552
+ const cacheKey = requirePersistedCacheKey(fileInfo);
553
+ this.#textDocumentCache.set(cacheKey, textDocument);
554
+ let storage;
555
+ try {
556
+ storage = this.#getStateStorage();
557
+ } catch {
558
+ return;
559
+ }
560
+ const state = cloneEditorState(this.getState());
561
+ const pendingRestore = this.#pendingStateRestore;
562
+ if (pendingRestore?.cacheKey === cacheKey && pendingRestore.textDocument === textDocument) {
563
+ this.#pendingStateRestore = void 0;
564
+ if (textDocument.version === pendingRestore.documentVersion && this.#selections === pendingRestore.selections && state.view?.scrollLeft === pendingRestore.view?.scrollLeft) return;
565
+ this.#writeState(storage, cacheKey, state, pendingRestore.completion);
566
+ return;
567
+ }
568
+ this.#writeState(storage, cacheKey, state);
569
+ }
570
+ #writeState(storage, cacheKey, state, waitFor) {
571
+ const previousWrite = this.#pendingStateWrites.get(cacheKey);
572
+ if (waitFor !== void 0 || previousWrite !== void 0) {
573
+ let pending = waitFor ?? Promise.resolve();
574
+ if (previousWrite !== void 0) pending = pending.then(() => previousWrite);
575
+ this.#trackStateWrite(cacheKey, pending.then(() => storage.set(cacheKey, state)));
576
+ return;
577
+ }
578
+ let result;
579
+ try {
580
+ result = storage.set(cacheKey, state);
581
+ } catch {
582
+ return;
583
+ }
584
+ if (!isPromise(result)) return;
585
+ this.#trackStateWrite(cacheKey, result);
586
+ }
587
+ #trackStateWrite(cacheKey, result) {
588
+ const pending = result.catch(() => {});
589
+ this.#pendingStateWrites.set(cacheKey, pending);
590
+ pending.finally(() => {
591
+ if (this.#pendingStateWrites.get(cacheKey) === pending) this.#pendingStateWrites.delete(cacheKey);
592
+ });
593
+ }
594
+ #restorePersistedState(cacheKey, textDocument) {
595
+ const generation = ++this.#stateRestoreGeneration;
596
+ const documentVersion = textDocument.version;
597
+ const selections = this.#selections;
598
+ const view = this.getState().view;
599
+ const applyState = (state) => {
600
+ const currentView = this.getState().view;
601
+ if (state === void 0 || generation !== this.#stateRestoreGeneration || this.#textDocument !== textDocument || textDocument.version !== documentVersion || this.#selections !== selections || currentView?.scrollLeft !== view?.scrollLeft || this.#fileInfo === void 0 || requirePersistedCacheKey(this.#fileInfo) !== cacheKey) return;
602
+ this.setState(cloneEditorState(state));
603
+ };
604
+ const readState = () => {
605
+ let result;
606
+ try {
607
+ result = this.#getStateStorage().get(cacheKey);
608
+ } catch {
609
+ return;
610
+ }
611
+ if (isPromise(result)) return result.then(applyState).catch(() => {});
612
+ else try {
613
+ applyState(result);
614
+ } catch {}
615
+ };
616
+ const pendingWrite = this.#pendingStateWrites.get(cacheKey);
617
+ const result = pendingWrite === void 0 ? readState() : pendingWrite.then(readState);
618
+ if (isPromise(result)) {
619
+ const pendingRestore = {
620
+ cacheKey,
621
+ textDocument,
622
+ documentVersion,
623
+ selections,
624
+ view,
625
+ completion: result.catch(() => {})
626
+ };
627
+ this.#pendingStateRestore = pendingRestore;
628
+ pendingRestore.completion.finally(() => {
629
+ if (this.#pendingStateRestore === pendingRestore) this.#pendingStateRestore = void 0;
630
+ });
631
+ }
632
+ }
633
+ #isLineRenderable(line) {
634
+ return this.#fileInstance?.isLineRenderable?.(line + 1) ?? true;
635
+ }
636
+ get #resolveRenderableLine() {
637
+ const fileInstance = this.#fileInstance;
638
+ if (fileInstance?.getNearestRenderableLine == null) return;
639
+ return (line, direction) => {
640
+ const nearest = fileInstance.getNearestRenderableLine(line + 1, direction);
641
+ return nearest == null ? void 0 : nearest - 1;
642
+ };
643
+ }
644
+ #revealLineIfCollapsed(line) {
645
+ if (!this.#isLineRenderable(line)) this.#fileInstance?.revealLine?.(line + 1);
646
+ }
409
647
  get #diffSyle() {
410
648
  return this.#fileInstance?.options.diffStyle ?? "split";
411
649
  }
@@ -417,21 +655,24 @@ var Editor = class {
417
655
  }
418
656
  #getPopoverManager() {
419
657
  return this.#popoverManager ??= new PopoverManager({
420
- hasActivePopover: () => this.#selectionAction !== void 0,
421
- updateActivePopover: () => this.#updateSelectionActionPopover()
658
+ hasActivePopover: () => this.#selectionAction !== void 0 || this.#markerRenderer?.isPopoverVisible() === true,
659
+ updateActivePopover: () => {
660
+ this.#updateSelectionActionPopover();
661
+ this.#markerRenderer?.updatePopoverPosition();
662
+ }
422
663
  });
423
664
  }
424
665
  #resetCache() {
425
666
  this.#lineYCache.clear();
426
- this.#wrapLineOffsetsCache.clear();
427
667
  this.#lineElementsCache.clear();
428
668
  this.#lastAccessedCharX = void 0;
429
669
  }
430
670
  #resetState() {
431
- this.#setSelectedLinesSafe(null);
671
+ this.#setEditorActiveLineSafe(null);
432
672
  this.#gutterWidthCache = void 0;
433
673
  this.#contentWidthCache = void 0;
434
674
  this.#shouldIgnoreSelectionChange = false;
675
+ this.#suppressNativeSelectionSync = false;
435
676
  this.#overlayElements?.forEach((el) => el.remove());
436
677
  this.#overlayElements = void 0;
437
678
  this.#selections = void 0;
@@ -443,6 +684,33 @@ var Editor = class {
443
684
  this.#searchPanel = void 0;
444
685
  this.#selectionAction?.cleanup();
445
686
  this.#selectionAction = void 0;
687
+ this.#canMountSelectionAction = true;
688
+ }
689
+ #invalidateOnAttach() {
690
+ const attachState = this.#attachState;
691
+ attachState.generation++;
692
+ if (attachState.callback != null) {
693
+ dequeueRender(attachState.callback);
694
+ attachState.callback = void 0;
695
+ }
696
+ }
697
+ #scheduleOnAttach(fileInstance) {
698
+ const attachState = this.#attachState;
699
+ const textDocument = this.#textDocument;
700
+ if (attachState.delivered || attachState.callback != null || textDocument == null) return;
701
+ const { generation } = attachState;
702
+ const callback = () => {
703
+ if (attachState.callback !== callback) return;
704
+ attachState.callback = void 0;
705
+ if (generation !== attachState.generation || this.#fileInstance !== fileInstance || this.#textDocument !== textDocument) return;
706
+ const contentElement = this.#contentElement;
707
+ const shadowRoot = this.#fileContainer?.shadowRoot;
708
+ if (contentElement == null || shadowRoot == null || !shadowRoot.contains(contentElement)) return;
709
+ attachState.delivered = true;
710
+ this.#options.onAttach?.(this, fileInstance);
711
+ };
712
+ attachState.callback = callback;
713
+ queueRender(callback);
446
714
  }
447
715
  #initialize() {
448
716
  this.#globalStyleElement = h("style", {
@@ -461,7 +729,26 @@ var Editor = class {
461
729
  this.#themeStyleElement = h("style", { dataset: "editorThemeCss" });
462
730
  this.#spriteElement = createSpriteElement();
463
731
  this.#overlayElement = h("div", { dataset: "editorOverlay" });
732
+ const replacementFocusTargetIsContent = (event) => {
733
+ const target = event.composedPath()[0];
734
+ return target instanceof Node && this.#contentElement?.contains(target) === true;
735
+ };
736
+ const cancelReplacementFocus = () => {
737
+ if (this.#replacementFocusRequest == null) return;
738
+ this.#replacementFocusRequest = void 0;
739
+ this.#contentHasFocus = false;
740
+ this.#shouldIgnoreSelectionChange = false;
741
+ };
464
742
  this.#globalEventDisposes = [
743
+ addEventListener(document, "focusin", (event) => {
744
+ if (!replacementFocusTargetIsContent(event)) cancelReplacementFocus();
745
+ }, { passive: true }),
746
+ addEventListener(document, "pointerdown", (event) => {
747
+ if (!replacementFocusTargetIsContent(event)) queueMicrotask(cancelReplacementFocus);
748
+ }, {
749
+ capture: true,
750
+ passive: true
751
+ }),
465
752
  addEventListener(document, "selectionchange", () => {
466
753
  const shadowRoot = this.#fileContainer?.shadowRoot;
467
754
  if (this.#shouldIgnoreSelectionChange || this.#suppressNativeSelectionSync || shadowRoot == null || !this.#contentHasFocus) return;
@@ -514,6 +801,12 @@ var Editor = class {
514
801
  this.#selectEventDisposes?.forEach((dispose) => dispose());
515
802
  this.#selectEventDisposes = disposes;
516
803
  }
804
+ #isLineSelectionEnabled() {
805
+ return this.#fileInstance?.options.enableLineSelection === true;
806
+ }
807
+ #preserveEditorSelectionsForGutterGesture() {
808
+ this.#suppressNativeSelectionSync = this.#selections !== void 0;
809
+ }
517
810
  #listenContentElement(contentEl, gutterEl) {
518
811
  const { onFocus, onBlur } = this.#options;
519
812
  const targetIsContentElement = (e) => {
@@ -528,10 +821,15 @@ var Editor = class {
528
821
  if (!this.#isContentMouseDown && !this.#shouldIgnoreSelectionChange && this.#selections !== void 0 && this.#selections.length > 0) this.#setWindowSelection(this.#selections.at(-1));
529
822
  }, { passive: true }),
530
823
  addEventListener(contentEl, "blur", () => {
824
+ if (this.#replacementFocusRequest?.target === contentEl) {
825
+ this.#replacementFocusRequest = void 0;
826
+ this.#shouldIgnoreSelectionChange = false;
827
+ }
531
828
  this.#contentHasFocus = false;
532
829
  onBlur?.();
533
830
  }, { passive: true }),
534
831
  addEventListener(contentEl, "pointerdown", (e) => {
832
+ this.#canMountSelectionAction = true;
535
833
  this.#suppressNativeSelectionSync = false;
536
834
  if (e.pointerType !== "mouse") return;
537
835
  if (this.#isDeletedLineTarget(e)) {
@@ -540,7 +838,7 @@ var Editor = class {
540
838
  return;
541
839
  }
542
840
  this.#setDeletedTextSelectionActive(false);
543
- this.#markerRenderer?.removePopup();
841
+ this.#markerRenderer?.removePopover();
544
842
  if (isSafari() && this.#lineAnnotations !== void 0 && this.#lineAnnotations.length > 0) {
545
843
  const annotationDisposes = [...contentEl.querySelectorAll("[data-line-annotation]")].map((el) => [addEventListener(el, "mouseenter", () => {
546
844
  this.#shouldIgnoreSelectionChange = true;
@@ -573,27 +871,21 @@ var Editor = class {
573
871
  this.#retainSearchPanelFocus = false;
574
872
  this.#selectionAction?.cleanup();
575
873
  this.#selectionAction = void 0;
576
- if (this.#selections !== void 0 && this.#selections.length > 0) {
577
- const primarySelection = this.#selections.at(-1);
578
- if (!isCollapsedSelection(primarySelection) || this.#selections.length > 1) {
579
- const pos = getCaretPosition(primarySelection);
580
- this.#updateSelections([{
581
- start: pos,
582
- end: pos,
583
- direction: 0
584
- }]);
585
- this.#focus(pos);
586
- }
587
- }
874
+ this.#runCommand("simplifySelection");
588
875
  return;
589
876
  }
590
877
  if (!targetIsContentElement(e)) return;
878
+ this.#canMountSelectionAction = true;
591
879
  this.#suppressNativeSelectionSync = false;
592
880
  const mvShortcut = isMoveCursorShortcut(e);
593
881
  const textDocument = this.#textDocument;
594
882
  if (this.#selections !== void 0 && this.#selections.length > 0 && mvShortcut !== void 0 && textDocument !== void 0) {
595
- if (e.shiftKey) this.#updateSelections(mapSelectionShift(textDocument, this.#selections, mvShortcut));
596
- else this.#updateSelections(mapCursorMove(textDocument, this.#selections, mvShortcut));
883
+ const cursorMoveOptions = {
884
+ getSoftLineOffsets: this.#isWrap ? (line) => this.#wrapLineText(line) : void 0,
885
+ resolveRenderableLine: this.#resolveRenderableLine
886
+ };
887
+ if (e.shiftKey) this.#updateSelections(mapSelectionShift(textDocument, this.#selections, mvShortcut, cursorMoveOptions));
888
+ else this.#updateSelections(mapCursorMove(textDocument, this.#selections, mvShortcut, cursorMoveOptions));
597
889
  this.#scrollToPrimaryCaret();
598
890
  e.preventDefault();
599
891
  return;
@@ -626,19 +918,34 @@ var Editor = class {
626
918
  addEventListener(contentEl, "copy", (e) => {
627
919
  if (!targetIsContentElement(e)) return;
628
920
  e.preventDefault();
629
- e.clipboardData?.setData("text", this.#isDeletedTextSelectionActive() ? this.#deletedTextForClipboard() : this.#getSelectionText());
921
+ if (this.#isDeletedTextSelectionActive()) e.clipboardData?.setData("text", this.#deletedTextForClipboard());
922
+ else this.#writeSelectionClipboardData(e.clipboardData, this.#getSelectionText(), this.#getSelectionClipboardTexts());
630
923
  }),
631
924
  addEventListener(contentEl, "cut", (e) => {
632
925
  if (!targetIsContentElement(e)) return;
633
926
  e.preventDefault();
634
- e.clipboardData?.setData("text", this.#isDeletedTextSelectionActive() ? this.#deletedTextForClipboard() : this.#cutSelectionText());
927
+ if (this.#isDeletedTextSelectionActive()) e.clipboardData?.setData("text", this.#deletedTextForClipboard());
928
+ else {
929
+ const selectionTexts = this.#getSelectionClipboardTexts();
930
+ this.#writeSelectionClipboardData(e.clipboardData, this.#cutSelectionText(), selectionTexts);
931
+ }
635
932
  }),
636
933
  addEventListener(contentEl, "paste", (e) => {
637
934
  if (!targetIsContentElement(e)) return;
638
935
  e.preventDefault();
639
- const text = e.clipboardData?.getData("text");
936
+ const clipboardData = e.clipboardData;
640
937
  const textDocument = this.#textDocument;
641
- if (text !== void 0 && textDocument !== void 0) this.#replaceSelectionText(textDocument.normalizeEol(text), void 0, true);
938
+ if (clipboardData === null || textDocument === void 0) return;
939
+ let text = clipboardData.getData("text");
940
+ const selectionCount = this.#selections?.length ?? 0;
941
+ if (selectionCount > 1) {
942
+ const multiSelectionText = clipboardData.getData(MULTI_SELECTION_CLIPBOARD_TYPE);
943
+ if (multiSelectionText !== void 0) try {
944
+ const selectionTexts = JSON.parse(multiSelectionText);
945
+ if (Array.isArray(selectionTexts) && selectionTexts.length === selectionCount) text = selectionTexts;
946
+ } catch {}
947
+ }
948
+ this.#replaceSelectionText(Array.isArray(text) ? text.map((t) => textDocument.normalizeEol(t)) : textDocument.normalizeEol(text), void 0, true, "document");
642
949
  }),
643
950
  addEventListener(contentEl, "beforeinput", (e) => {
644
951
  if (!targetIsContentElement(e)) return;
@@ -665,9 +972,20 @@ var Editor = class {
665
972
  ];
666
973
  const deletionsCode = this.#fileContainer?.shadowRoot?.querySelector("[data-deletions]");
667
974
  if (deletionsCode != null) this.#editorEventDisposes.push(addEventListener(deletionsCode, "pointerdown", (e) => {
668
- const target = e.composedPath()[0];
975
+ const path = e.composedPath();
976
+ if (isGutterUtilityPath(path)) {
977
+ this.#preserveEditorSelectionsForGutterGesture();
978
+ return;
979
+ }
980
+ const target = path[0];
669
981
  const gutterRow = target instanceof HTMLElement ? target.closest("[data-column-number]") : null;
670
- if (gutterRow instanceof HTMLElement && this.#beginDeletionGutterSelection(gutterRow, deletionsCode, e.pointerType === "mouse")) return;
982
+ if (gutterRow instanceof HTMLElement) {
983
+ if (this.#isLineSelectionEnabled()) {
984
+ this.#preserveEditorSelectionsForGutterGesture();
985
+ return;
986
+ }
987
+ if (this.#beginDeletionGutterSelection(gutterRow, deletionsCode, e.pointerType === "mouse")) return;
988
+ }
671
989
  this.#setDeletedTextSelectionActive(true);
672
990
  if (this.#selections !== void 0) this.#updateSelections([]);
673
991
  }, { passive: true }));
@@ -686,7 +1004,16 @@ var Editor = class {
686
1004
  return lineNumber - 1;
687
1005
  };
688
1006
  this.#editorEventDisposes.push(addEventListener(gutterEl, "pointerdown", (e) => {
689
- const gutterRow = resolveGutterTarget(e.composedPath()[0]);
1007
+ const path = e.composedPath();
1008
+ if (isGutterUtilityPath(path)) {
1009
+ this.#preserveEditorSelectionsForGutterGesture();
1010
+ return;
1011
+ }
1012
+ const gutterRow = resolveGutterTarget(path[0]);
1013
+ if (gutterRow !== void 0 && this.#isLineSelectionEnabled()) {
1014
+ this.#preserveEditorSelectionsForGutterGesture();
1015
+ return;
1016
+ }
690
1017
  if (gutterRow?.dataset.lineType === "change-deletion") {
691
1018
  const code = gutterRow.closest("[data-code]");
692
1019
  if (code != null) this.#beginDeletionGutterSelection(gutterRow, code, e.pointerType === "mouse");
@@ -696,7 +1023,8 @@ var Editor = class {
696
1023
  const textDocument = this.#textDocument;
697
1024
  const lineIndex = resolveEditableLine(gutterRow);
698
1025
  if (lineIndex === void 0 || textDocument === void 0) return;
699
- this.#markerRenderer?.removePopup();
1026
+ this.#canMountSelectionAction = true;
1027
+ this.#markerRenderer?.removePopover();
700
1028
  const selection = this.#spanLineSelection(lineIndex, lineIndex, textDocument);
701
1029
  this.#isGutterMouseDown = true;
702
1030
  this.#selectionStart = selection;
@@ -726,8 +1054,17 @@ var Editor = class {
726
1054
  #handleCustomPasteEvent = async () => {
727
1055
  const clipboard = this.#options.clipboard;
728
1056
  if (clipboard !== void 0) {
729
- const text = await clipboard.readText();
730
- this.#replaceSelectionText(text, void 0, true);
1057
+ let text = await clipboard.readText();
1058
+ const selectionCount = this.#selections?.length ?? 0;
1059
+ if (selectionCount > 1) {
1060
+ const multiSelectionText = await clipboard.readText(MULTI_SELECTION_CLIPBOARD_TYPE);
1061
+ try {
1062
+ const selectionTexts = JSON.parse(multiSelectionText);
1063
+ if (Array.isArray(selectionTexts) && selectionTexts.length === selectionCount) text = selectionTexts;
1064
+ } catch {}
1065
+ }
1066
+ const textDocument = this.#textDocument;
1067
+ if (textDocument !== void 0) this.#replaceSelectionText(Array.isArray(text) ? text.map((t) => textDocument.normalizeEol(t)) : textDocument.normalizeEol(text), void 0, true, "document");
731
1068
  }
732
1069
  };
733
1070
  #computeContentOffset(contentEl) {
@@ -763,9 +1100,11 @@ var Editor = class {
763
1100
  this.#updateSelections(expanded);
764
1101
  this.focus();
765
1102
  } else {
766
- const nextMatch = findNexMatch(textDocument, selections);
1103
+ const nextMatch = findNextMatch(textDocument, selections);
767
1104
  if (nextMatch !== void 0) {
768
1105
  this.#updateSelections(nextMatch);
1106
+ const primaryMatch = nextMatch.at(-1);
1107
+ if (primaryMatch !== void 0) this.#revealLineIfCollapsed(getCaretPosition(primaryMatch).line);
769
1108
  this.#scrollToPrimaryCaret();
770
1109
  }
771
1110
  }
@@ -775,18 +1114,74 @@ var Editor = class {
775
1114
  case "moveLineDown":
776
1115
  this.#moveSelectedLines(command === "moveLineUp" ? -1 : 1);
777
1116
  break;
1117
+ case "copyLineUp":
1118
+ case "copyLineDown":
1119
+ this.#copySelectedLines(command === "copyLineUp" ? -1 : 1);
1120
+ break;
1121
+ case "simplifySelection": {
1122
+ const selections = this.#selections;
1123
+ const primarySelection = selections?.at(-1);
1124
+ if (selections === void 0 || primarySelection === void 0) break;
1125
+ if (selections.length > 1) {
1126
+ this.#updateSelections([primarySelection]);
1127
+ this.#focus(getCaretPosition(primarySelection));
1128
+ } else if (!isCollapsedSelection(primarySelection)) {
1129
+ const caret = getCaretPosition(primarySelection);
1130
+ this.#updateSelections([{
1131
+ start: caret,
1132
+ end: caret,
1133
+ direction: 0
1134
+ }]);
1135
+ this.#focus(caret);
1136
+ }
1137
+ break;
1138
+ }
1139
+ case "insertBlankLine":
1140
+ this.#insertBlankLine();
1141
+ break;
1142
+ case "deleteHardLineForward":
1143
+ this.#deleteHardLineForward();
1144
+ break;
1145
+ case "toggleComment":
1146
+ case "toggleBlockComment": {
1147
+ const selections = this.#selections;
1148
+ if (selections === void 0) break;
1149
+ const { lineComment, blockComment } = resolveCommentConfig(textDocument.languageId, this.#options.languageCommentConfig);
1150
+ if (command === "toggleComment" && lineComment !== null) {
1151
+ this.#applyCommandEdits(resolveLineCommentEdits(textDocument, selections, lineComment));
1152
+ break;
1153
+ }
1154
+ const linewise = command === "toggleComment";
1155
+ const result = resolveBlockCommentEdits(textDocument, selections, blockComment, linewise);
1156
+ if (result !== void 0) this.#applyCommandEdits(result.edits, linewise ? void 0 : (document) => result.nextSelectionOffsets.map(([start, end, direction]) => ({
1157
+ start: document.positionAt(start),
1158
+ end: document.positionAt(end),
1159
+ direction
1160
+ })));
1161
+ break;
1162
+ }
778
1163
  case "indent":
779
1164
  case "outdent":
1165
+ case "indentLess":
1166
+ case "indentMore":
780
1167
  if (this.#selections !== void 0) {
781
1168
  const edits = [];
782
1169
  const nextSelections = [];
1170
+ const editedLines = /* @__PURE__ */ new Set();
783
1171
  const sameLineIndents = [];
784
1172
  for (const selection of this.#selections) {
785
1173
  const startLine = selection.start.line;
786
- const outdent = command === "outdent";
787
- if (startLine !== selection.end.line || outdent) {
1174
+ const outdent = command === "outdent" || command === "indentLess";
1175
+ const lineBased = command === "indentLess" || command === "indentMore";
1176
+ if (startLine !== selection.end.line || outdent || lineBased) {
788
1177
  const ret = resolveIndentEdits(textDocument, selection, this.#metrics.tabSize, outdent);
789
- edits.push(...ret[0]);
1178
+ for (const edit of ret[0]) {
1179
+ const line = edit.range.start.line;
1180
+ if (!editedLines.has(line)) {
1181
+ editedLines.add(line);
1182
+ edits.push(edit);
1183
+ }
1184
+ }
790
1185
  nextSelections.push(ret[1]);
791
1186
  } else {
792
1187
  const text = textDocument.charAt({
@@ -838,17 +1233,19 @@ var Editor = class {
838
1233
  const fullSelection = getDocumentFullSelection(textDocument);
839
1234
  this.#updateSelections([fullSelection]);
840
1235
  this.focus();
841
- const renderedLines = this.#getRenderedEditableLineRange();
842
- if (renderedLines !== void 0) {
1236
+ const renderedLineRanges = this.#getRenderedEditableLineRanges();
1237
+ const firstRenderedLine = renderedLineRanges[0]?.[0];
1238
+ const lastRenderedLine = renderedLineRanges.at(-1)?.[1];
1239
+ if (firstRenderedLine !== void 0 && lastRenderedLine !== void 0) {
843
1240
  this.#suppressNativeSelectionSync = true;
844
1241
  this.#setWindowSelection({
845
1242
  start: {
846
- line: renderedLines.first,
1243
+ line: firstRenderedLine,
847
1244
  character: 0
848
1245
  },
849
1246
  end: {
850
- line: renderedLines.last,
851
- character: textDocument.getLineLength(renderedLines.last)
1247
+ line: lastRenderedLine,
1248
+ character: textDocument.getLineLength(lastRenderedLine)
852
1249
  },
853
1250
  direction: 1
854
1251
  });
@@ -858,8 +1255,9 @@ var Editor = class {
858
1255
  case "moveCursorToDocStart":
859
1256
  case "moveCursorToDocEnd":
860
1257
  {
861
- const atEnd = command === "moveCursorToDocEnd";
862
- this.#updateSelections([getDocumentBoundarySelection(textDocument, atEnd, this.#isDiff)]);
1258
+ const boundarySelection = getDocumentBoundarySelection(textDocument, command === "moveCursorToDocEnd", this.#isDiff);
1259
+ this.#updateSelections([boundarySelection]);
1260
+ this.#revealLineIfCollapsed(getCaretPosition(boundarySelection).line);
863
1261
  this.#scrollToPrimaryCaret();
864
1262
  }
865
1263
  break;
@@ -869,25 +1267,191 @@ var Editor = class {
869
1267
  const atEnd = command === "expandSelectionDocEnd";
870
1268
  const selections = this.#selections;
871
1269
  if (selections !== void 0) {
872
- this.#updateSelections(extendSelections(selections, getDocumentBoundarySelection(textDocument, atEnd, this.#isDiff)));
1270
+ const boundarySelection = getDocumentBoundarySelection(textDocument, atEnd, this.#isDiff);
1271
+ this.#updateSelections(extendSelections(selections, boundarySelection));
1272
+ this.#revealLineIfCollapsed(getCaretPosition(boundarySelection).line);
873
1273
  this.#scrollToPrimaryCaret();
874
1274
  }
875
1275
  }
876
1276
  break;
877
1277
  case "undo":
878
- if (this.#textDocument?.canUndo === true) {
879
- const undoResult = this.#textDocument.undo();
880
- if (undoResult !== void 0) this.#applyChange(...undoResult);
881
- }
882
- break;
883
1278
  case "redo":
884
- if (this.#textDocument?.canRedo === true) {
885
- const redoResult = this.#textDocument.redo();
886
- if (redoResult !== void 0) this.#applyChange(...redoResult);
887
- }
1279
+ this.#applyHistoryChange(command);
888
1280
  break;
889
1281
  }
890
1282
  }
1283
+ #applyHistoryChange(command) {
1284
+ const textDocument = this.#textDocument;
1285
+ if (textDocument === void 0) return;
1286
+ if (command === "undo" && !textDocument.canUndo || command === "redo" && !textDocument.canRedo) return;
1287
+ const selections = this.#selections;
1288
+ const selectionOffsets = selections?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
1289
+ const result = command === "undo" ? textDocument.undo() : textDocument.redo();
1290
+ if (result === void 0) return;
1291
+ const [change, recordedSelections, lineAnnotations, selectionEdits] = result;
1292
+ const nextSelections = recordedSelections ?? (selections !== void 0 && selectionOffsets !== void 0 && selectionEdits !== void 0 ? remapSelectionsAfterEdits(textDocument, selections, selectionOffsets, selectionEdits) : void 0);
1293
+ this.#applyChange(change, nextSelections, lineAnnotations);
1294
+ }
1295
+ /** Applies one undoable command batch and records its resulting selections. */
1296
+ #applyCommandEdits(edits, resolveNextSelections) {
1297
+ const textDocument = this.#textDocument;
1298
+ const selections = this.#selections;
1299
+ if (textDocument === void 0 || selections === void 0) return;
1300
+ const remapSelections = resolveNextSelections === void 0;
1301
+ const selectionOffsets = remapSelections ? selections.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]) : void 0;
1302
+ const resolvedEdits = remapSelections ? edits.map((edit) => {
1303
+ const start = textDocument.offsetAt(edit.range.start);
1304
+ const end = textDocument.offsetAt(edit.range.end);
1305
+ return {
1306
+ start: Math.min(start, end),
1307
+ end: Math.max(start, end),
1308
+ text: edit.newText
1309
+ };
1310
+ }).sort((a, b) => {
1311
+ const startOrder = a.start - b.start;
1312
+ return startOrder !== 0 ? startOrder : a.end - b.end;
1313
+ }) : void 0;
1314
+ const change = textDocument.applyEdits(edits, true, selections, void 0, true);
1315
+ if (change === void 0) return;
1316
+ const nextSelections = remapSelections ? remapSelectionsAfterEdits(textDocument, selections, selectionOffsets, resolvedEdits) : resolveNextSelections(textDocument);
1317
+ textDocument.setLastUndoSelectionsAfter(nextSelections);
1318
+ this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
1319
+ }
1320
+ /** Copies selected line blocks and keeps the selection in the requested copy. */
1321
+ #copySelectedLines(direction) {
1322
+ const textDocument = this.#textDocument;
1323
+ const selections = this.#selections;
1324
+ if (textDocument === void 0 || selections === void 0) return;
1325
+ const blocks = getSelectedLineBlocks(selections);
1326
+ if (blocks.length === 0) return;
1327
+ const copiedLinesBefore = [];
1328
+ let copiedLineCount = 0;
1329
+ const edits = [];
1330
+ for (const block of blocks) {
1331
+ copiedLinesBefore.push(copiedLineCount);
1332
+ const blockLineCount = block.endLine - block.startLine + 1;
1333
+ copiedLineCount += blockLineCount;
1334
+ const text = textDocument.getText({
1335
+ start: {
1336
+ line: block.startLine,
1337
+ character: 0
1338
+ },
1339
+ end: {
1340
+ line: block.endLine,
1341
+ character: textDocument.getLineLength(block.endLine)
1342
+ }
1343
+ });
1344
+ if (direction > 0) {
1345
+ const position = {
1346
+ line: block.startLine,
1347
+ character: 0
1348
+ };
1349
+ edits.push({
1350
+ range: {
1351
+ start: position,
1352
+ end: position
1353
+ },
1354
+ newText: text + textDocument.eol
1355
+ });
1356
+ } else if (block.endLine < textDocument.lineCount - 1) {
1357
+ const position = {
1358
+ line: block.endLine + 1,
1359
+ character: 0
1360
+ };
1361
+ edits.push({
1362
+ range: {
1363
+ start: position,
1364
+ end: position
1365
+ },
1366
+ newText: text + textDocument.eol
1367
+ });
1368
+ } else {
1369
+ const position = {
1370
+ line: block.endLine,
1371
+ character: textDocument.getLineLength(block.endLine)
1372
+ };
1373
+ edits.push({
1374
+ range: {
1375
+ start: position,
1376
+ end: position
1377
+ },
1378
+ newText: textDocument.eol + text
1379
+ });
1380
+ }
1381
+ }
1382
+ const nextSelections = selections.map((selection) => {
1383
+ const line = selection.start.line;
1384
+ let low = 0;
1385
+ let high = blocks.length - 1;
1386
+ while (low <= high) {
1387
+ const middle = low + high >>> 1;
1388
+ const block = blocks[middle];
1389
+ if (line < block.startLine) high = middle - 1;
1390
+ else if (line > block.endLine) low = middle + 1;
1391
+ else {
1392
+ low = middle;
1393
+ break;
1394
+ }
1395
+ }
1396
+ const blockIndex = Math.max(0, low <= high ? low : high);
1397
+ const block = blocks[blockIndex];
1398
+ const shift = copiedLinesBefore[blockIndex] + (direction > 0 ? block.endLine - block.startLine + 1 : 0);
1399
+ return {
1400
+ start: {
1401
+ ...selection.start,
1402
+ line: selection.start.line + shift
1403
+ },
1404
+ end: {
1405
+ ...selection.end,
1406
+ line: selection.end.line + shift
1407
+ },
1408
+ direction: selection.direction
1409
+ };
1410
+ });
1411
+ this.#applyCommandEdits(edits, () => nextSelections);
1412
+ }
1413
+ /** Inserts an indented blank line after each selection's final line. */
1414
+ #insertBlankLine() {
1415
+ const textDocument = this.#textDocument;
1416
+ const selections = this.#selections;
1417
+ if (textDocument === void 0 || selections === void 0) return;
1418
+ const selectionLines = selections.map((selection) => getCaretPosition(selection).line);
1419
+ const targetLines = Array.from(new Set(selectionLines)).sort((a, b) => a - b);
1420
+ const targetIndex = /* @__PURE__ */ new Map();
1421
+ const indents = /* @__PURE__ */ new Map();
1422
+ const edits = [];
1423
+ for (let index = 0; index < targetLines.length; index++) {
1424
+ const line = targetLines[index];
1425
+ const lineText = textDocument.getLineText(line);
1426
+ const indent = /^\s*/.exec(lineText)?.[0] ?? "";
1427
+ targetIndex.set(line, index);
1428
+ indents.set(line, indent);
1429
+ const position = {
1430
+ line,
1431
+ character: textDocument.getLineLength(line)
1432
+ };
1433
+ edits.push({
1434
+ range: {
1435
+ start: position,
1436
+ end: position
1437
+ },
1438
+ newText: textDocument.eol + indent
1439
+ });
1440
+ }
1441
+ const nextSelections = selections.map((_, index) => {
1442
+ const line = selectionLines[index];
1443
+ const position = {
1444
+ line: line + 1 + targetIndex.get(line),
1445
+ character: indents.get(line).length
1446
+ };
1447
+ return {
1448
+ start: position,
1449
+ end: position,
1450
+ direction: 0
1451
+ };
1452
+ });
1453
+ this.#applyCommandEdits(edits, () => nextSelections);
1454
+ }
891
1455
  #moveSelectedLines(direction) {
892
1456
  const textDocument = this.#textDocument;
893
1457
  const selections = this.#selections;
@@ -956,15 +1520,15 @@ var Editor = class {
956
1520
  this.#lineElementsCache.clear();
957
1521
  this.#lastAccessedCharX = void 0;
958
1522
  this.#metrics.clearTextWidthCache();
959
- if (contentWidthChanged && (this.#isWrap || lineAnnotations > 0)) {
960
- this.#lineYCache.clear();
1523
+ if (contentWidthChanged) {
961
1524
  this.#wrapLineOffsetsCache.clear();
1525
+ if (this.#isWrap || lineAnnotations > 0) this.#lineYCache.clear();
962
1526
  }
963
1527
  if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) {
964
1528
  this.#updateSelections(this.#selections ?? []);
965
- if (this.#selections !== void 0) this.focus();
1529
+ if (this.#selections !== void 0 && this.#contentHasFocus) this.focus();
966
1530
  }
967
- this.#markerRenderer?.removePopup();
1531
+ this.#markerRenderer?.removePopover();
968
1532
  this.#computeContentOffset(this.#contentElement);
969
1533
  };
970
1534
  #remeasureMetricsOnFontLoad() {
@@ -977,8 +1541,9 @@ var Editor = class {
977
1541
  this.#gutterWidthCache = void 0;
978
1542
  this.#contentWidthCache = void 0;
979
1543
  this.#resetCache();
1544
+ this.#wrapLineOffsetsCache.clear();
980
1545
  if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
981
- this.#markerRenderer?.removePopup();
1546
+ this.#markerRenderer?.removePopover();
982
1547
  });
983
1548
  }
984
1549
  #rerender(change, newLineAnnotations, renderRange = this.#renderRange, shouldUpdateBuffer) {
@@ -994,45 +1559,57 @@ var Editor = class {
994
1559
  const t2 = performance.now();
995
1560
  if (dirtyLines.size > 0) {
996
1561
  const children = contentEl.children;
997
- const dirtyLineIndexes = new Set(dirtyLines.keys());
998
- const startingLine = renderRange?.startingLine ?? 0;
999
- for (let i = change.startLine - startingLine; i < children.length; i++) {
1562
+ const dirtyLineIndexes = /* @__PURE__ */ new Set();
1563
+ for (const lineIndex of dirtyLines.keys()) if (this.#isLineRenderable(lineIndex)) dirtyLineIndexes.add(lineIndex);
1564
+ for (let i = 0; i < children.length; i++) {
1000
1565
  const child = children[i];
1001
1566
  if (child !== void 0) {
1002
1567
  const lineNumber = getLineNumberAttr(child);
1003
1568
  const lineType = child.dataset.lineType;
1004
1569
  if (lineNumber === void 0 || lineType === "change-deletion") continue;
1005
1570
  const lineIndex = lineNumber - 1;
1006
- if (dirtyLines.has(lineIndex)) {
1571
+ if (dirtyLineIndexes.has(lineIndex)) {
1007
1572
  const tokens = dirtyLines.get(lineIndex);
1008
- child.replaceChildren(...renderLineTokens(tokens, tokenizer.themeType));
1573
+ child.replaceChildren(...renderLineTokens(tokens));
1009
1574
  dirtyLineIndexes.delete(lineIndex);
1010
1575
  if (dirtyLineIndexes.size === 0) break;
1011
1576
  }
1012
1577
  }
1013
1578
  }
1014
- if (dirtyLineIndexes.size > 0) for (const lineIndex of dirtyLineIndexes) {
1015
- const tokens = dirtyLines.get(lineIndex);
1016
- const lineNumber = String(lineIndex + 1);
1017
- h("div", {
1018
- dataset: {
1019
- line: lineNumber,
1020
- lineType: "context",
1021
- lineIndex: lineIndex.toString()
1022
- },
1023
- children: renderLineTokens(tokens, tokenizer.themeType)
1024
- }, contentEl);
1025
- if (gutterEl !== void 0) h("div", {
1026
- dataset: {
1027
- lineType: "context",
1028
- columnNumber: lineNumber,
1029
- lineIndex: lineIndex.toString()
1030
- },
1031
- children: [h("span", {
1032
- dataset: { lineNumberContent: "" },
1033
- textContent: lineNumber
1034
- })]
1035
- }, gutterEl);
1579
+ if (dirtyLineIndexes.size > 0) {
1580
+ let lastRenderedLineNumber = 0;
1581
+ for (let i = children.length - 1; i >= 0; i--) {
1582
+ const child = children[i];
1583
+ const lineNumber = getLineNumberAttr(child);
1584
+ if (lineNumber !== void 0 && child.dataset.lineType !== "change-deletion") {
1585
+ lastRenderedLineNumber = lineNumber;
1586
+ break;
1587
+ }
1588
+ }
1589
+ for (const lineIndex of dirtyLineIndexes) {
1590
+ if (lineIndex < lastRenderedLineNumber) continue;
1591
+ const tokens = dirtyLines.get(lineIndex);
1592
+ const lineNumber = String(lineIndex + 1);
1593
+ h("div", {
1594
+ dataset: {
1595
+ line: lineNumber,
1596
+ lineType: "context",
1597
+ lineIndex: lineIndex.toString()
1598
+ },
1599
+ children: renderLineTokens(tokens)
1600
+ }, contentEl);
1601
+ if (gutterEl !== void 0) h("div", {
1602
+ dataset: {
1603
+ lineType: "context",
1604
+ columnNumber: lineNumber,
1605
+ lineIndex: lineIndex.toString()
1606
+ },
1607
+ children: [h("span", {
1608
+ dataset: { lineNumberContent: "" },
1609
+ textContent: lineNumber
1610
+ })]
1611
+ }, gutterEl);
1612
+ }
1036
1613
  }
1037
1614
  }
1038
1615
  if (change.lineDelta < 0) for (const children of [contentEl.children, gutterEl?.children ?? []]) for (let i = children.length - 1; i >= 0; i--) {
@@ -1053,9 +1630,12 @@ var Editor = class {
1053
1630
  contentEl.style.gridRow = "span " + gridRow;
1054
1631
  if (gutterEl !== void 0) gutterEl.style.gridRow = "span " + gridRow;
1055
1632
  }
1056
- fileInstance.updateRenderCache(dirtyLines, tokenizer.themeType, !didLineCountChange);
1633
+ fileInstance.updateRenderCache(dirtyLines, tokenizer.themeType, {
1634
+ shouldRefreshDiffsView: this.#isDiff && !didLineCountChange,
1635
+ lineCountChangeInFlight: didLineCountChange
1636
+ });
1057
1637
  if (didLineCountChange) fileInstance.applyDocumentChange(textDocument, newLineAnnotations, shouldUpdateBuffer);
1058
- if (this.#isDiff && (this.#diffSyle === "unified" || didLineCountChange)) this.#resetCache();
1638
+ if (didLineCountChange || this.#isDiff && this.#diffSyle === "unified") this.#resetCache();
1059
1639
  if (newLineAnnotations !== void 0) {
1060
1640
  this.#lineAnnotations = newLineAnnotations;
1061
1641
  renderLineAnnotations(newLineAnnotations, contentEl, gutterEl);
@@ -1075,7 +1655,7 @@ var Editor = class {
1075
1655
  case "insertCompositionText": break;
1076
1656
  case "insertLineBreak":
1077
1657
  case "insertParagraph":
1078
- this.#replaceSelectionText("\n");
1658
+ this.#replaceSelectionText(this.#textDocument?.eol ?? "\n");
1079
1659
  break;
1080
1660
  case "deleteContentBackward":
1081
1661
  this.#deleteSelectionText();
@@ -1087,9 +1667,6 @@ var Editor = class {
1087
1667
  case "deleteHardLineBackward":
1088
1668
  this.#deleteSoftLineBackward();
1089
1669
  break;
1090
- case "deleteHardLineForward":
1091
- this.#deleteHardLineForward();
1092
- break;
1093
1670
  case "deleteWordBackward":
1094
1671
  this.#deleteWordBackward();
1095
1672
  break;
@@ -1101,7 +1678,7 @@ var Editor = class {
1101
1678
  break;
1102
1679
  }
1103
1680
  }
1104
- #focus(position, preventScroll = true) {
1681
+ #focus(position, preventScroll = true, shouldFocus) {
1105
1682
  this.#contentHasFocus = true;
1106
1683
  if (position !== void 0) {
1107
1684
  this.#shouldIgnoreSelectionChange = true;
@@ -1110,13 +1687,115 @@ var Editor = class {
1110
1687
  end: position,
1111
1688
  direction: 0
1112
1689
  });
1113
- requestAnimationFrame(() => {
1690
+ queueRender(() => {
1691
+ if (shouldFocus?.() === false || !this.#contentHasFocus) {
1692
+ this.#shouldIgnoreSelectionChange = false;
1693
+ return;
1694
+ }
1114
1695
  this.#contentElement?.focus({ preventScroll });
1115
- requestAnimationFrame(() => {
1696
+ queueRender(() => {
1116
1697
  this.#shouldIgnoreSelectionChange = false;
1117
1698
  });
1118
1699
  });
1119
- } else this.#contentElement?.focus({ preventScroll });
1700
+ } else if (shouldFocus?.() !== false) this.#contentElement?.focus({ preventScroll });
1701
+ else this.#contentHasFocus = false;
1702
+ }
1703
+ #restoreReplacementFocus(position) {
1704
+ const request = this.#replacementFocusRequest;
1705
+ if (request == null) return;
1706
+ if (this.#retainSearchPanelFocus) {
1707
+ this.#replacementFocusRequest = void 0;
1708
+ this.#contentHasFocus = false;
1709
+ this.#shouldIgnoreSelectionChange = false;
1710
+ return;
1711
+ }
1712
+ const contentElement = this.#contentElement;
1713
+ if (contentElement == null || !this.#replacementFocusIsAvailable(contentElement)) {
1714
+ this.#replacementFocusRequest = void 0;
1715
+ this.#contentHasFocus = false;
1716
+ return;
1717
+ }
1718
+ const shouldFocus = () => {
1719
+ if (this.#replacementFocusRequest !== request) return false;
1720
+ const shouldRestore = this.#replacementFocusIsAvailable(contentElement);
1721
+ this.#replacementFocusRequest = void 0;
1722
+ return shouldRestore;
1723
+ };
1724
+ if (position != null) {
1725
+ this.#focus(position, true, shouldFocus);
1726
+ return;
1727
+ }
1728
+ const primarySelection = this.#selections?.at(-1);
1729
+ if (primarySelection == null) {
1730
+ this.#focus(void 0, true, shouldFocus);
1731
+ return;
1732
+ }
1733
+ const primaryPosition = primarySelection.direction === -1 ? primarySelection.end : primarySelection.start;
1734
+ this.#focus(primaryPosition, true, shouldFocus);
1735
+ }
1736
+ #replacementFocusIsAvailable(contentElement) {
1737
+ const fileContainer = this.#fileContainer;
1738
+ const shadowRoot = fileContainer?.shadowRoot;
1739
+ if (fileContainer == null || shadowRoot == null || this.#contentElement !== contentElement || !shadowRoot.contains(contentElement)) return false;
1740
+ const { activeElement: shadowActiveElement } = shadowRoot;
1741
+ if (shadowActiveElement === contentElement || shadowActiveElement != null && contentElement.contains(shadowActiveElement)) return true;
1742
+ if (shadowActiveElement != null) return false;
1743
+ const { ownerDocument } = fileContainer;
1744
+ const { activeElement } = ownerDocument;
1745
+ return activeElement == null || activeElement === ownerDocument.body || activeElement === ownerDocument.documentElement || activeElement === fileContainer;
1746
+ }
1747
+ #getFirstVisibleLineNumber(offset = 0) {
1748
+ const contentElement = this.#contentElement;
1749
+ const fileContainer = this.#fileContainer;
1750
+ if (this.#textDocument == null || contentElement == null || fileContainer == null) return;
1751
+ const viewport = this.#fileInstance?.getEditorViewport?.() ?? this.#getDefaultEditorViewport(fileContainer);
1752
+ let viewportTop;
1753
+ let viewportBottom;
1754
+ if (viewport instanceof HTMLElement) {
1755
+ const viewportRect = viewport.getBoundingClientRect();
1756
+ viewportTop = viewportRect.top;
1757
+ viewportBottom = viewportRect.bottom;
1758
+ } else {
1759
+ const viewportWindow = viewport.defaultView;
1760
+ if (viewportWindow == null) return;
1761
+ viewportTop = 0;
1762
+ viewportBottom = viewportWindow.innerHeight;
1763
+ }
1764
+ const stickyHeader = fileContainer.shadowRoot?.querySelector("[data-diffs-header][data-sticky]");
1765
+ if (stickyHeader != null) viewportTop = Math.max(viewportTop, stickyHeader.getBoundingClientRect().bottom);
1766
+ if (Number.isFinite(offset)) viewportTop += Math.max(0, offset);
1767
+ if (viewportBottom <= viewportTop) return;
1768
+ for (const child of contentElement.children) {
1769
+ const row = child;
1770
+ const lineNumber = getLineNumberAttr(row);
1771
+ const lineType = row.dataset.lineType;
1772
+ if (lineNumber == null || lineType == null || !isLineEditable(lineType)) continue;
1773
+ const rowRect = row.getBoundingClientRect();
1774
+ if (rowRect.bottom <= rowRect.top) continue;
1775
+ if (rowRect.top >= viewportTop && rowRect.top < viewportBottom) return lineNumber;
1776
+ }
1777
+ }
1778
+ #getDefaultEditorViewport(fileContainer) {
1779
+ const ownerDocument = fileContainer.ownerDocument;
1780
+ let element = fileContainer.parentElement;
1781
+ while (element != null && element !== ownerDocument.body && element !== ownerDocument.documentElement) {
1782
+ const overflowY = element.ownerDocument.defaultView?.getComputedStyle(element).overflowY;
1783
+ if (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") return element;
1784
+ element = element.parentElement;
1785
+ }
1786
+ return ownerDocument;
1787
+ }
1788
+ #focusAtPosition(position, preventScroll) {
1789
+ const selection = {
1790
+ start: position,
1791
+ end: position,
1792
+ direction: 0
1793
+ };
1794
+ this.#canMountSelectionAction = false;
1795
+ this.#updateSelections([selection]);
1796
+ this.#revealLineIfCollapsed(position.line);
1797
+ if (preventScroll) this.#focus(position, true);
1798
+ else this.#scrollToPrimaryCaret();
1120
1799
  }
1121
1800
  #setWindowSelection(selection) {
1122
1801
  const winSelection = window.getSelection();
@@ -1149,7 +1828,11 @@ var Editor = class {
1149
1828
  block: scrollPosition,
1150
1829
  inline: "nearest"
1151
1830
  });
1152
- if (!noFocus) this.#focus(primarySelection.direction === -1 ? primarySelection.end : primarySelection.start);
1831
+ if (!noFocus) {
1832
+ const position = primarySelection.direction === -1 ? primarySelection.end : primarySelection.start;
1833
+ if (this.#replacementFocusRequest !== void 0) this.#restoreReplacementFocus(position);
1834
+ else this.#focus(position);
1835
+ }
1153
1836
  } else {
1154
1837
  const pos = getCaretPosition(primarySelection);
1155
1838
  this.#scrollToLine(pos.line, pos.character, noFocus);
@@ -1179,10 +1862,14 @@ var Editor = class {
1179
1862
  block: "center",
1180
1863
  inline: "nearest"
1181
1864
  });
1182
- if (!noFocus) this.#focus({
1183
- line,
1184
- character: char
1185
- });
1865
+ if (!noFocus) {
1866
+ const position = {
1867
+ line,
1868
+ character: char
1869
+ };
1870
+ if (this.#replacementFocusRequest !== void 0) this.#restoreReplacementFocus(position);
1871
+ else this.#focus(position);
1872
+ }
1186
1873
  this.#scrollingToLine = void 0;
1187
1874
  this.#scrollingToLineChar = void 0;
1188
1875
  this.#scrollingToLineFixed = false;
@@ -1196,7 +1883,7 @@ var Editor = class {
1196
1883
  block: "center",
1197
1884
  inline: "nearest"
1198
1885
  });
1199
- if (modelLinePosition.height > 0) {
1886
+ if (modelLinePosition.height > 0 && this.#isLineRenderable(line)) {
1200
1887
  this.#scrollingToLine = line;
1201
1888
  this.#scrollingToLineChar = char;
1202
1889
  this.#scrollingToLineNoFocus = noFocus;
@@ -1223,7 +1910,7 @@ var Editor = class {
1223
1910
  block: "center",
1224
1911
  inline: "nearest"
1225
1912
  });
1226
- if (this.#scrollingToLine === line && (yFix === 0 || this.#scrollingToLineFixed)) {
1913
+ if (this.#scrollingToLine === line && (yFix === 0 || this.#scrollingToLineFixed) || !this.#isLineRenderable(line)) {
1227
1914
  this.#scrollingToLine = void 0;
1228
1915
  this.#scrollingToLineChar = void 0;
1229
1916
  this.#scrollingToLineFixed = false;
@@ -1277,7 +1964,7 @@ var Editor = class {
1277
1964
  }
1278
1965
  this.#setDeletedTextSelectionActive(true);
1279
1966
  this.#updateSelections([]);
1280
- [...deletionsCode.querySelectorAll("[data-gutter] > [data-column-number]")][focusIndex]?.setAttribute("data-selected-line", "single");
1967
+ this.#setEditorActiveLineSafe(getLineNumberAttr(focusContent) ?? null, true, "deletions");
1281
1968
  const lo = Math.min(anchorIndex, focusIndex);
1282
1969
  const hi = Math.max(anchorIndex, focusIndex);
1283
1970
  this.#deletedSelectionText = lo < 0 ? "" : rows.slice(lo, hi + 1).filter((row) => row.getAttribute("data-line-type") === "change-deletion").map((row) => row.textContent ?? "").join("\n");
@@ -1315,19 +2002,18 @@ var Editor = class {
1315
2002
  if (pre == null) return;
1316
2003
  if (active) {
1317
2004
  pre.setAttribute("data-deleted-text-selection", "");
1318
- for (const highlighted of pre.querySelectorAll("[data-selected-line]")) highlighted.removeAttribute("data-selected-line");
2005
+ this.#setEditorActiveLineSafe(null);
1319
2006
  this.#deletedSelectionText = "";
1320
2007
  } else {
1321
2008
  pre.removeAttribute("data-deleted-text-selection");
1322
2009
  this.#deletedSelectionText = "";
1323
2010
  }
1324
2011
  }
1325
- #setSelectedLinesSafe(range, lineNumberOnly = false) {
2012
+ #setEditorActiveLineSafe(lineNumber, lineNumberOnly = false, side = "additions") {
1326
2013
  try {
1327
- this.#fileInstance?.setSelectedLines(range, {
1328
- notify: false,
1329
- activeLineSide: "additions",
1330
- lineNumberOnly
2014
+ this.#fileInstance?.setEditorActiveLine(lineNumber, {
2015
+ lineNumberOnly,
2016
+ side
1331
2017
  });
1332
2018
  } catch {}
1333
2019
  }
@@ -1341,7 +2027,7 @@ var Editor = class {
1341
2027
  #updateSelections(selections) {
1342
2028
  this.__postponeBgTokenizeToNextFrame();
1343
2029
  this.#primaryCaretElement = void 0;
1344
- this.#setSelectedLinesSafe(null);
2030
+ this.#setEditorActiveLineSafe(null);
1345
2031
  if (selections.length === 0 && this.#matches === void 0 && this.#markerRenderer === void 0) {
1346
2032
  this.#selections = void 0;
1347
2033
  this.#overlayElements?.forEach((el) => el.remove());
@@ -1361,10 +2047,7 @@ var Editor = class {
1361
2047
  this.#selections = normalizedSelections;
1362
2048
  const hasNonEmptySelection = normalizedSelections.some((selection) => !isCollapsedSelection(selection));
1363
2049
  const caretLine = getCaretPosition(primarySelection).line + 1;
1364
- this.#setSelectedLinesSafe({
1365
- start: caretLine,
1366
- end: caretLine
1367
- }, hasNonEmptySelection);
2050
+ this.#setEditorActiveLineSafe(caretLine, hasNonEmptySelection);
1368
2051
  for (const selection of normalizedSelections) {
1369
2052
  if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, "selection", selection);
1370
2053
  this.#renderCaret(renderCtx, selection, selection === primarySelection);
@@ -1374,16 +2057,50 @@ var Editor = class {
1374
2057
  }
1375
2058
  const textDocument = this.#textDocument;
1376
2059
  if (this.#matches !== void 0 && textDocument !== void 0) {
2060
+ const matches = this.#matches;
2061
+ const renderRange = this.#renderRange;
2062
+ const renderedLineRanges = renderRange !== void 0 && Number.isFinite(renderRange.totalLines) || this.#isDiff && this.#fileInstance?.options.expandUnchanged !== true ? this.#getRenderedEditableLineRanges() : void 0;
1377
2063
  const primarySelection = this.#selections?.at(-1);
1378
2064
  const primaryStartOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.start) : -1;
1379
2065
  const primaryEndOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.end) : -1;
1380
- for (const [startOffset, endOffset] of this.#matches) {
1381
- const range = {
1382
- start: textDocument.positionAt(startOffset),
1383
- end: textDocument.positionAt(endOffset)
1384
- };
1385
- const isFocused = primaryStartOffset === startOffset && primaryEndOffset === endOffset;
1386
- this.#renderSelection(renderCtx, "match", range, isFocused ? "focus" : void 0);
2066
+ let firstMatchIndex = 0;
2067
+ const rangeCount = renderedLineRanges?.length ?? 1;
2068
+ for (let rangeIndex = 0; rangeIndex < rangeCount; rangeIndex++) {
2069
+ let visibleEndOffset = Infinity;
2070
+ const lineRange = renderedLineRanges?.[rangeIndex];
2071
+ if (lineRange !== void 0) {
2072
+ const [firstLine, lastLine] = lineRange;
2073
+ const visibleStartOffset = textDocument.offsetAt({
2074
+ line: firstLine,
2075
+ character: 0
2076
+ });
2077
+ const endLine = lastLine + 1;
2078
+ visibleEndOffset = endLine < textDocument.lineCount ? textDocument.offsetAt({
2079
+ line: endLine,
2080
+ character: 0
2081
+ }) : textDocument.offsetAt({
2082
+ line: endLine - 1,
2083
+ character: textDocument.getLineLength(endLine - 1)
2084
+ });
2085
+ let low = firstMatchIndex;
2086
+ let high = matches.length;
2087
+ while (low < high) {
2088
+ const middle = low + Math.floor((high - low) / 2);
2089
+ if (matches[middle][1] <= visibleStartOffset) low = middle + 1;
2090
+ else high = middle;
2091
+ }
2092
+ firstMatchIndex = low;
2093
+ }
2094
+ for (; firstMatchIndex < matches.length; firstMatchIndex++) {
2095
+ const [startOffset, endOffset] = matches[firstMatchIndex];
2096
+ if (startOffset >= visibleEndOffset) break;
2097
+ const range = {
2098
+ start: textDocument.positionAt(startOffset),
2099
+ end: textDocument.positionAt(endOffset)
2100
+ };
2101
+ const isFocused = primaryStartOffset === startOffset && primaryEndOffset === endOffset;
2102
+ this.#renderSelection(renderCtx, "match", range, isFocused ? "focus" : void 0);
2103
+ }
1387
2104
  }
1388
2105
  }
1389
2106
  if (this.#markerRenderer !== void 0 && textDocument !== void 0) for (const marker of this.#markerRenderer.markers) this.#renderSelection(renderCtx, "marker", marker, markerSeverityDatasetKey(marker.severity));
@@ -1396,7 +2113,14 @@ var Editor = class {
1396
2113
  #renderSelection(renderCtx, type, range, extraDataset) {
1397
2114
  if (this.#textDocument === void 0) return;
1398
2115
  const { start, end } = range;
1399
- for (let line = start.line; line <= end.line; line++) {
2116
+ let firstLine = start.line;
2117
+ let endLine = end.line + 1;
2118
+ if (this.#renderRange !== void 0) {
2119
+ const { startingLine, totalLines } = this.#renderRange;
2120
+ firstLine = Math.max(firstLine, startingLine);
2121
+ endLine = Math.min(endLine, startingLine + totalLines);
2122
+ }
2123
+ for (let line = firstLine; line < endLine; line++) {
1400
2124
  if (!this.#isLineVisible(line)) continue;
1401
2125
  const isLastLine = line === end.line;
1402
2126
  const startChar = line === start.line ? start.character : 0;
@@ -1453,8 +2177,10 @@ var Editor = class {
1453
2177
  const rounded = (this.#options.roundedSelection ?? true) && type === "selection";
1454
2178
  const addRoundedCorner = (line, wrapLine, left, radius) => {
1455
2179
  const top = this.#getLineY(line) + wrapLine * lineHeight;
1456
- const cornerBg = this.#lineBackgroundColor(line);
1457
- const css = `width:${ch}px;transform:translateX(${left}px) translateY(${top}px);` + (cornerBg !== void 0 ? `--diffs-selection-corner-bg:${cornerBg};` : "");
2180
+ const lineElement = this.#getLineElement(line);
2181
+ let cornerBg = "initial";
2182
+ if (lineElement !== void 0 && (lineElement.dataset.lineType === "change-addition" || lineElement.dataset.selectedLine !== void 0)) cornerBg = getComputedStyle(lineElement).getPropertyValue("--diffs-line-bg");
2183
+ const css = `width:${ch}px;transform:translateX(${left}px) translateY(${top}px);--diffs-selection-corner-bg:${cornerBg}`;
1458
2184
  const dataset = {
1459
2185
  selectionCorner: "",
1460
2186
  [radius]: ""
@@ -1573,13 +2299,14 @@ var Editor = class {
1573
2299
  return;
1574
2300
  }
1575
2301
  if (this.#selectionAction === void 0) {
2302
+ if (!this.#canMountSelectionAction) return;
1576
2303
  const getActiveSelection = () => this.#selections?.at(-1) ?? primarySelection;
1577
2304
  const selectionActionElement = renderSelectionAction({
1578
2305
  textDocument,
1579
2306
  get selection() {
1580
2307
  return getActiveSelection();
1581
2308
  },
1582
- applyEdits: (edits) => this.applyEdits(edits, true),
2309
+ applyEdits: (edits) => this.applyEdits(edits),
1583
2310
  getSelectionText: () => this.#textDocument?.getText(getActiveSelection()) ?? "",
1584
2311
  replaceSelectionText: (text) => {
1585
2312
  this.#replaceSelectionText(text, [getActiveSelection()]);
@@ -1590,7 +2317,7 @@ var Editor = class {
1590
2317
  }
1591
2318
  });
1592
2319
  this.#selectionAction = new SelectionActionWidget(selectionActionElement, overlayElement, () => this.#updateSelectionActionPopover());
1593
- this.#getPopoverManager().resetPlacement();
2320
+ this.#getPopoverManager().resetPlacement(SELECTION_ACTION_POPOVER_PLACEMENT_KEY);
1594
2321
  }
1595
2322
  const lineHeight = this.#metrics.lineHeight;
1596
2323
  const isBackward = primarySelection.direction === -1;
@@ -1624,17 +2351,19 @@ var Editor = class {
1624
2351
  const atDocumentEdge = isBackward ? head.line < 3 : head.line >= lineCount - 3;
1625
2352
  const canUseFallback = this.#isLineVisible(fallback.anchor.line);
1626
2353
  const popoverManager = this.#getPopoverManager();
2354
+ const viewport = popoverManager.getPlacementBounds();
1627
2355
  const useFallback = canUseFallback && popoverManager.choosePlacement({
1628
2356
  preferred: preferredGeometry,
1629
2357
  fallback: fallbackGeometry,
1630
- viewport: popoverManager.getPlacementBounds(),
2358
+ viewport,
1631
2359
  popoverHeight,
1632
- atDocumentEdge
2360
+ atDocumentEdge,
2361
+ placementKey: SELECTION_ACTION_POPOVER_PLACEMENT_KEY
1633
2362
  }) === "fallback";
1634
- if (!canUseFallback) popoverManager.setPlacement("preferred");
2363
+ if (!canUseFallback) popoverManager.setPlacement("preferred", SELECTION_ACTION_POPOVER_PLACEMENT_KEY);
1635
2364
  const { placeAbove } = useFallback ? fallback : preferred;
1636
2365
  const { left, anchorTop } = useFallback ? fallbackGeometry : preferredGeometry;
1637
- this.#selectionAction.reposition(left, anchorTop, this.#getGutterWidth(), placeAbove);
2366
+ this.#selectionAction.reposition(left, anchorTop, this.#getGutterWidth(), placeAbove, viewport);
1638
2367
  }
1639
2368
  #openSearchPanel(mode) {
1640
2369
  if (this.#searchPanel !== void 0) {
@@ -1666,6 +2395,7 @@ var Editor = class {
1666
2395
  const scrollToMatch = ([startOffset, endOffset], retainFocus) => {
1667
2396
  const nextSelection = createSelectionFromAnchorAndFocusOffsets(textDocument, startOffset, endOffset);
1668
2397
  this.#updateSelections([nextSelection]);
2398
+ this.#revealLineIfCollapsed(getCaretPosition(nextSelection).line);
1669
2399
  this.#scrollToPrimaryCaret(true);
1670
2400
  this.#retainSearchPanelFocus = retainFocus;
1671
2401
  };
@@ -1732,6 +2462,18 @@ var Editor = class {
1732
2462
  if (textDocument === void 0 || selections === void 0) return "";
1733
2463
  return getSelectionText(textDocument, selections);
1734
2464
  }
2465
+ #getSelectionClipboardTexts() {
2466
+ const textDocument = this.#textDocument;
2467
+ const selections = this.#selections;
2468
+ if (textDocument === void 0 || selections === void 0) return [];
2469
+ return getSelectionClipboardTexts(textDocument, selections);
2470
+ }
2471
+ /** Writes both the portable text and the selection pairing metadata. */
2472
+ #writeSelectionClipboardData(clipboardData, text, selectionTexts) {
2473
+ if (clipboardData === null) return;
2474
+ clipboardData.setData("text", text);
2475
+ if (selectionTexts.length > 1) clipboardData.setData(MULTI_SELECTION_CLIPBOARD_TYPE, JSON.stringify(selectionTexts));
2476
+ }
1735
2477
  #cutSelectionText() {
1736
2478
  const textDocument = this.#textDocument;
1737
2479
  const selections = this.#selections;
@@ -1762,15 +2504,15 @@ var Editor = class {
1762
2504
  textDocument.setLastUndoSelectionsAfter(nextSelections);
1763
2505
  this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
1764
2506
  }
1765
- #replaceSelectionText(text, selections = this.#selections, undoBoundary = false) {
2507
+ #replaceSelectionText(text, selections = this.#selections, undoBoundary = false, textOrder = "selection") {
1766
2508
  if (selections === void 0) return;
1767
2509
  const textDocument = this.#textDocument;
1768
2510
  const primarySelection = selections.at(-1);
1769
2511
  if (textDocument === void 0 || primarySelection === void 0) return;
1770
- const { nextSelections, change } = Array.isArray(text) && text.length === selections.length ? applyTextReplaceToSelections(textDocument, selections, text, this.#lineAnnotations, undoBoundary) : applyTextChangeToSelections(textDocument, selections, {
2512
+ const { nextSelections, change } = Array.isArray(text) && text.length === selections.length ? applyTextReplaceToSelections(textDocument, selections, text, this.#lineAnnotations, undoBoundary, textOrder) : applyTextChangeToSelections(textDocument, selections, {
1771
2513
  start: textDocument.offsetAt(primarySelection.start),
1772
2514
  end: textDocument.offsetAt(primarySelection.end),
1773
- text: Array.isArray(text) ? text.join("\n") : text
2515
+ text: Array.isArray(text) ? text.join(textDocument.eol) : text
1774
2516
  }, this.#lineAnnotations, void 0, undoBoundary);
1775
2517
  if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
1776
2518
  }
@@ -1817,26 +2559,16 @@ var Editor = class {
1817
2559
  const { nextSelections, change } = applyTransposeToSelections(textDocument, selections, this.#lineAnnotations);
1818
2560
  if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
1819
2561
  }
1820
- #getFileRef() {
1821
- const fileInfo = this.#fileInfo;
1822
- const textDocument = this.#textDocument;
1823
- if (fileInfo === void 0 || textDocument === void 0) return;
1824
- const file = { ...fileInfo };
1825
- Object.defineProperty(file, "contents", {
1826
- enumerable: true,
1827
- get: () => textDocument.getText()
1828
- });
1829
- return file;
1830
- }
1831
2562
  #applyChange(change, newSelections, newLineAnnotations, options) {
1832
- const fileRef = this.#getFileRef();
2563
+ const fileRef = this.getFile();
1833
2564
  const onChange = this.#options.onChange;
1834
2565
  if (fileRef !== void 0 && onChange !== void 0) onChange(fileRef, newLineAnnotations ?? this.#lineAnnotations);
1835
2566
  if (change.lineDelta !== 0 || this.#isWrap) {
1836
2567
  for (const line of this.#lineYCache.keys()) if (line >= change.startLine) this.#lineYCache.delete(line);
1837
2568
  }
1838
- if (this.#isWrap) {
1839
- for (const line of this.#wrapLineOffsetsCache.keys()) if (line >= change.startLine) this.#wrapLineOffsetsCache.delete(line);
2569
+ if (this.#wrapLineOffsetsCache.size > 0) {
2570
+ if (change.lineDelta === 0 && (change.changedLineRanges.length === 1 || change.changedLineChanges?.every(([, , editLineDelta]) => editLineDelta === 0) === true)) for (const [rangeStart, rangeEnd] of change.changedLineRanges) for (let line = rangeStart; line <= rangeEnd; line++) this.#wrapLineOffsetsCache.delete(line);
2571
+ else for (const line of this.#wrapLineOffsetsCache.keys()) if (line >= change.startLine) this.#wrapLineOffsetsCache.delete(line);
1840
2572
  }
1841
2573
  this.#lastAccessedCharX = void 0;
1842
2574
  let renderRange = this.#renderRange;
@@ -1847,7 +2579,7 @@ var Editor = class {
1847
2579
  if (primarySelection.end.line >= renderRangeEndLine) {
1848
2580
  const widenedTotalLines = primarySelection.end.line - renderRange.startingLine + 1;
1849
2581
  const maxWidenLines = this.#viewportWindowLines === void 0 || this.#viewportWindowLines === Infinity ? Infinity : this.#viewportWindowLines * MAX_EDIT_WIDEN_WINDOW_MULTIPLE;
1850
- if (change.startLine <= renderRangeEndLine && widenedTotalLines <= maxWidenLines) {
2582
+ if (change.startLine <= renderRangeEndLine && widenedTotalLines <= maxWidenLines && this.#isLineRenderable(primarySelection.end.line)) {
1851
2583
  if (primarySelection.end.line > renderRangeEndLine) shouldUpdateBuffer = true;
1852
2584
  renderRange = {
1853
2585
  ...renderRange,
@@ -1862,6 +2594,8 @@ var Editor = class {
1862
2594
  if (newSelections !== void 0) {
1863
2595
  this.#updateSelections(newSelections);
1864
2596
  if (options?.skipFocus !== true) {
2597
+ const revealTarget = newSelections.at(-1);
2598
+ if (revealTarget !== void 0) this.#revealLineIfCollapsed(getCaretPosition(revealTarget).line);
1865
2599
  if (this.#primaryCaretElement !== void 0) requestAnimationFrame(() => {
1866
2600
  this.#primaryCaretElement?.scrollIntoView({
1867
2601
  block: "nearest",
@@ -1885,39 +2619,31 @@ var Editor = class {
1885
2619
  }
1886
2620
  }
1887
2621
  }
1888
- #lineBackgroundColor(line) {
1889
- const lineElement = this.#getLineElement(line);
1890
- if (lineElement === void 0) return;
1891
- if (navigator.userAgent.includes("jsdom")) return;
1892
- const backgroundColor = getComputedStyle(lineElement, "::after").backgroundColor;
1893
- return backgroundColor === "" || backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)" ? void 0 : backgroundColor;
1894
- }
1895
- #getRenderedEditableLineRange() {
2622
+ #getRenderedEditableLineRanges() {
1896
2623
  const contentElement = this.#contentElement;
1897
- if (contentElement === void 0) return;
1898
- let first;
1899
- let last;
2624
+ if (contentElement === void 0) return [];
2625
+ const ranges = [];
1900
2626
  for (const child of contentElement.children) {
1901
2627
  const el = child;
1902
2628
  const lineType = el.dataset.lineType;
1903
2629
  const lineNumber = getLineNumberAttr(el);
1904
2630
  if (lineNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) continue;
1905
2631
  const line = lineNumber - 1;
1906
- if (first === void 0 || line < first) first = line;
1907
- if (last === void 0 || line > last) last = line;
2632
+ const current = ranges.at(-1);
2633
+ if (current !== void 0 && line <= current[1] + 1) current[1] = Math.max(current[1], line);
2634
+ else ranges.push([line, line]);
1908
2635
  }
1909
- return first === void 0 || last === void 0 ? void 0 : {
1910
- first,
1911
- last
1912
- };
2636
+ return ranges;
1913
2637
  }
1914
2638
  #getLineElement(line) {
1915
2639
  let lineElement = this.#lineElementsCache.get(line);
1916
2640
  if (lineElement !== void 0) return lineElement ?? void 0;
2641
+ const renderRange = this.#renderRange;
2642
+ if (renderRange !== void 0 && (line < renderRange.startingLine || line >= renderRange.startingLine + renderRange.totalLines)) return;
1917
2643
  const contentElement = this.#contentElement;
1918
2644
  if (contentElement === void 0) return;
1919
- if (this.#renderRange !== void 0) {
1920
- const { startingLine } = this.#renderRange;
2645
+ if (renderRange !== void 0) {
2646
+ const { startingLine } = renderRange;
1921
2647
  const { children } = contentElement;
1922
2648
  for (let i = line - startingLine; i <= children.length; i++) {
1923
2649
  const child = children[i];
@@ -2008,6 +2734,7 @@ var Editor = class {
2008
2734
  #wrapLineText(line) {
2009
2735
  const cachedOffsets = this.#wrapLineOffsetsCache.get(line);
2010
2736
  if (cachedOffsets !== void 0) return cachedOffsets;
2737
+ if (this.#wrapLineOffsetsCache.size >= MAX_WRAP_OFFSETS_CACHE_LINES) this.#wrapLineOffsetsCache.clear();
2011
2738
  const lineText = this.#textDocument?.getLineText(line);
2012
2739
  if (lineText === void 0 || lineText.length === 0) {
2013
2740
  const offsets = new Uint32Array([0]);
@@ -2037,21 +2764,30 @@ var Editor = class {
2037
2764
  try {
2038
2765
  const unicodeOffsets = getUnicodeMeasurementOffsets(lineText);
2039
2766
  const wrapLineStartLeft = div.getBoundingClientRect().left + this.#metrics.ch;
2040
- let previousOffset = 0;
2041
- let lastTop = Number.NEGATIVE_INFINITY;
2042
- for (let i = 0, offsetIndex = 0; i < lineText.length;) {
2043
- const nextOffset = unicodeOffsets === void 0 ? i + 1 : unicodeOffsets[offsetIndex + 1];
2044
- range.setStart(textNode, i);
2045
- range.setEnd(textNode, nextOffset);
2046
- const { left, top } = range.getBoundingClientRect();
2047
- if (top > lastTop) {
2048
- const startsPastLineStart = isSafari() && starts.length > 0 && left - wrapLineStartLeft > this.#metrics.ch / 2;
2049
- starts.push(startsPastLineStart ? previousOffset : i);
2050
- lastTop = top;
2767
+ const graphemeCount = unicodeOffsets === void 0 ? lineText.length : unicodeOffsets.length - 1;
2768
+ const graphemeStart = (index) => unicodeOffsets === void 0 ? index : unicodeOffsets[index];
2769
+ const measureGrapheme = (index) => {
2770
+ range.setStart(textNode, graphemeStart(index));
2771
+ range.setEnd(textNode, graphemeStart(index + 1));
2772
+ return range.getBoundingClientRect();
2773
+ };
2774
+ starts.push(0);
2775
+ let lastTop = measureGrapheme(0).top;
2776
+ let searchStart = 1;
2777
+ while (searchStart < graphemeCount) {
2778
+ let low = searchStart;
2779
+ let high = graphemeCount;
2780
+ while (low < high) {
2781
+ const mid = low + high >> 1;
2782
+ if (measureGrapheme(mid).top > lastTop) high = mid;
2783
+ else low = mid + 1;
2051
2784
  }
2052
- previousOffset = i;
2053
- i = nextOffset;
2054
- offsetIndex++;
2785
+ if (low >= graphemeCount) break;
2786
+ const { left, top } = measureGrapheme(low);
2787
+ const startsPastLineStart = isSafari() && left - wrapLineStartLeft > this.#metrics.ch / 2;
2788
+ starts.push(graphemeStart(startsPastLineStart ? low - 1 : low));
2789
+ lastTop = top;
2790
+ searchStart = low + 1;
2055
2791
  }
2056
2792
  const offsets = new Uint32Array(starts.length + 1);
2057
2793
  for (let i = 0; i < starts.length; i++) offsets[i] = starts[i];