@pierre/diffs 1.3.6 → 1.4.0

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 (254) hide show
  1. package/dist/components/CodeView.d.ts +113 -68
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +153 -58
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +75 -15
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +223 -62
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +97 -21
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +400 -100
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/UnresolvedFile.d.ts +2 -2
  14. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  15. package/dist/components/UnresolvedFile.js +2 -2
  16. package/dist/components/UnresolvedFile.js.map +1 -1
  17. package/dist/components/VirtualizedFile.d.ts +16 -7
  18. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  19. package/dist/components/VirtualizedFile.js +140 -51
  20. package/dist/components/VirtualizedFile.js.map +1 -1
  21. package/dist/components/VirtualizedFileDiff.d.ts +14 -8
  22. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  23. package/dist/components/VirtualizedFileDiff.js +183 -93
  24. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  25. package/dist/components/VirtulizerDevelopment.d.ts +1 -1
  26. package/dist/components/VirtulizerDevelopment.d.ts.map +1 -1
  27. package/dist/edit/index.d.ts +7 -4
  28. package/dist/edit/index.js +5 -1
  29. package/dist/editor/EditStateManager.d.ts +56 -0
  30. package/dist/editor/EditStateManager.d.ts.map +1 -0
  31. package/dist/editor/EditStateManager.js +135 -0
  32. package/dist/editor/EditStateManager.js.map +1 -0
  33. package/dist/editor/cloneRetainedDiffSessionSnapshot.d.ts +7 -0
  34. package/dist/editor/cloneRetainedDiffSessionSnapshot.d.ts.map +1 -0
  35. package/dist/editor/cloneRetainedDiffSessionSnapshot.js +16 -0
  36. package/dist/editor/cloneRetainedDiffSessionSnapshot.js.map +1 -0
  37. package/dist/editor/editPrediction.d.ts +76 -0
  38. package/dist/editor/editPrediction.d.ts.map +1 -0
  39. package/dist/editor/editPrediction.js +365 -0
  40. package/dist/editor/editPrediction.js.map +1 -0
  41. package/dist/editor/editStack.d.ts +19 -40
  42. package/dist/editor/editStack.d.ts.map +1 -1
  43. package/dist/editor/editStack.js +67 -4
  44. package/dist/editor/editStack.js.map +1 -1
  45. package/dist/editor/editor.d.ts +118 -29
  46. package/dist/editor/editor.d.ts.map +1 -1
  47. package/dist/editor/editor.js +1103 -383
  48. package/dist/editor/editor.js.map +1 -1
  49. package/dist/editor/editor2.js +1 -1
  50. package/dist/editor/languages.d.ts +3 -3
  51. package/dist/editor/languages.d.ts.map +1 -1
  52. package/dist/editor/languages.js.map +1 -1
  53. package/dist/editor/lineAnnotations.d.ts +3 -3
  54. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  55. package/dist/editor/lineAnnotations.js +16 -7
  56. package/dist/editor/lineAnnotations.js.map +1 -1
  57. package/dist/editor/marker.d.ts +2 -2
  58. package/dist/editor/marker.d.ts.map +1 -1
  59. package/dist/editor/marker.js.map +1 -1
  60. package/dist/editor/matchBrackets.d.ts +2 -2
  61. package/dist/editor/matchBrackets.d.ts.map +1 -1
  62. package/dist/editor/matchBrackets.js.map +1 -1
  63. package/dist/editor/pieceTable.d.ts +1 -1
  64. package/dist/editor/pieceTable.js.map +1 -1
  65. package/dist/editor/searchPanel.d.ts +2 -2
  66. package/dist/editor/searchPanel.js +1 -1
  67. package/dist/editor/searchPanel.js.map +1 -1
  68. package/dist/editor/selection.d.ts +32 -23
  69. package/dist/editor/selection.d.ts.map +1 -1
  70. package/dist/editor/selection.js +3 -3
  71. package/dist/editor/selection.js.map +1 -1
  72. package/dist/editor/selectionAction.d.ts +3 -3
  73. package/dist/editor/selectionAction.d.ts.map +1 -1
  74. package/dist/editor/selectionAction.js.map +1 -1
  75. package/dist/editor/textDocument.d.ts +11 -8
  76. package/dist/editor/textDocument.d.ts.map +1 -1
  77. package/dist/editor/textDocument.js +28 -5
  78. package/dist/editor/textDocument.js.map +1 -1
  79. package/dist/editor/textDocumentChangeTransaction.d.ts +15 -0
  80. package/dist/editor/textDocumentChangeTransaction.d.ts.map +1 -0
  81. package/dist/editor/textDocumentChangeTransaction.js +12 -0
  82. package/dist/editor/textDocumentChangeTransaction.js.map +1 -0
  83. package/dist/editor/textMeasure.d.ts +5 -0
  84. package/dist/editor/textMeasure.d.ts.map +1 -1
  85. package/dist/editor/textMeasure.js +10 -0
  86. package/dist/editor/textMeasure.js.map +1 -1
  87. package/dist/editor/tokenizer.d.ts +1 -1
  88. package/dist/editor/tokenizer.js.map +1 -1
  89. package/dist/editor/types.d.ts +267 -0
  90. package/dist/editor/types.d.ts.map +1 -0
  91. package/dist/editor/types.js +0 -0
  92. package/dist/editor/utils.d.ts +2 -1
  93. package/dist/editor/utils.d.ts.map +1 -1
  94. package/dist/editor/utils.js +4 -1
  95. package/dist/editor/utils.js.map +1 -1
  96. package/dist/index.d.ts +8 -6
  97. package/dist/index.js +9 -8
  98. package/dist/managers/InteractionManager.d.ts.map +1 -1
  99. package/dist/managers/InteractionManager.js +7 -0
  100. package/dist/managers/InteractionManager.js.map +1 -1
  101. package/dist/react/CodeView.d.ts +36 -25
  102. package/dist/react/CodeView.d.ts.map +1 -1
  103. package/dist/react/CodeView.js +15 -14
  104. package/dist/react/CodeView.js.map +1 -1
  105. package/dist/react/EditContext.d.ts +8 -11
  106. package/dist/react/EditContext.d.ts.map +1 -1
  107. package/dist/react/EditContext.js +2 -9
  108. package/dist/react/EditContext.js.map +1 -1
  109. package/dist/react/File.d.ts +10 -6
  110. package/dist/react/File.d.ts.map +1 -1
  111. package/dist/react/File.js +8 -4
  112. package/dist/react/File.js.map +1 -1
  113. package/dist/react/FileDiff.d.ts +10 -5
  114. package/dist/react/FileDiff.d.ts.map +1 -1
  115. package/dist/react/FileDiff.js +8 -4
  116. package/dist/react/FileDiff.js.map +1 -1
  117. package/dist/react/MultiFileDiff.d.ts +8 -5
  118. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  119. package/dist/react/MultiFileDiff.js +8 -12
  120. package/dist/react/MultiFileDiff.js.map +1 -1
  121. package/dist/react/PatchDiff.d.ts +7 -4
  122. package/dist/react/PatchDiff.d.ts.map +1 -1
  123. package/dist/react/PatchDiff.js +8 -4
  124. package/dist/react/PatchDiff.js.map +1 -1
  125. package/dist/react/UnresolvedFile.d.ts +3 -3
  126. package/dist/react/UnresolvedFile.d.ts.map +1 -1
  127. package/dist/react/UnresolvedFile.js.map +1 -1
  128. package/dist/react/index.d.ts +8 -5
  129. package/dist/react/types.d.ts +46 -9
  130. package/dist/react/types.d.ts.map +1 -1
  131. package/dist/react/utils/renderDiffChildren.d.ts +14 -12
  132. package/dist/react/utils/renderDiffChildren.d.ts.map +1 -1
  133. package/dist/react/utils/renderDiffChildren.js +2 -2
  134. package/dist/react/utils/renderDiffChildren.js.map +1 -1
  135. package/dist/react/utils/renderFileChildren.d.ts +14 -12
  136. package/dist/react/utils/renderFileChildren.d.ts.map +1 -1
  137. package/dist/react/utils/renderFileChildren.js +2 -2
  138. package/dist/react/utils/renderFileChildren.js.map +1 -1
  139. package/dist/react/utils/useFileDiffInstance.d.ts +22 -10
  140. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  141. package/dist/react/utils/useFileDiffInstance.js +112 -22
  142. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  143. package/dist/react/utils/useFileInstance.d.ts +15 -9
  144. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  145. package/dist/react/utils/useFileInstance.js +84 -25
  146. package/dist/react/utils/useFileInstance.js.map +1 -1
  147. package/dist/renderers/DiffHunksRenderer.d.ts +30 -15
  148. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  149. package/dist/renderers/DiffHunksRenderer.js +172 -108
  150. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  151. package/dist/renderers/FileRenderer.d.ts +27 -11
  152. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  153. package/dist/renderers/FileRenderer.js +138 -64
  154. package/dist/renderers/FileRenderer.js.map +1 -1
  155. package/dist/renderers/UnresolvedFileHunksRenderer.d.ts +2 -2
  156. package/dist/renderers/UnresolvedFileHunksRenderer.d.ts.map +1 -1
  157. package/dist/renderers/UnresolvedFileHunksRenderer.js +2 -2
  158. package/dist/renderers/UnresolvedFileHunksRenderer.js.map +1 -1
  159. package/dist/shiki-stream/tokenizer.d.ts.map +1 -1
  160. package/dist/shiki-stream/tokenizer.js +2 -1
  161. package/dist/shiki-stream/tokenizer.js.map +1 -1
  162. package/dist/ssr/index.d.ts +2 -2
  163. package/dist/ssr/preloadDiffs.d.ts +21 -21
  164. package/dist/ssr/preloadDiffs.d.ts.map +1 -1
  165. package/dist/ssr/preloadDiffs.js +1 -1
  166. package/dist/ssr/preloadDiffs.js.map +1 -1
  167. package/dist/ssr/preloadFile.d.ts +6 -6
  168. package/dist/ssr/preloadFile.d.ts.map +1 -1
  169. package/dist/ssr/preloadFile.js.map +1 -1
  170. package/dist/ssr/preloadPatchFile.d.ts +4 -4
  171. package/dist/ssr/preloadPatchFile.d.ts.map +1 -1
  172. package/dist/ssr/preloadPatchFile.js.map +1 -1
  173. package/dist/style.js +1 -1
  174. package/dist/types.d.ts +13 -263
  175. package/dist/types.d.ts.map +1 -1
  176. package/dist/utils/appendItems.d.ts +5 -0
  177. package/dist/utils/appendItems.d.ts.map +1 -0
  178. package/dist/utils/appendItems.js +8 -0
  179. package/dist/utils/appendItems.js.map +1 -0
  180. package/dist/utils/areDiffTargetsEqual.js +1 -1
  181. package/dist/utils/areDiffTargetsEqual.js.map +1 -1
  182. package/dist/utils/areFileTargetsEqual.d.ts +7 -0
  183. package/dist/utils/areFileTargetsEqual.d.ts.map +1 -0
  184. package/dist/utils/areFileTargetsEqual.js +8 -0
  185. package/dist/utils/areFileTargetsEqual.js.map +1 -0
  186. package/dist/utils/areManagedSnapshotsEqual.d.ts +1 -1
  187. package/dist/utils/areManagedSnapshotsEqual.d.ts.map +1 -1
  188. package/dist/utils/areManagedSnapshotsEqual.js.map +1 -1
  189. package/dist/utils/areOptionsEqual.d.ts +3 -3
  190. package/dist/utils/areOptionsEqual.d.ts.map +1 -1
  191. package/dist/utils/areOptionsEqual.js.map +1 -1
  192. package/dist/utils/cloneFileDiffMetadata.d.ts +3 -2
  193. package/dist/utils/cloneFileDiffMetadata.d.ts.map +1 -1
  194. package/dist/utils/cloneFileDiffMetadata.js +8 -5
  195. package/dist/utils/cloneFileDiffMetadata.js.map +1 -1
  196. package/dist/utils/composeCacheKey.d.ts +6 -0
  197. package/dist/utils/composeCacheKey.d.ts.map +1 -0
  198. package/dist/utils/composeCacheKey.js +10 -0
  199. package/dist/utils/composeCacheKey.js.map +1 -0
  200. package/dist/utils/editSessionAnnotations.d.ts +69 -0
  201. package/dist/utils/editSessionAnnotations.d.ts.map +1 -0
  202. package/dist/utils/editSessionAnnotations.js +64 -0
  203. package/dist/utils/editSessionAnnotations.js.map +1 -0
  204. package/dist/utils/editSessionHunks.d.ts +5 -3
  205. package/dist/utils/editSessionHunks.d.ts.map +1 -1
  206. package/dist/utils/editSessionHunks.js +7 -5
  207. package/dist/utils/editSessionHunks.js.map +1 -1
  208. package/dist/utils/getDiffHunksRendererOptions.d.ts +1 -1
  209. package/dist/utils/getDiffHunksRendererOptions.d.ts.map +1 -1
  210. package/dist/utils/getDiffHunksRendererOptions.js.map +1 -1
  211. package/dist/utils/getFileRendererOptions.d.ts +1 -1
  212. package/dist/utils/getFileRendererOptions.d.ts.map +1 -1
  213. package/dist/utils/getFileRendererOptions.js.map +1 -1
  214. package/dist/utils/hydratePartialDiff.d.ts.map +1 -1
  215. package/dist/utils/hydratePartialDiff.js +2 -1
  216. package/dist/utils/hydratePartialDiff.js.map +1 -1
  217. package/dist/utils/lineAnnotationIdentity.d.ts +16 -0
  218. package/dist/utils/lineAnnotationIdentity.d.ts.map +1 -0
  219. package/dist/utils/lineAnnotationIdentity.js +17 -0
  220. package/dist/utils/lineAnnotationIdentity.js.map +1 -0
  221. package/dist/utils/parseDiffFromFile.d.ts +2 -2
  222. package/dist/utils/parseDiffFromFile.d.ts.map +1 -1
  223. package/dist/utils/parseDiffFromFile.js +6 -6
  224. package/dist/utils/parseDiffFromFile.js.map +1 -1
  225. package/dist/utils/parsePatchFiles.d.ts +6 -3
  226. package/dist/utils/parsePatchFiles.d.ts.map +1 -1
  227. package/dist/utils/parsePatchFiles.js +34 -9
  228. package/dist/utils/parsePatchFiles.js.map +1 -1
  229. package/dist/utils/renderDiffWithHighlighter.d.ts.map +1 -1
  230. package/dist/utils/renderDiffWithHighlighter.js +6 -5
  231. package/dist/utils/renderDiffWithHighlighter.js.map +1 -1
  232. package/dist/utils/renderFileWithHighlighter.d.ts.map +1 -1
  233. package/dist/utils/renderFileWithHighlighter.js +7 -4
  234. package/dist/utils/renderFileWithHighlighter.js.map +1 -1
  235. package/dist/utils/trimPatchContext.d.ts.map +1 -1
  236. package/dist/utils/trimPatchContext.js +3 -2
  237. package/dist/utils/trimPatchContext.js.map +1 -1
  238. package/dist/utils/updateDiffHunks.js +1 -1
  239. package/dist/utils/virtualDiffLayout.js +1 -1
  240. package/dist/worker/WorkerPoolManager.d.ts +4 -3
  241. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  242. package/dist/worker/WorkerPoolManager.js +33 -4
  243. package/dist/worker/WorkerPoolManager.js.map +1 -1
  244. package/dist/worker/types.d.ts +5 -0
  245. package/dist/worker/types.d.ts.map +1 -1
  246. package/dist/worker/worker-portable.js +14 -6
  247. package/dist/worker/worker-portable.js.map +1 -1
  248. package/dist/worker/worker.js +14 -6
  249. package/dist/worker/worker.js.map +1 -1
  250. package/package.json +9 -1
  251. package/dist/editor/stateStorage.d.ts +0 -15
  252. package/dist/editor/stateStorage.d.ts.map +0 -1
  253. package/dist/editor/stateStorage.js +0 -65
  254. package/dist/editor/stateStorage.js.map +0 -1
@@ -1,23 +1,27 @@
1
1
  import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
2
2
  import { isGutterUtilityPath } from "../utils/isGutterUtilityPath.js";
3
+ import { computeLineOffsets } from "../utils/computeFileOffsets.js";
3
4
  import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
4
5
  import { isSafari } from "../utils/platform.js";
6
+ import { EditStack } from "./editStack.js";
7
+ import { getTextDocumentChangeTransaction } from "./textDocumentChangeTransaction.js";
8
+ import { TextDocument } from "./textDocument.js";
9
+ import { EditStateManager, cloneEditorViewState, toManagedEditState } from "./EditStateManager.js";
5
10
  import { isMoveCursorShortcut, isPrimaryModifier } from "./platform.js";
6
11
  import { resolveEditorCommandFromKeyboardEvent, resolveFindAgainShortcut } from "./command.js";
12
+ import { cloneRetainedDiffSessionSnapshot } from "./cloneRetainedDiffSessionSnapshot.js";
7
13
  import editor_default from "./editor2.js";
8
- import { EditStack } from "./editStack.js";
14
+ import { buildEditPredictionRequest, matchesEditPredictionPattern, recordEditPrediction } from "./editPrediction.js";
9
15
  import { resolveBlockCommentEdits, resolveCommentConfig, resolveLineCommentEdits } from "./languages.js";
10
16
  import { addEventListener, clampDomOffset, extend, getLineNumberAttr, h, lookupScrollContainer, round } from "./utils.js";
11
17
  import { applyDocumentChangeToLineAnnotations, renderLineAnnotations } from "./lineAnnotations.js";
12
18
  import { PopoverManager } from "./popover.js";
13
- 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, snapCharacterToGraphemeBoundary } from "./selection.js";
19
+ import { applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNextMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionClipboardTexts, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapOffsetThroughEdits, remapSelectionsAfterEdits, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines, snapCharacterToGraphemeBoundary } from "./selection.js";
14
20
  import { MarkerRenderer, markerSeverityDatasetKey } from "./marker.js";
15
21
  import { findBracketMatchRanges } from "./matchBrackets.js";
16
22
  import { createSpriteElement } from "./sprite.js";
17
23
  import { SearchPanelWidget } from "./searchPanel.js";
18
24
  import { SelectionActionWidget } from "./selectionAction.js";
19
- import { cloneEditorState, createStateStorage } from "./stateStorage.js";
20
- import { TextDocument } from "./textDocument.js";
21
25
  import { Metrics, getExpandedAsciiTextColumns, getUnicodeMeasurementOffsets, snapTextOffsetToUnicodeBoundary } from "./textMeasure.js";
22
26
  import { EditorTokenizer, renderLineTokens } from "./tokenizer.js";
23
27
  //#region src/editor/editor.ts
@@ -33,29 +37,45 @@ function getShadowRootRange(shadowRoot) {
33
37
  endOffset: range.endOffset
34
38
  };
35
39
  }
36
- function requirePersistedCacheKey(file) {
37
- 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.`);
38
- return file.cacheKey;
40
+ /** Describe replacing the complete document from its previous text. */
41
+ function createFullDocumentChange(previousContents, contents) {
42
+ const lineOffsets = computeLineOffsets(previousContents);
43
+ const lastLineOffset = lineOffsets[lineOffsets.length - 1] ?? 0;
44
+ return {
45
+ start: 0,
46
+ end: previousContents.length,
47
+ text: contents,
48
+ range: {
49
+ start: {
50
+ line: 0,
51
+ character: 0
52
+ },
53
+ end: {
54
+ line: lineOffsets.length - 1,
55
+ character: previousContents.length - lastLineOffset
56
+ }
57
+ }
58
+ };
39
59
  }
40
- function isPromise(value) {
41
- return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
60
+ function isVirtualizedEditorComponent(instance) {
61
+ return instance != null && "renderType" in instance && instance.renderType === "virtualized";
42
62
  }
43
63
  const MAX_EDIT_WIDEN_WINDOW_MULTIPLE = 2;
44
64
  const MAX_WRAP_OFFSETS_CACHE_LINES = 1e4;
65
+ const EDIT_PREDICTION_DEBOUNCE_MS = 300;
66
+ const MAX_EDIT_PREDICTION_RESPONSE_EDITS = 256;
67
+ const MAX_EDIT_PREDICTION_RESPONSE_BYTES = 128 * 1024;
68
+ const editPredictionTextEncoder = new TextEncoder();
45
69
  const SELECTION_ACTION_POPOVER_PLACEMENT_KEY = "selection-action";
46
70
  const MULTI_SELECTION_CLIPBOARD_TYPE = "application/vnd.pierre.diffs-selections+json";
47
71
  var Editor = class {
72
+ type;
48
73
  #options;
74
+ #initialState;
75
+ #editSession;
49
76
  #metrics = new Metrics();
50
77
  #tokenizer;
51
78
  #popoverManager;
52
- #textDocumentCache = /* @__PURE__ */ new Map();
53
- #stateStorage;
54
- #stateStorageOption;
55
- #pendingStateWrites = /* @__PURE__ */ new Map();
56
- #pendingStateRestore;
57
- #stateRestoreGeneration = 0;
58
- #restoreStateOnNextSync = false;
59
79
  #editorEventDisposes;
60
80
  #globalEventDisposes;
61
81
  #selectEventDisposes;
@@ -79,17 +99,25 @@ var Editor = class {
79
99
  #fileContainer;
80
100
  #gutterElement;
81
101
  #contentElement;
102
+ #deletionsColumn;
82
103
  #overlayElement;
83
104
  #overlayElements;
105
+ #caretElements;
106
+ #caretHighlightElements;
84
107
  #primaryCaretElement;
85
108
  #resizeObserver;
86
109
  #fileInstance;
87
- #fileInfo;
110
+ #applyDocumentChange;
111
+ #publishChange;
112
+ #isRendering = false;
113
+ #restoreEditorStateOnSync = false;
88
114
  #lineAnnotations;
89
- #textDocument;
115
+ #editStateKey;
116
+ #ownsVerticalViewport;
90
117
  #renderRange;
91
118
  #viewportWindowLines;
92
119
  #markerRenderer;
120
+ #carets;
93
121
  #searchPanel;
94
122
  #selectionAction;
95
123
  #canMountSelectionAction = true;
@@ -115,53 +143,166 @@ var Editor = class {
115
143
  #retainSearchPanelFocus = false;
116
144
  #fontRemeasureScheduled = false;
117
145
  #themeSelectionRefreshFrame;
146
+ #editPredictionTimer;
147
+ #editPredictionAbortController;
148
+ #editPredictionGeneration = 0;
149
+ #editPredictionRevealed = false;
150
+ #retryEditPredictionOnRender = false;
151
+ #editPrediction;
152
+ #editPredictionHistory = [];
153
+ #editPredictionSpacers = /* @__PURE__ */ new Map();
154
+ #ghostTextRows = /* @__PURE__ */ new Map();
118
155
  #onDeferTokenize = (lines, themeType) => {
156
+ if (!this.#isRendering) return;
119
157
  this.#fileInstance?.updateRenderCache(lines, themeType);
120
158
  if (this.#renderRange !== void 0 && this.#renderRange.totalLines !== Infinity) {
159
+ const predictionLines = this.#editPrediction === void 0 ? void 0 : new Set(this.#editPrediction.response.edits.map((edit) => edit.range.start.line));
160
+ let refreshPrediction = false;
121
161
  const { startingLine, totalLines } = this.#renderRange;
122
- const endLine = Math.min(startingLine + totalLines, this.#textDocument?.lineCount ?? 0);
162
+ const endLine = Math.min(startingLine + totalLines, (this.#editSession ?? this.#initialState)?.document?.lineCount ?? 0);
123
163
  for (const [line, tokens] of lines) if (line >= startingLine && line < endLine) {
124
164
  const lineElement = this.#getLineElement(line);
125
- if (lineElement !== void 0) lineElement.replaceChildren(...renderLineTokens(tokens));
165
+ if (lineElement !== void 0) {
166
+ lineElement.replaceChildren(...renderLineTokens(tokens));
167
+ refreshPrediction ||= predictionLines?.has(line) === true;
168
+ }
126
169
  }
170
+ if (refreshPrediction && this.#selections !== void 0) this.#updateSelections(this.#selections);
127
171
  }
128
172
  };
129
- constructor(options = {}) {
173
+ /**
174
+ * @param type The component surface this editor can attach to.
175
+ * @param options Configure editor behavior and lifecycle callbacks.
176
+ * @param editStateKey Retain this editable draft and its undo/redo history
177
+ * in memory so a later editor using the same type and key can resume them.
178
+ */
179
+ constructor(type, options = {}, editStateKey) {
180
+ this.type = type;
130
181
  this.#options = options;
182
+ this.#ownsVerticalViewport = options.ownsVerticalViewport === true;
183
+ this.#initialState = options.initialState;
184
+ this.#editStateKey = editStateKey;
185
+ }
186
+ get #getTypedEditor() {
187
+ return this.type === "file" ? this : this;
131
188
  }
132
189
  setOptions(options) {
133
- const previousStorageOption = this.#options.persistStateStorage ?? "inMemory";
134
- const nextOptions = {
190
+ const previousRenderCaret = this.#options.renderCaret;
191
+ const previousEditPrediction = this.#options.editPrediction;
192
+ this.#options = {
135
193
  ...this.#options,
136
194
  ...options
137
195
  };
138
- if (nextOptions.persistState === true && this.#fileInstance?.type === "file") {
139
- const file = this.#fileInstance.__getCurrentFile?.() ?? this.#fileInfo;
140
- if (file !== void 0) requirePersistedCacheKey(file);
141
- }
142
- this.#options = nextOptions;
143
- if (this.#options.persistState !== true) {
144
- this.#textDocumentCache.clear();
145
- this.#stateRestoreGeneration++;
146
- this.#restoreStateOnNextSync = false;
196
+ if (previousRenderCaret !== this.#options.renderCaret) {
197
+ this.#caretElements?.forEach((element) => element.remove());
198
+ this.#caretElements = void 0;
199
+ this.#renderCarets();
147
200
  }
148
- if ((this.#options.persistStateStorage ?? "inMemory") !== previousStorageOption) {
149
- this.#stateRestoreGeneration++;
150
- this.#stateStorage = void 0;
151
- this.#stateStorageOption = void 0;
152
- this.#pendingStateWrites.clear();
201
+ if (previousEditPrediction !== this.#options.editPrediction) {
202
+ this.#cancelEditPrediction(true);
203
+ this.#editPredictionHistory = [];
204
+ this.#scheduleEditPrediction();
153
205
  }
154
206
  }
207
+ __emitEditComplete(event) {
208
+ this.#options.onComplete?.(event);
209
+ }
210
+ /** @internal */
211
+ __getGhostTextRows() {
212
+ return this.#ghostTextRows;
213
+ }
214
+ setCarets(carets) {
215
+ const textDocument = this.#editSession?.document;
216
+ if (textDocument === void 0) return;
217
+ this.#caretElements?.forEach((element) => element.remove());
218
+ this.#caretElements = void 0;
219
+ this.#caretHighlightElements?.forEach((element) => element.remove());
220
+ this.#caretHighlightElements = void 0;
221
+ this.#carets = carets.length === 0 ? void 0 : carets.map((caret) => {
222
+ const anchor = textDocument.normalizePosition(caret.anchor);
223
+ const focus = textDocument.normalizePosition(caret.focus);
224
+ return {
225
+ caret: {
226
+ ...caret,
227
+ anchor,
228
+ focus
229
+ },
230
+ anchorOffset: textDocument.offsetAt(anchor),
231
+ focusOffset: textDocument.offsetAt(focus)
232
+ };
233
+ });
234
+ this.#renderCarets();
235
+ }
155
236
  edit(fileInstance) {
156
- if (this.#options.persistState === true && fileInstance.type === "file") {
157
- const file = fileInstance.__getCurrentFile?.();
158
- if (file !== void 0) requirePersistedCacheKey(file);
159
- }
160
- this.#invalidateOnAttach();
237
+ const editor = this.#getTypedEditor;
238
+ const previousSession = this.#editSession;
239
+ if (this.#isRendering) throw new Error("Editor.edit: the editor is already rendering its component");
240
+ if (this.#fileInstance != null && this.#fileInstance !== fileInstance) throw new Error("Editor.edit: a recycled edit session cannot attach to a different component");
241
+ const initialState = this.#initialState;
242
+ const initialEditorState = initialState?.editor;
243
+ const editStateKey = this.#editStateKey;
244
+ const editSession = getEditSession({
245
+ type: this.type,
246
+ editStateKey,
247
+ owner: this,
248
+ initialState,
249
+ previousSession
250
+ });
251
+ const previousDiffSession = editSession.diffSession;
252
+ this.#initialState = void 0;
253
+ this.#editSession = editSession;
161
254
  this.#fileInstance = fileInstance;
162
- this.#initialize();
163
- this.#detach = fileInstance.attachEditor(this);
164
- return () => this.cleanUp();
255
+ this.#isRendering = true;
256
+ this.#restoreEditorStateOnSync = editSession.editor != null;
257
+ try {
258
+ this.#initialize();
259
+ if (fileInstance.type === "file" && editor.type === "file") {
260
+ editor.#applyDocumentChange = fileInstance.applyDocumentChange.bind(fileInstance);
261
+ editor.#publishChange = (changes, file, lineAnnotations) => {
262
+ const event = {
263
+ changes,
264
+ file,
265
+ editor,
266
+ lineAnnotations
267
+ };
268
+ editor.#options.onChange?.(event);
269
+ fileInstance.emitEditChange(event);
270
+ };
271
+ if (this.#detach == null) this.#detach = fileInstance.__attachEditor(editor);
272
+ else fileInstance.__resumeEditor(editor);
273
+ } else if (fileInstance.type === "file-diff" && editor.type === "file-diff") {
274
+ editor.#applyDocumentChange = fileInstance.applyDocumentChange.bind(fileInstance);
275
+ editor.#publishChange = (changes, file, lineAnnotations) => {
276
+ const event = {
277
+ changes,
278
+ file,
279
+ editor,
280
+ lineAnnotations
281
+ };
282
+ editor.#options.onChange?.(event);
283
+ fileInstance.emitEditChange(event);
284
+ };
285
+ if (this.#detach == null) this.#detach = fileInstance.__attachEditor(editor);
286
+ else fileInstance.__resumeEditor(editor);
287
+ } else throw new Error("Editor.edit: Impossible edit state");
288
+ } catch (error) {
289
+ this.cleanUp("recycle");
290
+ if (editSession.type === "file-diff" && editSession.diffSession == null) editSession.diffSession = previousDiffSession;
291
+ if (this.#detach == null && editStateKey != null) if (editor.type === "file") EditStateManager.releaseFile(editStateKey, editor);
292
+ else EditStateManager.releaseFileDiff(editStateKey, editor);
293
+ this.#editSession = previousSession;
294
+ if (this.#detach == null) {
295
+ this.#fileInstance = void 0;
296
+ this.#applyDocumentChange = void 0;
297
+ this.#publishChange = void 0;
298
+ }
299
+ if (initialState != null && initialEditorState != null) initialState.editor = initialEditorState;
300
+ this.#initialState = initialState;
301
+ throw error;
302
+ }
303
+ return () => {
304
+ this.cleanUp("complete");
305
+ };
165
306
  }
166
307
  /**
167
308
  * Apply edits to current attached file. Every edit joins the undo timeline:
@@ -174,8 +315,8 @@ var Editor = class {
174
315
  * are remapped during replay and the text edit still joins the undo timeline.
175
316
  */
176
317
  applyEdits(edits, updateHistory = true) {
177
- const textDocument = this.#textDocument;
178
- if (textDocument == null) throw new Error("Editor is not attached");
318
+ const textDocument = this.#editSession?.document;
319
+ if (textDocument == null) throw new Error("Editor.applyEdits: Editor is not attached");
179
320
  const wasFocused = this.#contentHasFocus;
180
321
  const selectionsBefore = this.#selections;
181
322
  const selectionOffsetsBefore = selectionsBefore?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
@@ -191,11 +332,11 @@ var Editor = class {
191
332
  }
192
333
  /** Whether there is an edit to undo. */
193
334
  get canUndo() {
194
- return this.#textDocument?.canUndo ?? false;
335
+ return (this.#editSession ?? this.#initialState)?.document?.canUndo ?? false;
195
336
  }
196
337
  /** Whether there is an undone edit to redo. */
197
338
  get canRedo() {
198
- return this.#textDocument?.canRedo ?? false;
339
+ return (this.#editSession ?? this.#initialState)?.document?.canRedo ?? false;
199
340
  }
200
341
  /** Undo the last edit. Does nothing when there is nothing to undo. */
201
342
  undo() {
@@ -206,8 +347,9 @@ var Editor = class {
206
347
  this.#runCommand("redo");
207
348
  }
208
349
  getFile() {
209
- const fileInfo = this.#fileInfo;
210
- const textDocument = this.#textDocument;
350
+ const editSession = this.#editSession ?? this.#initialState;
351
+ const fileInfo = editSession?.fileInfo;
352
+ const textDocument = editSession?.document;
211
353
  if (fileInfo === void 0 || textDocument === void 0) return;
212
354
  const file = { ...fileInfo };
213
355
  Object.defineProperty(file, "contents", {
@@ -217,32 +359,56 @@ var Editor = class {
217
359
  return file;
218
360
  }
219
361
  getText() {
220
- return this.#textDocument?.getText() ?? "";
221
- }
222
- getState() {
223
- const fileInstance = this.#fileInstance;
362
+ return (this.#editSession ?? this.#initialState)?.document?.getText() ?? "";
363
+ }
364
+ /** Return an isolated copy of selections and restorable view state. */
365
+ getViewState() {
366
+ const fileInstance = this.#isRendering ? this.#fileInstance : void 0;
367
+ if (!this.#isRendering && this.#editSession?.editor != null) return cloneEditorViewState(this.#editSession.editor);
368
+ const selections = this.#selections?.map((selection) => ({
369
+ ...selection,
370
+ start: { ...selection.start },
371
+ end: { ...selection.end }
372
+ }));
373
+ if (fileInstance == null) return { selections };
374
+ const viewport = this.#getOwnedVerticalViewport();
375
+ const scrollLeft = fileInstance.getCodeScrollLeft();
376
+ if (viewport == null) return {
377
+ selections,
378
+ view: { scrollLeft }
379
+ };
224
380
  return {
225
- selections: this.#selections,
226
- view: fileInstance != null ? {
227
- scrollLeft: fileInstance.getCodeScrollLeft(),
228
- scrollTop: this.#getViewportScrollTop()
229
- } : void 0
381
+ selections,
382
+ view: {
383
+ scrollLeft,
384
+ scrollTop: viewport.scrollTop
385
+ }
230
386
  };
231
387
  }
232
- setState({ selections, view }) {
233
- if (this.#fileInstance === void 0 || this.#textDocument === void 0) throw new Error("Editor is not attached");
388
+ /**
389
+ * Return the objects that make up the active edit session, or undefined when
390
+ * no complete session exists.
391
+ */
392
+ getEditState() {
393
+ const editSession = this.#editSession;
394
+ if (editSession == null) return;
395
+ if (this.#fileInstance == null) throw new Error(`Editor.getEditState: active ${this.type} session has no component`);
396
+ return toManagedEditState(editSession);
397
+ }
398
+ setViewState({ selections, view }) {
399
+ if (!this.#isRendering || this.#fileInstance === void 0 || this.#editSession?.document === void 0) throw new Error("Editor.setViewState: Editor is not attached");
234
400
  this.#canMountSelectionAction = false;
235
401
  this.#updateSelections(selections ?? []);
236
402
  if (view != null) {
237
403
  this.#fileInstance.setCodeScrollLeft(view.scrollLeft);
238
- if (view.scrollTop !== void 0) this.#setViewportScrollTop(view.scrollTop);
239
- return;
240
- }
241
- this.#scrollToPrimaryCaret();
404
+ const viewport = this.#getOwnedVerticalViewport();
405
+ if (view.scrollTop !== void 0 && viewport != null) viewport.scrollTop = view.scrollTop;
406
+ } else if (this.#ownsVerticalViewport) this.#scrollToPrimaryCaret();
407
+ this.#checkpointEditSessionState();
242
408
  }
243
409
  setSelections(selections) {
244
- const textDocument = this.#textDocument;
245
- if (textDocument === void 0) throw new Error("Text document is not initialized");
410
+ const textDocument = this.#editSession?.document;
411
+ if (textDocument === void 0) throw new Error("Editor.setSelections: Text document is not initialized");
246
412
  const resolvedSelections = selections.map((selection) => {
247
413
  let start = textDocument.normalizePosition(selection.start);
248
414
  let end = textDocument.normalizePosition(selection.end);
@@ -264,8 +430,8 @@ var Editor = class {
264
430
  this.#scrollToPrimaryCaret();
265
431
  }
266
432
  setMarkers(markers) {
267
- const textDocument = this.#textDocument;
268
- if (textDocument === void 0) throw new Error("Text document is not initialized");
433
+ const textDocument = this.#editSession?.document;
434
+ if (textDocument === void 0) throw new Error("Editor.setMarkers: Text document is not initialized");
269
435
  if (markers.length === 0) {
270
436
  this.#markerRenderer?.cleanup();
271
437
  this.#markerRenderer = void 0;
@@ -284,13 +450,14 @@ var Editor = class {
284
450
  this.#markerRenderer.setMarkers(markers, textDocument);
285
451
  if (this.#contentElement !== void 0) this.#markerRenderer.listenHover(this.#contentElement);
286
452
  this.#updateSelections(this.#selections ?? []);
453
+ this.#renderCarets();
287
454
  }
288
455
  focus(options) {
289
456
  const preventScroll = options?.preventScroll ?? false;
290
457
  const lineNumber = options?.lineNumber;
291
458
  if (lineNumber === "first-visible" || typeof lineNumber === "number") {
292
- const textDocument = this.#textDocument;
293
- if (textDocument == null || this.#fileInstance == null) return;
459
+ const textDocument = this.#editSession?.document;
460
+ if (textDocument == null || !this.#isRendering || this.#fileInstance == null) return;
294
461
  const targetLineNumber = lineNumber === "first-visible" ? this.#getFirstVisibleLineNumber(options?.offset) : lineNumber;
295
462
  if (targetLineNumber == null) return;
296
463
  const position = textDocument.normalizePosition({
@@ -312,27 +479,30 @@ var Editor = class {
312
479
  this.#shouldIgnoreSelectionChange = false;
313
480
  this.#contentElement?.blur();
314
481
  }
315
- cleanUp(recycle = false) {
482
+ cleanUp(reason = "discard") {
483
+ const fileInstance = this.#fileInstance;
484
+ const recycle = reason === "recycle";
485
+ this.#cancelEditPrediction(true, false);
486
+ if (!recycle) this.#editPredictionHistory = [];
487
+ const editSession = this.#editSession;
488
+ if (fileInstance != null && editSession != null) {
489
+ const discardDiffState = !this.#checkpointEditSessionState();
490
+ if (!recycle) this.#releaseEditSession(discardDiffState);
491
+ }
316
492
  this.#invalidateOnAttach();
317
493
  if (!recycle) this.#attachState.delivered = false;
318
- const hadFileInstance = this.#fileInstance != null;
319
- const shouldRestoreState = this.#options.persistState === true;
320
- this.#stateRestoreGeneration++;
321
- this.#persistCurrentState();
322
- if (hadFileInstance) this.#restoreStateOnNextSync = shouldRestoreState;
494
+ this.#isRendering = false;
323
495
  dequeueRender(this.#handleCustomPasteEvent);
324
496
  this.#tokenizer?.cleanUp();
325
497
  this.#tokenizer = void 0;
326
- if (!recycle) {
327
- this.#textDocument = void 0;
328
- this.#fileInfo = void 0;
329
- }
330
498
  this.#globalEventDisposes?.forEach((dispose) => dispose());
331
499
  this.#globalEventDisposes = void 0;
332
500
  this.#editorEventDisposes?.forEach((dispose) => dispose());
333
501
  this.#editorEventDisposes = void 0;
334
- this.#detach?.(recycle);
335
- this.#detach = void 0;
502
+ if (!recycle) {
503
+ this.#detach?.();
504
+ this.#detach = void 0;
505
+ }
336
506
  this.#gutterWidthCache = void 0;
337
507
  this.#contentWidthCache = void 0;
338
508
  this.#lineYCache.clear();
@@ -351,6 +521,7 @@ var Editor = class {
351
521
  this.#popoverManager?.cleanUp();
352
522
  this.#popoverManager = void 0;
353
523
  this.#gutterElement = void 0;
524
+ this.#deletionsColumn = void 0;
354
525
  this.#contentElement?.removeAttribute("contentEditable");
355
526
  this.#contentElement = void 0;
356
527
  this.#replacementFocusRequest = void 0;
@@ -364,25 +535,44 @@ var Editor = class {
364
535
  cancelAnimationFrame(this.#themeSelectionRefreshFrame);
365
536
  this.#themeSelectionRefreshFrame = void 0;
366
537
  }
367
- this.#resetState();
368
- this.#fileInstance = void 0;
369
- }
370
- /** @internal Capture outgoing state and substitute cached text before render. */
371
- __prepareFile(file) {
372
- if (this.#options.persistState !== true) return file;
373
- const cacheKey = requirePersistedCacheKey(file);
374
- const fileInfo = this.#fileInfo;
375
- const languageId = file.lang ?? getFiletypeFromFileName(file.name);
376
- if (fileInfo !== void 0 && (requirePersistedCacheKey(fileInfo) !== cacheKey || fileInfo.name !== file.name || this.#textDocument?.languageId !== languageId)) {
377
- this.#stateRestoreGeneration++;
378
- this.#persistCurrentState();
379
- }
380
- const textDocument = this.#getCachedTextDocument(file, cacheKey);
381
- if (textDocument === void 0 || textDocument.getText() === file.contents) return file;
382
- return {
383
- ...file,
384
- contents: textDocument.getText()
385
- };
538
+ this.#resetState(recycle);
539
+ if (!recycle) try {
540
+ if (fileInstance != null && editSession != null) {
541
+ const editor = this.#getTypedEditor;
542
+ const mode = reason === "complete" ? "install" : "discard";
543
+ if (fileInstance.type === "file" && editor.type === "file") fileInstance.__completeEditSession(editor, mode);
544
+ else if (fileInstance.type === "file-diff" && editor.type === "file-diff") fileInstance.__completeEditSession(editor, mode);
545
+ else throw new Error("Editor.cleanUp: Impossible edit state");
546
+ }
547
+ } finally {
548
+ this.#editSession = void 0;
549
+ this.#fileInstance = void 0;
550
+ this.#applyDocumentChange = void 0;
551
+ this.#publishChange = void 0;
552
+ }
553
+ }
554
+ #checkpointEditSessionState() {
555
+ const fileInstance = this.#fileInstance;
556
+ const editSession = this.#editSession;
557
+ if (fileInstance == null || editSession == null) return false;
558
+ const editorState = this.#isRendering ? this.getViewState() : editSession.editor ?? {};
559
+ const { view } = editorState;
560
+ const hasNonDefaultView = view != null && (view.scrollLeft !== 0 || (view.scrollTop ?? 0) !== 0);
561
+ const hasEditorState = editorState.selections != null || hasNonDefaultView;
562
+ editSession.editor = cloneEditorViewState(editorState);
563
+ if (editSession.type === "file") return false;
564
+ const capturedDiffSession = fileInstance.__captureDocumentSessionState();
565
+ editSession.diffSession = capturedDiffSession?.diffSession;
566
+ if (capturedDiffSession == null) return false;
567
+ const document = editSession.document;
568
+ return capturedDiffSession.hasChanges || hasEditorState || document?.canUndo === true || document?.canRedo === true;
569
+ }
570
+ #releaseEditSession(discardDiffState = false) {
571
+ const editStateKey = this.#editStateKey;
572
+ if (editStateKey == null) return;
573
+ const editor = this.#getTypedEditor;
574
+ if (editor.type === "file") EditStateManager.releaseFile(editStateKey, editor);
575
+ else EditStateManager.releaseFileDiff(editStateKey, editor, discardDiffState);
386
576
  }
387
577
  /** @internal */
388
578
  __postponeBgTokenizeToNextFrame() {
@@ -401,9 +591,40 @@ var Editor = class {
401
591
  if (contentElement != null && (this.#contentHasFocus || shadowActiveElement === contentElement || shadowActiveElement != null && contentElement.contains(shadowActiveElement))) this.#replacementFocusRequest = {};
402
592
  }
403
593
  /** @internal */
404
- __syncRenderView = (highlighter, fileContainer, fileOrDiff, lineAnnotations, renderRange) => {
594
+ __getDocumentContents(fallbackFile) {
595
+ const fileInfo = this.#editSession?.fileInfo;
596
+ const textDocument = this.#editSession?.document;
597
+ if (textDocument == null) return fileInfo == null || fallbackFile == null ? void 0 : {
598
+ ...fileInfo,
599
+ contents: fallbackFile.contents
600
+ };
601
+ const resolvedFileInfo = fileInfo ?? fallbackFile;
602
+ if (resolvedFileInfo == null) return;
603
+ return {
604
+ name: resolvedFileInfo.name,
605
+ lang: resolvedFileInfo.lang,
606
+ contents: textDocument.getText()
607
+ };
608
+ }
609
+ /** @internal */
610
+ __getDocumentSessionState() {
611
+ const snapshot = this.#editSession?.diffSession;
612
+ return snapshot != null ? cloneRetainedDiffSessionSnapshot(snapshot) : void 0;
613
+ }
614
+ /** @internal */
615
+ __syncRenderView = (props) => {
616
+ const { highlighter, fileContainer, renderRange, resetHistory = false } = props;
617
+ const renderView = props;
618
+ let fileOrDiff;
619
+ if ("file" in renderView && this.type === "file") fileOrDiff = renderView.file;
620
+ else if ("fileDiff" in renderView && this.type === "file-diff") fileOrDiff = renderView.fileDiff;
621
+ else throw new Error("Editor.__syncRenderView: Impossible render state");
622
+ const lineAnnotations = renderView.lineAnnotations;
623
+ const editStateKey = this.#editStateKey;
624
+ const externalDocument = renderView.externalDocument === true;
405
625
  const fileInstance = this.#fileInstance;
406
- if (fileInstance == null) return;
626
+ const editSession = this.#editSession;
627
+ if (!this.#isRendering || fileInstance == null || editSession == null) return;
407
628
  const shadowRoot = fileContainer.shadowRoot;
408
629
  if (shadowRoot == null) {
409
630
  console.error("[editor] Could not find the shadow root.");
@@ -412,16 +633,26 @@ var Editor = class {
412
633
  let codeElement;
413
634
  let gutterEl;
414
635
  let contentEl;
415
- for (const el of shadowRoot.querySelectorAll("[data-code]")) if (el.dataset.deletions === void 0) {
416
- codeElement = el;
636
+ let deletionsGutterEl;
637
+ let deletionsContentEl;
638
+ for (const el of shadowRoot.querySelectorAll("[data-code]")) {
639
+ const isDeletions = el.dataset.deletions != null;
640
+ if (!isDeletions) codeElement = el;
417
641
  for (const child of el.children) {
418
- const el = child;
419
- const { gutter, content } = el.dataset;
420
- if (gutter !== void 0) gutterEl = el;
421
- else if (content !== void 0) contentEl = el;
642
+ if (!(child instanceof HTMLElement)) continue;
643
+ const { gutter, content } = child.dataset;
644
+ if (gutter != null) if (isDeletions) deletionsGutterEl = child;
645
+ else gutterEl = child;
646
+ else if (content != null) if (isDeletions) deletionsContentEl = child;
647
+ else contentEl = child;
422
648
  }
423
- break;
424
649
  }
650
+ if (deletionsContentEl == null) this.#deletionsColumn = void 0;
651
+ else if (deletionsGutterEl == null) throw new Error("Editor.__syncRenderView: the deletions column has no gutter");
652
+ else this.#deletionsColumn = {
653
+ content: deletionsContentEl,
654
+ gutter: deletionsGutterEl
655
+ };
425
656
  if (codeElement === void 0 || contentEl === void 0) {
426
657
  this.#replacementFocusRequest = void 0;
427
658
  return;
@@ -434,45 +665,44 @@ var Editor = class {
434
665
  if (this.#themeStyleElement !== void 0) shadowRoot.appendChild(this.#themeStyleElement);
435
666
  if (this.#spriteElement !== void 0) shadowRoot.prepend(this.#spriteElement);
436
667
  }
437
- 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;
438
- const persistedCacheKey = this.#options.persistState === true ? requirePersistedCacheKey(fileOrDiff) : void 0;
439
- let persistedStateTarget;
440
- if (shouldRebuildDocument) {
668
+ const languageId = fileOrDiff.lang ?? getFiletypeFromFileName(fileOrDiff.name);
669
+ const contents = "contents" in fileOrDiff ? fileOrDiff.contents : fileOrDiff.additionLines.join("");
670
+ const previousTextDocument = editSession.document;
671
+ const documentChanges = externalDocument && previousTextDocument !== void 0 && previousTextDocument.getText() !== contents ? [createFullDocumentChange(previousTextDocument.getText(), contents)] : void 0;
672
+ const resetForExternalDocument = externalDocument && resetHistory;
673
+ if (editSession.document == null || editSession.fileInfo == null || editStateKey == null && (editSession.fileInfo.name !== fileOrDiff.name || editSession.document.languageId !== languageId) || resetForExternalDocument) {
674
+ this.#cancelEditPrediction(true);
675
+ this.#editPredictionHistory = [];
441
676
  this.#invalidateOnAttach();
442
- let contents = "";
443
- if ("contents" in fileOrDiff) contents = fileOrDiff.contents;
444
- else contents = fileOrDiff.additionLines.join("");
445
- const editStack = new EditStack({ maxEntries: this.#options.historyMaxEntries });
446
- const { name, lang, cacheKey } = fileOrDiff;
447
- const languageId = lang ?? getFiletypeFromFileName(fileOrDiff.name);
448
- const cachedTextDocument = persistedCacheKey !== void 0 ? this.#getCachedTextDocument(fileOrDiff, persistedCacheKey) : void 0;
449
- const reusableTextDocument = fileInstance.type === "file" || cachedTextDocument?.getText() === contents ? cachedTextDocument : void 0;
450
- const textDocument = reusableTextDocument ?? new TextDocument(fileOrDiff.name, contents, languageId, 0, editStack);
451
- this.#fileInfo = {
677
+ const { name, lang } = fileOrDiff;
678
+ let textDocument = resetForExternalDocument || editStateKey == null && editSession.fileInfo != null ? void 0 : editSession.document;
679
+ if (previousTextDocument != null && textDocument == null) editSession.editor = void 0;
680
+ if (textDocument == null) {
681
+ const editStack = new EditStack({ maxEntries: this.#options.historyMaxEntries });
682
+ textDocument = new TextDocument(fileOrDiff.name, contents, languageId, 0, editStack);
683
+ }
684
+ editSession.fileInfo = {
452
685
  name,
453
- lang,
454
- cacheKey
686
+ lang
455
687
  };
456
- this.#textDocument = textDocument;
457
- if (persistedCacheKey !== void 0) {
458
- this.#textDocumentCache.set(persistedCacheKey, textDocument);
459
- persistedStateTarget = {
460
- cacheKey: persistedCacheKey,
461
- textDocument
462
- };
463
- }
688
+ editSession.document = textDocument;
689
+ if (resetForExternalDocument) editSession.diffSession = void 0;
464
690
  this.#tokenizer?.cleanUp();
465
691
  this.#tokenizer = void 0;
466
692
  this.#resetState();
467
693
  this.#wrapLineOffsetsCache.clear();
468
694
  this.#selections = this.#initSelections;
469
- if (this.#options.__debug === true) console.log(reusableTextDocument !== void 0 ? "[diffs/editor] cached text document reused for" : "[diffs/editor] text document rebuilt from", fileOrDiff.name);
695
+ if (this.#options.__debug === true) console.log("[diffs/editor] text document initialized from", fileOrDiff.name);
470
696
  }
471
- if (persistedStateTarget === void 0 && this.#restoreStateOnNextSync && persistedCacheKey !== void 0 && this.#textDocument !== void 0) persistedStateTarget = {
472
- cacheKey: persistedCacheKey,
473
- textDocument: this.#textDocument
474
- };
475
- const textDocument = this.#textDocument;
697
+ if (externalDocument && !resetForExternalDocument) {
698
+ this.#applyExternalDocumentReplacement(editSession, contents, lineAnnotations);
699
+ const { name, lang } = fileOrDiff;
700
+ editSession.fileInfo = {
701
+ name,
702
+ lang
703
+ };
704
+ }
705
+ const textDocument = editSession.document;
476
706
  if (this.#tokenizer == null && textDocument != null) this.#tokenizer = new EditorTokenizer({
477
707
  highlighter,
478
708
  textDocument,
@@ -516,178 +746,86 @@ var Editor = class {
516
746
  this.#renderRange = renderRange;
517
747
  this.#viewportWindowLines = renderRange?.totalLines;
518
748
  this.#tokenizer?.prebuildStateStack(renderRange);
519
- this.#markerRenderer?.removePopover();
520
- if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
521
- if (this.#initSelections !== void 0 && this.#primaryCaretElement !== void 0) {
522
- this.#initSelections = void 0;
523
- this.#scrollToPrimaryCaret(false, "center");
524
- } else if (this.#scrollingToLine !== void 0) this.#scrollToLine(this.#scrollingToLine, this.#scrollingToLineChar, this.#scrollingToLineNoFocus);
525
- else if (this.#replacementFocusRequest !== void 0) this.#restoreReplacementFocus();
526
- else if (this.#selections !== void 0 && this.#selections.length > 0 && this.#contentHasFocus && !this.#retainSearchPanelFocus) this.focus({ preventScroll: true });
527
- if (this.#retainSearchPanelFocus) this.#searchPanel?.focus();
528
- if (this.#options.__debug === true && renderRange !== void 0) {
529
- const { startingLine, totalLines } = renderRange;
530
- console.log("[diffs/editor] render file:", fileOrDiff.name, "RenderRange:", startingLine + "-" + (startingLine + totalLines), "of", this.#textDocument?.lineCount, "lines");
531
- }
532
- if (persistedStateTarget !== void 0) {
533
- this.#restoreStateOnNextSync = false;
534
- this.#restorePersistedState(persistedStateTarget.cacheKey, persistedStateTarget.textDocument);
749
+ const retainedEditorState = editSession.editor;
750
+ const restoreEditorStateOnSync = this.#restoreEditorStateOnSync;
751
+ try {
752
+ this.#markerRenderer?.removePopover();
753
+ if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
754
+ this.#renderCarets();
755
+ if (this.#retryEditPredictionOnRender) this.#scheduleEditPrediction();
756
+ if (this.#initSelections !== void 0 && this.#primaryCaretElement !== void 0) {
757
+ this.#initSelections = void 0;
758
+ this.#scrollToPrimaryCaret(false, "center");
759
+ } else if (this.#scrollingToLine !== void 0) this.#scrollToLine(this.#scrollingToLine, this.#scrollingToLineChar, this.#scrollingToLineNoFocus);
760
+ else if (this.#replacementFocusRequest !== void 0) this.#restoreReplacementFocus();
761
+ else if (this.#selections !== void 0 && this.#selections.length > 0 && this.#contentHasFocus && !this.#retainSearchPanelFocus) this.focus({ preventScroll: true });
762
+ if (this.#retainSearchPanelFocus) this.#searchPanel?.focus();
763
+ if (restoreEditorStateOnSync) {
764
+ this.#restoreEditorStateOnSync = false;
765
+ if (retainedEditorState !== void 0) this.setViewState(retainedEditorState);
766
+ }
767
+ if (this.#options.__debug === true && renderRange !== void 0) {
768
+ const { startingLine, totalLines } = renderRange;
769
+ console.log("[diffs/editor] render file:", fileOrDiff.name, "RenderRange:", startingLine + "-" + (startingLine + totalLines), "of", editSession.document?.lineCount, "lines");
770
+ }
771
+ this.#checkpointEditSessionState();
772
+ } catch (error) {
773
+ this.#restoreEditorStateOnSync = restoreEditorStateOnSync;
774
+ editSession.editor = retainedEditorState;
775
+ throw error;
535
776
  }
777
+ if (documentChanges != null) this.#emitChange(documentChanges, lineAnnotations);
536
778
  this.#scheduleOnAttach(fileInstance);
537
779
  };
538
- #getCachedTextDocument(file, cacheKey) {
539
- const textDocument = this.#textDocumentCache.get(cacheKey);
540
- const languageId = file.lang ?? getFiletypeFromFileName(file.name);
541
- return textDocument?.languageId === languageId ? textDocument : void 0;
542
- }
543
- #getStateStorage() {
544
- const option = this.#options.persistStateStorage ?? "inMemory";
545
- if (this.#stateStorage === void 0 || this.#stateStorageOption !== option) {
546
- this.#stateStorage = createStateStorage(option);
547
- this.#stateStorageOption = option;
548
- }
549
- return this.#stateStorage;
550
- }
551
- #persistCurrentState() {
552
- const fileInfo = this.#fileInfo;
553
- const textDocument = this.#textDocument;
554
- if (this.#options.persistState !== true || this.#fileInstance === void 0 || fileInfo === void 0 || textDocument === void 0) return;
555
- const cacheKey = requirePersistedCacheKey(fileInfo);
556
- this.#textDocumentCache.set(cacheKey, textDocument);
557
- let storage;
558
- try {
559
- storage = this.#getStateStorage();
560
- } catch {
561
- return;
562
- }
563
- const state = cloneEditorState(this.getState());
564
- const pendingRestore = this.#pendingStateRestore;
565
- if (pendingRestore?.cacheKey === cacheKey && pendingRestore.textDocument === textDocument) {
566
- this.#pendingStateRestore = void 0;
567
- if (textDocument.version === pendingRestore.documentVersion && this.#selections === pendingRestore.selections && state.view?.scrollLeft === pendingRestore.view?.scrollLeft && state.view?.scrollTop === pendingRestore.view?.scrollTop) return;
568
- this.#writeState(storage, cacheKey, state, pendingRestore.completion);
569
- return;
570
- }
571
- this.#writeState(storage, cacheKey, state);
572
- }
573
- #writeState(storage, cacheKey, state, waitFor) {
574
- const previousWrite = this.#pendingStateWrites.get(cacheKey);
575
- if (waitFor !== void 0 || previousWrite !== void 0) {
576
- let pending = waitFor ?? Promise.resolve();
577
- if (previousWrite !== void 0) pending = pending.then(() => previousWrite);
578
- this.#trackStateWrite(cacheKey, pending.then(() => storage.set(cacheKey, state)));
579
- return;
580
- }
581
- let result;
582
- try {
583
- result = storage.set(cacheKey, state);
584
- } catch {
585
- return;
586
- }
587
- if (!isPromise(result)) return;
588
- this.#trackStateWrite(cacheKey, result);
589
- }
590
- #trackStateWrite(cacheKey, result) {
591
- const pending = result.catch(() => {});
592
- this.#pendingStateWrites.set(cacheKey, pending);
593
- pending.finally(() => {
594
- if (this.#pendingStateWrites.get(cacheKey) === pending) this.#pendingStateWrites.delete(cacheKey);
595
- });
596
- }
597
- #restorePersistedState(cacheKey, textDocument) {
598
- const generation = ++this.#stateRestoreGeneration;
599
- const documentVersion = textDocument.version;
780
+ #applyExternalDocumentReplacement(editSession, contents, lineAnnotations) {
781
+ const textDocument = editSession.document;
782
+ if (textDocument == null || textDocument.getText() === contents) return;
600
783
  const selections = this.#selections;
601
- const view = this.getState().view;
602
- let inputWatch;
603
- const applyState = (state) => {
604
- const currentView = this.getState().view;
605
- if (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;
606
- const userScrolled = inputWatch?.userScrolled() === true;
607
- if (state === void 0) {
608
- this.#fileInstance?.setCodeScrollLeft(0);
609
- if (!userScrolled) this.#setViewportScrollTop(0);
610
- return;
611
- }
612
- const restored = cloneEditorState(state);
613
- if (userScrolled && restored.view !== void 0) restored.view = { scrollLeft: restored.view.scrollLeft };
614
- this.setState(restored);
615
- };
616
- const readState = () => {
617
- let result;
618
- try {
619
- result = this.#getStateStorage().get(cacheKey);
620
- } catch {
621
- return;
622
- }
623
- if (isPromise(result)) return result.then(applyState).catch(() => {});
624
- else try {
625
- applyState(result);
626
- } catch {}
784
+ const selectionOffsets = selections?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
785
+ const replacement = {
786
+ start: 0,
787
+ end: textDocument.getText().length,
788
+ text: contents
627
789
  };
628
- const pendingWrite = this.#pendingStateWrites.get(cacheKey);
629
- const result = pendingWrite === void 0 ? readState() : pendingWrite.then(readState);
630
- if (isPromise(result)) {
631
- inputWatch = this.#watchViewportUserInput();
632
- const pendingRestore = {
633
- cacheKey,
634
- textDocument,
635
- documentVersion,
636
- selections,
637
- view,
638
- completion: result.catch(() => {})
639
- };
640
- this.#pendingStateRestore = pendingRestore;
641
- pendingRestore.completion.finally(() => {
642
- inputWatch?.dispose();
643
- if (this.#pendingStateRestore === pendingRestore) this.#pendingStateRestore = void 0;
644
- });
790
+ if (textDocument.applyResolvedEdits([replacement], true, selections, void 0, true) == null) return;
791
+ if (selections != null && selectionOffsets != null) {
792
+ const nextSelections = remapSelectionsAfterEdits(textDocument, selections, selectionOffsets, [replacement]);
793
+ textDocument.setLastUndoSelectionsAfter(nextSelections);
794
+ this.#selections = nextSelections;
645
795
  }
796
+ if (this.#lineAnnotations != null || lineAnnotations != null) textDocument.setLastUndoLineAnnotations(this.#lineAnnotations ?? [], lineAnnotations ?? []);
797
+ this.#tokenizer?.cleanUp();
798
+ this.#tokenizer = void 0;
799
+ this.#resetCache();
800
+ this.#wrapLineOffsetsCache.clear();
801
+ this.#markerRenderer?.removePopover();
802
+ if (this.#searchPanel !== void 0 && this.#matches !== void 0) this.#searchPanel.updateMatches({ syncSelection: false });
646
803
  }
647
- #watchViewportUserInput() {
648
- const viewport = this.#getScrollViewport();
649
- if (!(viewport instanceof HTMLElement)) return;
650
- let scrolled = false;
651
- const eventTypes = [
652
- "wheel",
653
- "touchstart",
654
- "mousedown"
655
- ];
656
- const dispose = () => {
657
- for (const type of eventTypes) viewport.removeEventListener(type, onInput, { capture: true });
658
- };
659
- const onInput = () => {
660
- scrolled = true;
661
- dispose();
662
- };
663
- for (const type of eventTypes) viewport.addEventListener(type, onInput, {
664
- capture: true,
665
- passive: true
666
- });
667
- return {
668
- userScrolled: () => scrolled,
669
- dispose
670
- };
804
+ get #fileDiffInstance() {
805
+ return this.#fileInstance?.type === "file-diff" ? this.#fileInstance : void 0;
806
+ }
807
+ get #virtualizedInstance() {
808
+ return isVirtualizedEditorComponent(this.#fileInstance) ? this.#fileInstance : void 0;
671
809
  }
672
810
  #isLineRenderable(line) {
673
- return this.#fileInstance?.isLineRenderable?.(line + 1) ?? true;
811
+ return this.#fileDiffInstance?.isLineRenderable?.(line + 1) ?? true;
674
812
  }
675
813
  get #resolveRenderableLine() {
676
- const fileInstance = this.#fileInstance;
677
- if (fileInstance?.getNearestRenderableLine == null) return;
814
+ const fileInstance = this.#fileDiffInstance;
815
+ if (fileInstance == null) return;
678
816
  return (line, direction) => {
679
817
  const nearest = fileInstance.getNearestRenderableLine(line + 1, direction);
680
818
  return nearest == null ? void 0 : nearest - 1;
681
819
  };
682
820
  }
683
821
  #revealLineIfCollapsed(line) {
684
- if (!this.#isLineRenderable(line)) this.#fileInstance?.revealLine?.(line + 1);
822
+ if (!this.#isLineRenderable(line)) this.#fileDiffInstance?.revealLine?.(line + 1);
685
823
  }
686
824
  get #diffSyle() {
687
- return this.#fileInstance?.options.diffStyle ?? "split";
825
+ return this.#fileDiffInstance?.options.diffStyle ?? "split";
688
826
  }
689
827
  get #isDiff() {
690
- return this.#fileInstance?.type === "file-diff";
828
+ return this.type === "file-diff";
691
829
  }
692
830
  get #isWrap() {
693
831
  return this.#fileInstance?.options.overflow === "wrap";
@@ -706,7 +844,7 @@ var Editor = class {
706
844
  this.#lineElementsCache.clear();
707
845
  this.#lastAccessedCharX = void 0;
708
846
  }
709
- #resetState() {
847
+ #resetState(preserveCarets = false) {
710
848
  this.#setEditorActiveLineSafe(null);
711
849
  this.#gutterWidthCache = void 0;
712
850
  this.#contentWidthCache = void 0;
@@ -714,6 +852,11 @@ var Editor = class {
714
852
  this.#suppressNativeSelectionSync = false;
715
853
  this.#overlayElements?.forEach((el) => el.remove());
716
854
  this.#overlayElements = void 0;
855
+ this.#caretElements?.forEach((element) => element.remove());
856
+ this.#caretElements = void 0;
857
+ this.#caretHighlightElements?.forEach((element) => element.remove());
858
+ this.#caretHighlightElements = void 0;
859
+ if (!preserveCarets) this.#carets = void 0;
717
860
  this.#selections = void 0;
718
861
  this.#scrollingToLine = void 0;
719
862
  this.#markerRenderer?.cleanup();
@@ -734,13 +877,13 @@ var Editor = class {
734
877
  }
735
878
  #scheduleOnAttach(fileInstance) {
736
879
  const attachState = this.#attachState;
737
- const textDocument = this.#textDocument;
880
+ const textDocument = this.#editSession?.document;
738
881
  if (attachState.delivered || attachState.callback != null || textDocument == null) return;
739
882
  const { generation } = attachState;
740
883
  const callback = () => {
741
884
  if (attachState.callback !== callback) return;
742
885
  attachState.callback = void 0;
743
- if (generation !== attachState.generation || this.#fileInstance !== fileInstance || this.#textDocument !== textDocument) return;
886
+ if (generation !== attachState.generation || this.#fileInstance !== fileInstance || this.#editSession?.document !== textDocument) return;
744
887
  const contentElement = this.#contentElement;
745
888
  const shadowRoot = this.#fileContainer?.shadowRoot;
746
889
  if (contentElement == null || shadowRoot == null || !shadowRoot.contains(contentElement)) return;
@@ -751,21 +894,16 @@ var Editor = class {
751
894
  queueRender(callback);
752
895
  }
753
896
  #getScrollViewport() {
754
- const viewport = this.#fileInstance?.getEditorViewport?.();
755
- if (viewport !== void 0) return viewport;
897
+ const viewport = this.#virtualizedInstance?.getEditorViewport?.();
898
+ if (viewport != null) return viewport;
756
899
  const fileContainer = this.#fileContainer;
757
900
  if (this.#fileInstance == null || fileContainer == null) return;
758
901
  return lookupScrollContainer(fileContainer);
759
902
  }
760
- #getViewportScrollTop() {
761
- const viewport = this.#getScrollViewport();
762
- if (viewport instanceof HTMLElement) return viewport.scrollTop;
763
- return viewport?.defaultView?.scrollY ?? 0;
764
- }
765
- #setViewportScrollTop(scrollTop) {
766
- const viewport = this.#getScrollViewport();
767
- if (viewport instanceof HTMLElement) viewport.scrollTop = scrollTop;
768
- else if (viewport instanceof Document) viewport.defaultView?.scrollTo({ top: scrollTop });
903
+ #getOwnedVerticalViewport() {
904
+ if (!this.#ownsVerticalViewport) return;
905
+ const viewport = this.#virtualizedInstance?.getEditorViewport?.();
906
+ if (viewport instanceof HTMLElement) return viewport;
769
907
  }
770
908
  #initialize() {
771
909
  this.#globalStyleElement = h("style", {
@@ -817,7 +955,9 @@ var Editor = class {
817
955
  if (this.#selections !== void 0 && this.#selections.length > 1 && !this.#isContentMouseDown) return;
818
956
  const selectionRaw = document.getSelection();
819
957
  if (selectionRaw == null) return;
820
- const composedRange = typeof selectionRaw.getComposedRanges === "function" ? selectionRaw.getComposedRanges({ shadowRoots: [shadowRoot] })?.[0] : getShadowRootRange(shadowRoot);
958
+ let composedRange;
959
+ if (typeof selectionRaw.getComposedRanges === "function") composedRange = selectionRaw.getComposedRanges({ shadowRoots: [shadowRoot] })?.[0];
960
+ else composedRange = getShadowRootRange(shadowRoot);
821
961
  if (composedRange === void 0 || !this.#rangeBelongsToEditor(composedRange)) return;
822
962
  let selection = convertSelection(composedRange, 0);
823
963
  if (selection === void 0) return;
@@ -844,6 +984,10 @@ var Editor = class {
844
984
  addEventListener(document, "pointercancel", finishMouseSelection, { passive: true }),
845
985
  addEventListener(document, "keydown", (e) => {
846
986
  if (e.key === "Shift") this.#selectionStart = this.#selections?.at(-1);
987
+ else if (e.key === "Alt" && !e.repeat && this.#contentHasFocus && this.#options.editPrediction?.mode === "subtle") {
988
+ this.#editPredictionRevealed = !this.#editPredictionRevealed;
989
+ this.#updateSelections(this.#selections ?? []);
990
+ }
847
991
  }, { passive: true }),
848
992
  addEventListener(document, "keyup", (e) => {
849
993
  if (e.key === "Shift") this.#selectionStart = void 0;
@@ -955,6 +1099,16 @@ var Editor = class {
955
1099
  this.#canMountSelectionAction = true;
956
1100
  this.#suppressNativeSelectionSync = false;
957
1101
  if (e.isComposing || e.keyCode === 229) return;
1102
+ if (e.key === "Tab" && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.isComposing && !this.#isComposing && (!e.altKey || this.#options.editPrediction?.mode === "subtle") && this.#editPrediction?.rendered === true) {
1103
+ this.#acceptEditPrediction();
1104
+ e.preventDefault();
1105
+ return;
1106
+ }
1107
+ if (e.key === "Escape" && this.#editPrediction !== void 0) {
1108
+ this.#cancelEditPrediction(true);
1109
+ e.preventDefault();
1110
+ return;
1111
+ }
958
1112
  const command = resolveEditorCommandFromKeyboardEvent(e, this.#options.keymap);
959
1113
  if (command !== void 0) {
960
1114
  e.preventDefault();
@@ -969,7 +1123,7 @@ var Editor = class {
969
1123
  return;
970
1124
  }
971
1125
  const mvShortcut = isMoveCursorShortcut(e);
972
- const textDocument = this.#textDocument;
1126
+ const textDocument = this.#editSession?.document;
973
1127
  if (this.#selections !== void 0 && this.#selections.length > 0 && mvShortcut !== void 0 && textDocument !== void 0) {
974
1128
  const cursorMoveOptions = {
975
1129
  getSoftLineOffsets: this.#isWrap ? (line) => this.#wrapLineTextOrWholeLine(line) : void 0,
@@ -1020,7 +1174,7 @@ var Editor = class {
1020
1174
  if (!targetIsContentElement(e)) return;
1021
1175
  e.preventDefault();
1022
1176
  const clipboardData = e.clipboardData;
1023
- const textDocument = this.#textDocument;
1177
+ const textDocument = this.#editSession?.document;
1024
1178
  if (clipboardData === null || textDocument === void 0) return;
1025
1179
  let text = clipboardData.getData("text");
1026
1180
  const selectionCount = this.#selections?.length ?? 0;
@@ -1035,7 +1189,10 @@ var Editor = class {
1035
1189
  }),
1036
1190
  addEventListener(contentEl, "beforeinput", (e) => {
1037
1191
  if (!targetIsContentElement(e)) return;
1038
- if (e.inputType === "insertCompositionText") return;
1192
+ if (e.inputType === "insertCompositionText") {
1193
+ this.#cancelEditPrediction(true);
1194
+ return;
1195
+ }
1039
1196
  e.preventDefault();
1040
1197
  this.#handleInput(e.inputType, e.data);
1041
1198
  }),
@@ -1045,6 +1202,7 @@ var Editor = class {
1045
1202
  }),
1046
1203
  addEventListener(contentEl, "compositionstart", (e) => {
1047
1204
  if (!targetIsContentElement(e)) return;
1205
+ this.#cancelEditPrediction(true);
1048
1206
  this.#isComposing = true;
1049
1207
  this.#shouldIgnoreSelectionChange = true;
1050
1208
  }, { passive: true }),
@@ -1106,7 +1264,7 @@ var Editor = class {
1106
1264
  return;
1107
1265
  }
1108
1266
  if (e.pointerType !== "mouse") return;
1109
- const textDocument = this.#textDocument;
1267
+ const textDocument = this.#editSession?.document;
1110
1268
  const lineIndex = resolveEditableLine(gutterRow);
1111
1269
  if (lineIndex === void 0 || textDocument === void 0) return;
1112
1270
  this.#canMountSelectionAction = true;
@@ -1119,7 +1277,7 @@ var Editor = class {
1119
1277
  this.#focus();
1120
1278
  this.#replaceSelectEventListeners([addEventListener(document, "mousemove", (e) => {
1121
1279
  if (!this.#isGutterMouseDown) return;
1122
- const textDocument = this.#textDocument;
1280
+ const textDocument = this.#editSession?.document;
1123
1281
  const lineIndex = resolveEditableLine(resolveGutterTarget(e.composedPath()[0], true));
1124
1282
  if (lineIndex === void 0 || textDocument === void 0) return;
1125
1283
  const anchorLine = this.#selectionStart?.start.line ?? lineIndex;
@@ -1149,14 +1307,14 @@ var Editor = class {
1149
1307
  if (Array.isArray(selectionTexts) && selectionTexts.length === selectionCount) text = selectionTexts;
1150
1308
  } catch {}
1151
1309
  }
1152
- const textDocument = this.#textDocument;
1153
- if (textDocument !== void 0) this.#replaceSelectionText(Array.isArray(text) ? text.map((t) => textDocument.normalizeEol(t)) : textDocument.normalizeEol(text), void 0, true, "document");
1310
+ const textDocument = this.#editSession?.document;
1311
+ if (textDocument != null) this.#replaceSelectionText(Array.isArray(text) ? text.map((t) => textDocument.normalizeEol(t)) : textDocument.normalizeEol(text), void 0, true, "document");
1154
1312
  }
1155
1313
  };
1156
1314
  #computeContentOffset(contentEl) {
1157
1315
  if (this.#isDiff && this.#diffSyle === "split" && this.#isWrap) {
1158
1316
  this.#contentOffset = {
1159
- top: contentEl.offsetTop,
1317
+ top: contentEl.offsetTop - this.#metrics.paddingTop,
1160
1318
  left: contentEl.offsetLeft - this.#getGutterWidth()
1161
1319
  };
1162
1320
  if (this.#options.__debug === true) console.log("[diffs/editor] content offset:", this.#contentOffset);
@@ -1166,8 +1324,8 @@ var Editor = class {
1166
1324
  if (this.#isDiff && this.#diffSyle === "split" && this.#isWrap) return this.#contentOffset;
1167
1325
  }
1168
1326
  #runCommand(command) {
1169
- const textDocument = this.#textDocument;
1170
- if (textDocument === void 0) return;
1327
+ const textDocument = this.#editSession?.document;
1328
+ if (textDocument == null) return;
1171
1329
  switch (command) {
1172
1330
  case "openSearchPanel":
1173
1331
  this.#openSearchPanel("find");
@@ -1367,8 +1525,8 @@ var Editor = class {
1367
1525
  }
1368
1526
  }
1369
1527
  #applyHistoryChange(command) {
1370
- const textDocument = this.#textDocument;
1371
- if (textDocument === void 0) return;
1528
+ const textDocument = this.#editSession?.document;
1529
+ if (textDocument == null) return;
1372
1530
  if (command === "undo" && !textDocument.canUndo || command === "redo" && !textDocument.canRedo) return;
1373
1531
  const selections = this.#selections;
1374
1532
  const selectionOffsets = selections?.map((selection) => [textDocument.offsetAt(selection.start), textDocument.offsetAt(selection.end)]);
@@ -1376,11 +1534,12 @@ var Editor = class {
1376
1534
  if (result === void 0) return;
1377
1535
  const [change, recordedSelections, lineAnnotations, selectionEdits] = result;
1378
1536
  const nextSelections = recordedSelections ?? (selections !== void 0 && selectionOffsets !== void 0 && selectionEdits !== void 0 ? remapSelectionsAfterEdits(textDocument, selections, selectionOffsets, selectionEdits) : void 0);
1379
- this.#applyChange(change, nextSelections, lineAnnotations);
1537
+ const replayedLineAnnotations = lineAnnotations ?? (this.#lineAnnotations != null ? applyDocumentChangeToLineAnnotations(change, this.#lineAnnotations) : void 0);
1538
+ this.#applyChange(change, nextSelections, replayedLineAnnotations);
1380
1539
  }
1381
1540
  /** Applies one undoable command batch and records its resulting selections. */
1382
1541
  #applyCommandEdits(edits, resolveNextSelections) {
1383
- const textDocument = this.#textDocument;
1542
+ const textDocument = this.#editSession?.document;
1384
1543
  const selections = this.#selections;
1385
1544
  if (textDocument === void 0 || selections === void 0) return;
1386
1545
  const remapSelections = resolveNextSelections === void 0;
@@ -1405,7 +1564,7 @@ var Editor = class {
1405
1564
  }
1406
1565
  /** Copies selected line blocks and keeps the selection in the requested copy. */
1407
1566
  #copySelectedLines(direction) {
1408
- const textDocument = this.#textDocument;
1567
+ const textDocument = this.#editSession?.document;
1409
1568
  const selections = this.#selections;
1410
1569
  if (textDocument === void 0 || selections === void 0) return;
1411
1570
  const blocks = getSelectedLineBlocks(selections);
@@ -1498,7 +1657,7 @@ var Editor = class {
1498
1657
  }
1499
1658
  /** Inserts an indented blank line after each selection's final line. */
1500
1659
  #insertBlankLine() {
1501
- const textDocument = this.#textDocument;
1660
+ const textDocument = this.#editSession?.document;
1502
1661
  const selections = this.#selections;
1503
1662
  if (textDocument === void 0 || selections === void 0) return;
1504
1663
  const selectionLines = selections.map((selection) => getCaretPosition(selection).line);
@@ -1539,7 +1698,7 @@ var Editor = class {
1539
1698
  this.#applyCommandEdits(edits, () => nextSelections);
1540
1699
  }
1541
1700
  #moveSelectedLines(direction) {
1542
- const textDocument = this.#textDocument;
1701
+ const textDocument = this.#editSession?.document;
1543
1702
  const selections = this.#selections;
1544
1703
  if (textDocument === void 0 || selections === void 0) return;
1545
1704
  const blocks = getSelectedLineBlocks(selections);
@@ -1616,6 +1775,7 @@ var Editor = class {
1616
1775
  }
1617
1776
  this.#markerRenderer?.removePopover();
1618
1777
  this.#computeContentOffset(this.#contentElement);
1778
+ this.#renderCarets();
1619
1779
  };
1620
1780
  #remeasureMetricsOnFontLoad() {
1621
1781
  if (this.#fontRemeasureScheduled) return;
@@ -1629,19 +1789,21 @@ var Editor = class {
1629
1789
  this.#resetCache();
1630
1790
  this.#wrapLineOffsetsCache.clear();
1631
1791
  if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
1792
+ this.#renderCarets();
1632
1793
  this.#markerRenderer?.removePopover();
1633
1794
  });
1634
1795
  }
1635
1796
  #rerender(change, newLineAnnotations, renderRange = this.#renderRange, shouldUpdateBuffer) {
1636
1797
  const tokenizer = this.#tokenizer;
1637
1798
  const fileInstance = this.#fileInstance;
1638
- const textDocument = this.#textDocument;
1799
+ const applyDocumentChange = this.#applyDocumentChange;
1800
+ const textDocument = this.#editSession?.document;
1639
1801
  const gutterEl = this.#gutterElement;
1640
1802
  const contentEl = this.#contentElement;
1641
- if (tokenizer === void 0 || fileInstance === void 0 || textDocument === void 0 || contentEl === void 0) return;
1803
+ if (tokenizer == null || fileInstance == null || applyDocumentChange == null || textDocument == null || contentEl == null) return;
1642
1804
  tokenizer.stopBackgroundTokenize();
1643
1805
  const t = performance.now();
1644
- const dirtyLines = tokenizer.tokenize(change, renderRange, !this.#isDiff && fileInstance.getLinePosition !== void 0);
1806
+ const dirtyLines = tokenizer.tokenize(change, renderRange, !this.#isDiff && this.#virtualizedInstance != null);
1645
1807
  const t2 = performance.now();
1646
1808
  if (dirtyLines.size > 0) {
1647
1809
  const children = contentEl.children;
@@ -1720,19 +1882,20 @@ var Editor = class {
1720
1882
  shouldRefreshDiffsView: this.#isDiff && !didLineCountChange,
1721
1883
  lineCountChangeInFlight: didLineCountChange
1722
1884
  });
1723
- if (didLineCountChange) fileInstance.applyDocumentChange(textDocument, newLineAnnotations, shouldUpdateBuffer);
1885
+ if (didLineCountChange) applyDocumentChange(textDocument, newLineAnnotations, shouldUpdateBuffer);
1724
1886
  if (didLineCountChange || this.#isDiff && this.#diffSyle === "unified") this.#resetCache();
1725
- if (newLineAnnotations !== void 0) {
1887
+ if (newLineAnnotations != null) {
1726
1888
  this.#lineAnnotations = newLineAnnotations;
1727
- renderLineAnnotations(newLineAnnotations, contentEl, gutterEl);
1889
+ if (!this.#isDiff || !didLineCountChange) renderLineAnnotations(newLineAnnotations, contentEl, gutterEl, fileInstance.getAnnotationSlotName);
1728
1890
  }
1891
+ this.#renderCarets();
1729
1892
  if (this.#options.__debug === true) console.log(`[diffs/editor] re-render in: ${round(performance.now() - t2)}ms,`, `tokenize in: ${round(t2 - t)}ms (${dirtyLines.size} dirty lines)`);
1730
1893
  }
1731
1894
  #handleInput(inputType, data) {
1732
1895
  switch (inputType) {
1733
1896
  case "insertText": {
1734
1897
  const text = data ?? "";
1735
- const textDocument = this.#textDocument;
1898
+ const textDocument = this.#editSession?.document;
1736
1899
  const selections = this.#selections;
1737
1900
  const autoSurroundTexts = textDocument !== void 0 && selections !== void 0 ? getAutoSurroundReplacementTexts(textDocument, selections, text, this.#options.autoSurround) : void 0;
1738
1901
  this.#replaceSelectionText(autoSurroundTexts ?? text);
@@ -1741,7 +1904,7 @@ var Editor = class {
1741
1904
  case "insertCompositionText": break;
1742
1905
  case "insertLineBreak":
1743
1906
  case "insertParagraph":
1744
- this.#replaceSelectionText(this.#textDocument?.eol ?? "\n");
1907
+ this.#replaceSelectionText(this.#editSession?.document?.eol ?? "\n");
1745
1908
  break;
1746
1909
  case "deleteContentBackward":
1747
1910
  this.#deleteSelectionText();
@@ -1833,7 +1996,7 @@ var Editor = class {
1833
1996
  #getFirstVisibleLineNumber(offset = 0) {
1834
1997
  const contentElement = this.#contentElement;
1835
1998
  const fileContainer = this.#fileContainer;
1836
- if (this.#textDocument == null || contentElement == null || fileContainer == null) return;
1999
+ if (this.#editSession?.document == null || contentElement == null || fileContainer == null) return;
1837
2000
  const viewport = this.#getScrollViewport();
1838
2001
  if (viewport === void 0) return;
1839
2002
  let viewportTop;
@@ -1950,7 +2113,7 @@ var Editor = class {
1950
2113
  this.#scrollingToLineFixed = false;
1951
2114
  this.#scrollingToLineNoFocus = false;
1952
2115
  } else {
1953
- const modelLinePosition = this.#fileInstance?.getLinePosition?.(line + 1);
2116
+ const modelLinePosition = this.#virtualizedInstance?.getLinePosition?.(line + 1);
1954
2117
  if (modelLinePosition !== void 0) {
1955
2118
  virtualCaret.style.top = modelLinePosition.top + "px";
1956
2119
  this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
@@ -2102,7 +2265,7 @@ var Editor = class {
2102
2265
  #updateAltColumnSelections() {
2103
2266
  const drag = this.#altColumnDrag;
2104
2267
  const selectionStart = this.#selectionStart;
2105
- const textDocument = this.#textDocument;
2268
+ const textDocument = this.#editSession?.document;
2106
2269
  if (drag === void 0 || drag.focusLine === void 0 || selectionStart === void 0 || textDocument === void 0) return false;
2107
2270
  const anchor = selectionStart.direction === -1 ? selectionStart.end : selectionStart.start;
2108
2271
  const scrollLeft = this.#contentElement?.parentElement?.scrollLeft ?? 0;
@@ -2141,8 +2304,451 @@ var Editor = class {
2141
2304
  this.#updateSelections(selections);
2142
2305
  return true;
2143
2306
  }
2307
+ #removeRenderedEditPrediction() {
2308
+ for (const [key, element] of this.#overlayElements ?? []) if (key.startsWith("editPrediction")) {
2309
+ element.remove();
2310
+ this.#overlayElements?.delete(key);
2311
+ }
2312
+ }
2313
+ #composeEditPredictionGroup(textDocument, edits, startIndex) {
2314
+ const firstEdit = edits[startIndex];
2315
+ let endIndex = startIndex;
2316
+ let endLine = firstEdit.range.end.line;
2317
+ while (endIndex + 1 < edits.length && edits[endIndex + 1].range.start.line <= endLine) {
2318
+ endIndex++;
2319
+ endLine = Math.max(endLine, edits[endIndex].range.end.line);
2320
+ }
2321
+ if (endIndex === startIndex && (firstEdit.newText.length === 0 || comparePosition(firstEdit.range.start, firstEdit.range.end) === 0)) return {
2322
+ edit: firstEdit,
2323
+ endIndex
2324
+ };
2325
+ const start = firstEdit.range.start;
2326
+ const end = {
2327
+ line: endLine,
2328
+ character: textDocument.getLineLength(endLine)
2329
+ };
2330
+ const parts = [];
2331
+ let consumed = textDocument.offsetAt(start);
2332
+ for (let index = startIndex; index <= endIndex; index++) {
2333
+ const edit = edits[index];
2334
+ const editStart = textDocument.offsetAt(edit.range.start);
2335
+ const editEnd = textDocument.offsetAt(edit.range.end);
2336
+ parts.push(textDocument.getTextSlice(consumed, editStart), edit.newText);
2337
+ consumed = editEnd;
2338
+ }
2339
+ parts.push(textDocument.getTextSlice(consumed, textDocument.offsetAt(end)));
2340
+ return {
2341
+ edit: {
2342
+ range: {
2343
+ start,
2344
+ end
2345
+ },
2346
+ newText: parts.join("")
2347
+ },
2348
+ endIndex
2349
+ };
2350
+ }
2351
+ #getEditPredictionGroups() {
2352
+ const prediction = this.#editPrediction;
2353
+ const textDocument = this.#editSession?.document;
2354
+ if (prediction == null || textDocument == null || prediction.document !== textDocument || prediction.version !== textDocument.version || this.#options.editPrediction?.mode === "subtle" && !this.#editPredictionRevealed) return;
2355
+ const { edits } = prediction.response;
2356
+ const groups = [];
2357
+ let allVisible = edits.length > 0;
2358
+ for (let startIndex = 0; startIndex < edits.length;) {
2359
+ const { edit, endIndex } = this.#composeEditPredictionGroup(textDocument, edits, startIndex);
2360
+ for (let line = edit.range.start.line; allVisible && line <= edit.range.end.line; line++) allVisible = this.#isLineVisible(line);
2361
+ groups.push({
2362
+ edit,
2363
+ startIndex,
2364
+ endIndex
2365
+ });
2366
+ startIndex = endIndex + 1;
2367
+ }
2368
+ return {
2369
+ groups,
2370
+ allVisible
2371
+ };
2372
+ }
2373
+ #fillGhostTextElement(element, newText, anchorLeft, lineLeft, insertionSuffix) {
2374
+ if (this.#isWrap) {
2375
+ element.dataset.wrap = "";
2376
+ element.style.width = `calc(100cqw - ${lineLeft}px)`;
2377
+ } else {
2378
+ delete element.dataset.wrap;
2379
+ element.style.width = "max-content";
2380
+ }
2381
+ const lines = newText.split(/\r\n|\r|\n/);
2382
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
2383
+ const lineText = lines[lineIndex];
2384
+ const suffix = lineIndex === lines.length - 1 ? insertionSuffix : void 0;
2385
+ const isEmpty = lineText.length === 0 && suffix === void 0;
2386
+ const line = h("span", {
2387
+ dataset: isEmpty ? ["editPredictionLine", "empty"] : "editPredictionLine",
2388
+ textContent: isEmpty ? "​" : lineText
2389
+ }, element);
2390
+ if (suffix !== void 0) h("span", { dataset: "editPredictionSuffix" }, line).append(suffix);
2391
+ if (lineIndex === 0 && anchorLeft !== lineLeft) line.style.textIndent = `${anchorLeft - lineLeft}px`;
2392
+ }
2393
+ }
2394
+ #cloneInsertionSuffix(group) {
2395
+ const prediction = this.#editPrediction;
2396
+ const textDocument = this.#editSession?.document;
2397
+ if (prediction == null || textDocument == null) return;
2398
+ const { edit, startIndex, endIndex } = group;
2399
+ const { start, end } = edit.range;
2400
+ if (!(comparePosition(start, end) === 0 && start.character < textDocument.getLineLength(start.line)) || prediction.response.edits[startIndex - 1]?.range.end.line === start.line || prediction.response.edits[endIndex + 1]?.range.start.line === start.line) return;
2401
+ const sourceLine = this.#getLineElement(start.line);
2402
+ if (sourceLine === void 0) return document.createTextNode(textDocument.getLineText(start.line).slice(start.character));
2403
+ const [suffixNode, suffixOffset] = getSelectionAnchor(sourceLine, start.character);
2404
+ const suffixRange = document.createRange();
2405
+ suffixRange.selectNodeContents(sourceLine);
2406
+ suffixRange.setStart(suffixNode, clampDomOffset(suffixNode, suffixOffset));
2407
+ const suffix = suffixRange.cloneContents();
2408
+ if (suffix.firstChild?.textContent === "") suffix.firstChild.remove();
2409
+ return suffix;
2410
+ }
2411
+ #measureGhostTextRows(group) {
2412
+ const { edit } = group;
2413
+ const { start } = edit.range;
2414
+ const [anchorLeft, anchorWrapLine] = this.#getCharX(start.line, start.character);
2415
+ let anchorVisualLines = 1;
2416
+ let ghostVisualLines = edit.newText.split(/\r\n|\r|\n/).length;
2417
+ const overlayElement = this.#overlayElement;
2418
+ if (this.#isWrap && overlayElement != null) {
2419
+ anchorVisualLines = Math.max(1, this.#wrapLineTextOrWholeLine(start.line).length - 1);
2420
+ const probe = h("span", {
2421
+ dataset: "editPrediction",
2422
+ style: { visibility: "hidden" }
2423
+ }, overlayElement);
2424
+ this.#fillGhostTextElement(probe, edit.newText, anchorLeft, this.#getCharX(start.line, 0)[0], this.#cloneInsertionSuffix(group));
2425
+ const { width, height } = probe.getBoundingClientRect();
2426
+ probe.remove();
2427
+ const { lineHeight } = this.#metrics;
2428
+ if (width > 0 && height > 0 && lineHeight > 0) ghostVisualLines = Math.round(height / lineHeight);
2429
+ }
2430
+ return Math.max(0, ghostVisualLines - (anchorVisualLines - anchorWrapLine));
2431
+ }
2432
+ #syncEditPredictionSpacers(notifyComponent = true) {
2433
+ const nextSpacers = /* @__PURE__ */ new Map();
2434
+ const ghostTextRows = /* @__PURE__ */ new Map();
2435
+ const previousRows = this.#ghostTextRows;
2436
+ let rowsChanged = false;
2437
+ const contentElement = this.#contentElement;
2438
+ const composed = this.#getEditPredictionGroups();
2439
+ if (contentElement != null && composed != null && composed.allVisible) {
2440
+ for (const group of composed.groups) {
2441
+ const { edit } = group;
2442
+ if (edit.newText.length === 0) continue;
2443
+ const count = this.#measureGhostTextRows(group);
2444
+ if (count > (ghostTextRows.get(edit.range.start.line) ?? 0)) ghostTextRows.set(edit.range.start.line, count);
2445
+ }
2446
+ if (ghostTextRows.size > 0) {
2447
+ let rowIndexes;
2448
+ const startingLine = this.#renderRange?.startingLine ?? 0;
2449
+ for (const [line, count] of ghostTextRows) {
2450
+ rowsChanged ||= previousRows.get(line) !== count;
2451
+ const lineElement = this.#getLineElement(line);
2452
+ if (lineElement === void 0) continue;
2453
+ let rowIndex = line - startingLine;
2454
+ if (contentElement.children[rowIndex] !== lineElement) {
2455
+ if (rowIndexes === void 0) {
2456
+ rowIndexes = /* @__PURE__ */ new Map();
2457
+ for (let index = 0; index < contentElement.children.length; index++) rowIndexes.set(contentElement.children[index], index);
2458
+ }
2459
+ rowIndex = rowIndexes.get(lineElement) ?? -1;
2460
+ }
2461
+ if (rowIndex < 0) continue;
2462
+ nextSpacers.set(lineElement, count);
2463
+ const gutterRow = this.#gutterElement?.children[rowIndex];
2464
+ if (gutterRow instanceof HTMLElement) nextSpacers.set(gutterRow, count);
2465
+ const partner = this.#deletionsColumnPartner(rowIndex);
2466
+ if (partner != null) {
2467
+ nextSpacers.set(partner.content, (nextSpacers.get(partner.content) ?? 0) + count);
2468
+ nextSpacers.set(partner.gutter, (nextSpacers.get(partner.gutter) ?? 0) + count);
2469
+ }
2470
+ }
2471
+ }
2472
+ }
2473
+ let changed = false;
2474
+ for (const [element, count] of this.#editPredictionSpacers) {
2475
+ if (nextSpacers.get(element) === count) continue;
2476
+ delete element.dataset.editPredictionSpacer;
2477
+ element.style.removeProperty("--diffs-edit-prediction-spacer-height");
2478
+ changed = true;
2479
+ }
2480
+ for (const [element, count] of nextSpacers) {
2481
+ if (this.#editPredictionSpacers.get(element) === count) continue;
2482
+ element.dataset.editPredictionSpacer = "";
2483
+ element.style.setProperty("--diffs-edit-prediction-spacer-height", `${count}lh`);
2484
+ changed = true;
2485
+ }
2486
+ this.#editPredictionSpacers = nextSpacers;
2487
+ if (rowsChanged || ghostTextRows.size !== previousRows.size) {
2488
+ this.#ghostTextRows = ghostTextRows;
2489
+ if (notifyComponent) this.#virtualizedInstance?.syncGhostTextRows();
2490
+ }
2491
+ if (changed) this.#resetCache();
2492
+ }
2493
+ #deletionsColumnPartner(rowIndex) {
2494
+ const deletionsColumn = this.#deletionsColumn;
2495
+ const contentElement = this.#contentElement;
2496
+ if (deletionsColumn == null || contentElement == null || !this.#isDiff || this.#diffSyle !== "split" || this.#isWrap) return;
2497
+ let track = 0;
2498
+ for (let index = 0; index < rowIndex; index++) track += gridTrackSpan(contentElement.children[index]);
2499
+ const { content, gutter } = deletionsColumn;
2500
+ let covered = 0;
2501
+ for (let index = 0; index < content.children.length; index++) {
2502
+ const child = content.children[index];
2503
+ covered += gridTrackSpan(child);
2504
+ if (covered > track) {
2505
+ const gutterChild = gutter.children[index];
2506
+ if (!(child instanceof HTMLElement) || !(gutterChild instanceof HTMLElement)) throw new Error("Editor: deletions column rows and gutter cells are out of step");
2507
+ return {
2508
+ content: child,
2509
+ gutter: gutterChild
2510
+ };
2511
+ }
2512
+ }
2513
+ }
2514
+ #cancelEditPrediction(removeRendered, notifyComponent = true) {
2515
+ if (this.#editPredictionTimer !== void 0) {
2516
+ clearTimeout(this.#editPredictionTimer);
2517
+ this.#editPredictionTimer = void 0;
2518
+ }
2519
+ this.#editPredictionAbortController?.abort();
2520
+ this.#editPredictionAbortController = void 0;
2521
+ this.#editPredictionGeneration++;
2522
+ this.#editPredictionRevealed = false;
2523
+ this.#retryEditPredictionOnRender = false;
2524
+ this.#editPrediction = void 0;
2525
+ this.#syncEditPredictionSpacers(notifyComponent);
2526
+ if (removeRendered) this.#removeRenderedEditPrediction();
2527
+ }
2528
+ #includesEditPredictionPath(path) {
2529
+ const options = this.#options.editPrediction;
2530
+ if (options === void 0) return false;
2531
+ const normalizedPath = path.replaceAll("\\", "/");
2532
+ return (options.include === void 0 || options.include.some((pattern) => matchesEditPredictionPattern(normalizedPath, pattern))) && options.exclude?.some((pattern) => matchesEditPredictionPattern(normalizedPath, pattern)) !== true;
2533
+ }
2534
+ #scheduleEditPrediction() {
2535
+ this.#cancelEditPrediction(true);
2536
+ const selection = this.#selections?.[0];
2537
+ if (this.#options.editPrediction === void 0 || this.#editSession?.document === void 0 || this.#editSession?.fileInfo === void 0 || this.#selections?.length !== 1 || selection === void 0 || !isCollapsedSelection(selection)) return;
2538
+ const document = this.#editSession?.document;
2539
+ const cursorOffset = document.offsetAt(getCaretPosition(selection));
2540
+ this.#editPredictionTimer = setTimeout(() => {
2541
+ this.#editPredictionTimer = void 0;
2542
+ const options = this.#options.editPrediction;
2543
+ const currentSelection = this.#selections?.[0];
2544
+ const path = this.#editSession?.fileInfo?.name;
2545
+ if (options === void 0 || path === void 0 || this.#editSession?.document !== document || this.#selections?.length !== 1 || currentSelection === void 0 || !isCollapsedSelection(currentSelection) || document.offsetAt(getCaretPosition(currentSelection)) !== cursorOffset) return;
2546
+ if (!this.#includesEditPredictionPath(path)) return;
2547
+ const isLineEditable = (line) => this.#isLineInRenderRange(line) && this.#isLineRenderable(line);
2548
+ if (!isLineEditable(getCaretPosition(currentSelection).line)) {
2549
+ this.#retryEditPredictionOnRender = true;
2550
+ return;
2551
+ }
2552
+ const request = buildEditPredictionRequest(path, document, cursorOffset, this.#editPredictionHistory, isLineEditable);
2553
+ if (request === void 0) return;
2554
+ const excerptStartOffset = document.offsetAt({
2555
+ line: request.excerptStartLine,
2556
+ character: 0
2557
+ });
2558
+ const editableStart = excerptStartOffset + request.editableRange.start;
2559
+ const editableEnd = excerptStartOffset + request.editableRange.end;
2560
+ const controller = new AbortController();
2561
+ const generation = ++this.#editPredictionGeneration;
2562
+ this.#editPredictionAbortController = controller;
2563
+ let prediction;
2564
+ try {
2565
+ prediction = options.provider.predict(request, { signal: controller.signal });
2566
+ } catch {
2567
+ this.#editPredictionAbortController = void 0;
2568
+ return;
2569
+ }
2570
+ Promise.resolve(prediction).then((response) => {
2571
+ const selection = this.#selections?.[0];
2572
+ if (controller.signal.aborted || generation !== this.#editPredictionGeneration || this.#editPredictionAbortController !== controller || this.#editSession?.document !== document || document.version !== request.version || this.#selections?.length !== 1 || selection === void 0 || !isCollapsedSelection(selection) || document.offsetAt(getCaretPosition(selection)) !== cursorOffset) return;
2573
+ if (response == null || !Array.isArray(response.edits) || response.edits.length === 0 || response.edits.length > MAX_EDIT_PREDICTION_RESPONSE_EDITS || response.newCursor == null) return;
2574
+ const resolvedEdits = [];
2575
+ let responseBytes = 0;
2576
+ for (const edit of response.edits) {
2577
+ if (edit == null || typeof edit.newText !== "string" || !isValidEditPredictionPosition(document, edit.range?.start) || !isValidEditPredictionPosition(document, edit.range?.end) || comparePosition(edit.range.start, edit.range.end) > 0) return;
2578
+ responseBytes += editPredictionTextEncoder.encode(edit.newText).byteLength;
2579
+ if (responseBytes > MAX_EDIT_PREDICTION_RESPONSE_BYTES) return;
2580
+ const start = document.offsetAt(edit.range.start);
2581
+ const end = document.offsetAt(edit.range.end);
2582
+ const resolvedEdit = document.resolveEdits([edit])[0];
2583
+ if (resolvedEdit.start !== start || resolvedEdit.end !== end) return;
2584
+ resolvedEdits.push(resolvedEdit);
2585
+ }
2586
+ resolvedEdits.sort((left, right) => {
2587
+ const startDelta = left.start - right.start;
2588
+ return startDelta === 0 ? left.end - right.end : startDelta;
2589
+ });
2590
+ for (let index = 0; index < resolvedEdits.length; index++) {
2591
+ const edit = resolvedEdits[index];
2592
+ if (edit.start < editableStart || edit.end > editableEnd || index > 0 && resolvedEdits[index - 1].end > edit.start) return;
2593
+ }
2594
+ const edits = resolvedEdits.filter((edit) => edit.text !== document.getTextSlice(edit.start, edit.end));
2595
+ if (edits.length === 0) return;
2596
+ const firstEditPosition = document.positionAt(edits[0].start);
2597
+ const lastEditPosition = document.positionAt(edits.at(-1).end);
2598
+ const affectedStart = document.offsetAt({
2599
+ line: firstEditPosition.line,
2600
+ character: 0
2601
+ });
2602
+ const affectedEnd = document.offsetAt({
2603
+ line: lastEditPosition.line,
2604
+ character: document.getLineLength(lastEditPosition.line)
2605
+ });
2606
+ const predictedParts = [];
2607
+ let consumed = affectedStart;
2608
+ for (const edit of edits) {
2609
+ predictedParts.push(document.getTextSlice(consumed, edit.start), edit.text);
2610
+ consumed = edit.end;
2611
+ }
2612
+ predictedParts.push(document.getTextSlice(consumed, affectedEnd));
2613
+ const predictedLines = predictedParts.join("").split(/\r\n|\r|\n/);
2614
+ const affectedEndLine = firstEditPosition.line + predictedLines.length - 1;
2615
+ const lineDelta = predictedLines.length - (lastEditPosition.line - firstEditPosition.line + 1);
2616
+ const newCursor = response.newCursor;
2617
+ if (!Number.isInteger(newCursor.line) || !Number.isInteger(newCursor.character) || newCursor.line < 0 || newCursor.character < 0) return;
2618
+ if (newCursor.line >= firstEditPosition.line && newCursor.line <= affectedEndLine) {
2619
+ const line = predictedLines[newCursor.line - firstEditPosition.line];
2620
+ if (newCursor.character > line.length || splitsSurrogatePair(line, newCursor.character)) return;
2621
+ } else {
2622
+ const originalLine = newCursor.line < firstEditPosition.line ? newCursor.line : newCursor.line - lineDelta;
2623
+ if (originalLine < 0 || originalLine >= document.lineCount || newCursor.character > document.getLineLength(originalLine)) return;
2624
+ const originalOffset = document.offsetAt({
2625
+ line: originalLine,
2626
+ character: newCursor.character
2627
+ });
2628
+ if (splitsSurrogatePair(document.charAt(originalOffset - 1) + document.charAt(originalOffset), 1)) return;
2629
+ }
2630
+ this.#editPrediction = {
2631
+ document,
2632
+ version: request.version,
2633
+ cursorOffset,
2634
+ rendered: false,
2635
+ response: {
2636
+ edits: edits.map((edit) => ({
2637
+ range: {
2638
+ start: document.positionAt(edit.start),
2639
+ end: document.positionAt(edit.end)
2640
+ },
2641
+ newText: edit.text
2642
+ })),
2643
+ newCursor: { ...newCursor }
2644
+ }
2645
+ };
2646
+ this.#updateSelections(this.#selections);
2647
+ }).catch(() => {}).finally(() => {
2648
+ if (this.#editPredictionAbortController === controller) this.#editPredictionAbortController = void 0;
2649
+ });
2650
+ }, EDIT_PREDICTION_DEBOUNCE_MS);
2651
+ }
2652
+ #recordEditPredictionHistory(change, source) {
2653
+ const textDocument = this.#editSession?.document;
2654
+ const path = this.#editSession?.fileInfo?.name;
2655
+ const transaction = getTextDocumentChangeTransaction(change);
2656
+ if (textDocument === void 0 || path === void 0 || transaction === void 0 || !this.#includesEditPredictionPath(path)) return;
2657
+ this.#editPredictionHistory = recordEditPrediction(this.#editPredictionHistory, path, textDocument, transaction, source);
2658
+ }
2659
+ #acceptEditPrediction() {
2660
+ const prediction = this.#editPrediction;
2661
+ const textDocument = this.#editSession?.document;
2662
+ const selection = this.#selections?.[0];
2663
+ if (prediction === void 0 || !prediction.rendered || textDocument === void 0 || prediction.document !== textDocument || prediction.version !== textDocument.version || this.#selections?.length !== 1 || selection === void 0 || !isCollapsedSelection(selection) || textDocument.offsetAt(getCaretPosition(selection)) !== prediction.cursorOffset) return;
2664
+ const { edits, newCursor } = prediction.response;
2665
+ this.#cancelEditPrediction(true);
2666
+ const change = textDocument.applyEdits(edits.map((edit) => ({
2667
+ range: {
2668
+ start: { ...edit.range.start },
2669
+ end: { ...edit.range.end }
2670
+ },
2671
+ newText: edit.newText
2672
+ })), true, this.#selections, void 0, true);
2673
+ if (change === void 0) {
2674
+ this.#scheduleEditPrediction();
2675
+ return;
2676
+ }
2677
+ const cursor = textDocument.normalizePosition(newCursor);
2678
+ const nextSelections = [{
2679
+ start: cursor,
2680
+ end: cursor,
2681
+ direction: 0
2682
+ }];
2683
+ textDocument.setLastUndoSelectionsAfter(nextSelections);
2684
+ this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change), { editSource: "prediction" });
2685
+ }
2686
+ #renderEditPrediction(renderCtx) {
2687
+ const prediction = this.#editPrediction;
2688
+ const textDocument = this.#editSession?.document;
2689
+ if (prediction !== void 0) prediction.rendered = false;
2690
+ const composed = this.#getEditPredictionGroups();
2691
+ if (prediction == null || textDocument == null || composed == null || !composed.allVisible) return;
2692
+ for (const group of composed.groups) {
2693
+ const { edit, endIndex } = group;
2694
+ const { start, end } = edit.range;
2695
+ const isDeletion = edit.newText.length === 0;
2696
+ const isReplacement = comparePosition(start, end) !== 0;
2697
+ const lineLength = textDocument.getLineLength(start.line);
2698
+ const isMidLineInsertion = !isReplacement && start.character < lineLength;
2699
+ if (isReplacement) this.#renderSelection(renderCtx, isDeletion ? "editPredictionDeletion" : "editPredictionReplacement", {
2700
+ start,
2701
+ end
2702
+ });
2703
+ else if (isMidLineInsertion) this.#renderSelection(renderCtx, "editPredictionInsertion", {
2704
+ start,
2705
+ end: {
2706
+ line: start.line,
2707
+ character: lineLength
2708
+ }
2709
+ });
2710
+ if (isDeletion) continue;
2711
+ const [anchorLeft, anchorWrapLine] = this.#getCharX(start.line, start.character);
2712
+ const lineLeft = this.#getCharX(start.line, 0)[0];
2713
+ const anchorTop = this.#getLineY(start.line) + anchorWrapLine * this.#metrics.lineHeight;
2714
+ const key = `editPrediction-${endIndex}`;
2715
+ let element = this.#overlayElements?.get(key);
2716
+ if (element !== void 0) {
2717
+ this.#overlayElements?.delete(key);
2718
+ element.replaceChildren();
2719
+ } else element = h("span", {
2720
+ ariaHidden: "true",
2721
+ contentEditable: "false",
2722
+ dataset: "editPrediction"
2723
+ }, renderCtx.fragment);
2724
+ if (isReplacement) {
2725
+ element.dataset.replacement = "";
2726
+ const lineElement = this.#getLineElement(start.line);
2727
+ if (lineElement !== void 0) element.style.setProperty("--diffs-edit-prediction-bg", getComputedStyle(lineElement).getPropertyValue("--diffs-line-bg"));
2728
+ } else {
2729
+ delete element.dataset.replacement;
2730
+ element.style.removeProperty("--diffs-edit-prediction-bg");
2731
+ }
2732
+ this.#fillGhostTextElement(element, edit.newText, anchorLeft, lineLeft, this.#cloneInsertionSuffix(group));
2733
+ element.style.transform = `translateX(${lineLeft}px) translateY(${anchorTop}px)`;
2734
+ renderCtx.elements.set(key, element);
2735
+ }
2736
+ prediction.rendered = true;
2737
+ }
2144
2738
  #updateSelections(selections) {
2145
2739
  this.__postponeBgTokenizeToNextFrame();
2740
+ const previousSelections = this.#selections;
2741
+ let selectionsChanged = previousSelections?.length !== selections.length;
2742
+ if (!selectionsChanged && previousSelections !== void 0) for (let i = 0; i < selections.length; i++) {
2743
+ const previous = previousSelections[i];
2744
+ const next = selections[i];
2745
+ if (previous.direction !== next.direction || comparePosition(previous.start, next.start) !== 0 || comparePosition(previous.end, next.end) !== 0) {
2746
+ selectionsChanged = true;
2747
+ break;
2748
+ }
2749
+ }
2750
+ if (selectionsChanged) this.#cancelEditPrediction(true);
2751
+ this.#syncEditPredictionSpacers();
2146
2752
  this.#primaryCaretElement = void 0;
2147
2753
  this.#setEditorActiveLineSafe(null);
2148
2754
  if (selections.length === 0 && this.#matches === void 0 && this.#markerRenderer === void 0) {
@@ -2151,6 +2757,7 @@ var Editor = class {
2151
2757
  this.#overlayElements?.clear();
2152
2758
  this.#selectionAction?.cleanup();
2153
2759
  this.#selectionAction = void 0;
2760
+ if (selectionsChanged) this.#scheduleEditPrediction();
2154
2761
  return;
2155
2762
  }
2156
2763
  const fragment = document.createDocumentFragment();
@@ -2169,14 +2776,14 @@ var Editor = class {
2169
2776
  if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, "selection", selection);
2170
2777
  this.#renderCaret(renderCtx, selection, selection === primarySelection);
2171
2778
  }
2172
- const bracketMatchRanges = this.#options.matchBrackets !== false && this.#textDocument !== void 0 && this.#tokenizer !== void 0 && isCollapsedSelection(primarySelection) ? findBracketMatchRanges(this.#textDocument, this.#tokenizer, primarySelection.start) : void 0;
2779
+ const bracketMatchRanges = this.#options.matchBrackets !== false && this.#editSession?.document != null && this.#tokenizer !== void 0 && isCollapsedSelection(primarySelection) ? findBracketMatchRanges(this.#editSession?.document, this.#tokenizer, primarySelection.start) : void 0;
2173
2780
  if (bracketMatchRanges !== void 0) for (const range of bracketMatchRanges) this.#renderSelection(renderCtx, "bracketMatch", range);
2174
2781
  }
2175
- const textDocument = this.#textDocument;
2782
+ const textDocument = this.#editSession?.document;
2176
2783
  if (this.#matches !== void 0 && textDocument !== void 0) {
2177
2784
  const matches = this.#matches;
2178
2785
  const renderRange = this.#renderRange;
2179
- const renderedLineRanges = renderRange !== void 0 && Number.isFinite(renderRange.totalLines) || this.#isDiff && this.#fileInstance?.options.expandUnchanged !== true ? this.#getRenderedEditableLineRanges() : void 0;
2786
+ const renderedLineRanges = renderRange != null && Number.isFinite(renderRange.totalLines) || this.#isDiff && this.#fileDiffInstance?.options.expandUnchanged !== true ? this.#getRenderedEditableLineRanges() : void 0;
2180
2787
  const primarySelection = this.#selections?.at(-1);
2181
2788
  const primaryStartOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.start) : -1;
2182
2789
  const primaryEndOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.end) : -1;
@@ -2221,14 +2828,70 @@ var Editor = class {
2221
2828
  }
2222
2829
  }
2223
2830
  if (this.#markerRenderer !== void 0 && textDocument !== void 0) for (const marker of this.#markerRenderer.markers) this.#renderSelection(renderCtx, "marker", marker, markerSeverityDatasetKey(marker.severity));
2831
+ this.#renderEditPrediction(renderCtx);
2224
2832
  this.#overlayElement?.appendChild(fragment);
2225
2833
  this.#overlayElements?.forEach((el) => el.remove());
2226
2834
  this.#overlayElements?.clear();
2227
2835
  this.#overlayElements = renderCtx.elements;
2228
2836
  this.#updateSelectionActionPopover();
2837
+ if (selectionsChanged) this.#scheduleEditPrediction();
2229
2838
  }
2230
- #renderSelection(renderCtx, type, range, extraDataset) {
2231
- if (this.#textDocument === void 0) return;
2839
+ #renderCarets() {
2840
+ const carets = this.#carets;
2841
+ const renderCaret = this.#options.renderCaret;
2842
+ const overlayElement = this.#overlayElement;
2843
+ this.#caretHighlightElements?.forEach((element) => element.remove());
2844
+ this.#caretHighlightElements = void 0;
2845
+ if (carets === void 0 || renderCaret === void 0 || overlayElement === void 0) {
2846
+ this.#caretElements?.forEach((element) => element.remove());
2847
+ this.#caretElements = void 0;
2848
+ return;
2849
+ }
2850
+ const elements = this.#caretElements ??= /* @__PURE__ */ new Map();
2851
+ for (const [trackedCaret, element] of elements) if (!this.#isLineVisible(trackedCaret.caret.focus.line)) {
2852
+ element.remove();
2853
+ elements.delete(trackedCaret);
2854
+ }
2855
+ const fragment = document.createDocumentFragment();
2856
+ const highlightElements = [];
2857
+ for (const trackedCaret of carets) {
2858
+ const { caret } = trackedCaret;
2859
+ const { line, character } = caret.focus;
2860
+ if (trackedCaret.anchorOffset !== trackedCaret.focusOffset) {
2861
+ const highlightContext = {
2862
+ fragment,
2863
+ elements: /* @__PURE__ */ new Map()
2864
+ };
2865
+ this.#renderSelection(highlightContext, "caretHighlight", trackedCaret.anchorOffset < trackedCaret.focusOffset ? {
2866
+ start: caret.anchor,
2867
+ end: caret.focus
2868
+ } : {
2869
+ start: caret.focus,
2870
+ end: caret.anchor
2871
+ }, void 0, caret.focus);
2872
+ for (const highlightElement of highlightContext.elements.values()) {
2873
+ highlightElement.style.setProperty("--diffs-caret-highlight-bg", `color-mix(in srgb, ${caret.metadata.color} 32%, transparent)`);
2874
+ highlightElements.push(highlightElement);
2875
+ }
2876
+ }
2877
+ if (!this.#isLineVisible(line)) continue;
2878
+ let element = elements.get(trackedCaret);
2879
+ if (element === void 0) {
2880
+ element = h("div", {
2881
+ dataset: "remoteCaret",
2882
+ children: [renderCaret(caret)]
2883
+ }, fragment);
2884
+ elements.set(trackedCaret, element);
2885
+ }
2886
+ const [left, wrapLine] = this.#getCharX(line, character);
2887
+ const top = this.#getLineY(line) + wrapLine * this.#metrics.lineHeight;
2888
+ element.style.transform = `translateX(${left}px) translateY(${top}px)`;
2889
+ }
2890
+ this.#caretHighlightElements = highlightElements;
2891
+ overlayElement.appendChild(fragment);
2892
+ }
2893
+ #renderSelection(renderCtx, type, range, extraDataset, connectedCaret) {
2894
+ if (this.#editSession?.document == null) return;
2232
2895
  const { start, end } = range;
2233
2896
  let firstLine = start.line;
2234
2897
  let endLine = end.line + 1;
@@ -2241,12 +2904,18 @@ var Editor = class {
2241
2904
  if (!this.#isLineVisible(line)) continue;
2242
2905
  const isLastLine = line === end.line;
2243
2906
  const startChar = line === start.line ? start.character : 0;
2244
- const endChar = isLastLine ? end.character : this.#textDocument.getLineLength(line);
2907
+ const endChar = isLastLine ? end.character : this.#editSession?.document.getLineLength(line);
2908
+ if (startChar === endChar && (type === "editPredictionDeletion" || type === "editPredictionReplacement")) {
2909
+ if (isLastLine && line !== start.line) continue;
2910
+ const [left, wrapLine] = this.#getCharX(line, startChar);
2911
+ this.#renderSelectionBlock(renderCtx, type, line, wrapLine, left, this.#metrics.ch, extraDataset);
2912
+ continue;
2913
+ }
2245
2914
  if (this.#isWrap) {
2246
2915
  const contentWidth = this.#getContentWidth();
2247
- const lineText = this.#textDocument.getLineText(line);
2916
+ const lineText = this.#editSession?.document.getLineText(line);
2248
2917
  if (2 * this.#metrics.ch + this.#metrics.measureTextWidth(lineText) > contentWidth) {
2249
- this.#renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset);
2918
+ this.#renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset, connectedCaret);
2250
2919
  continue;
2251
2920
  }
2252
2921
  }
@@ -2258,10 +2927,13 @@ var Editor = class {
2258
2927
  if (!isLastLine && type === "selection") paddingEnd = this.#metrics.ch;
2259
2928
  if (startChar === endChar) width = paddingEnd;
2260
2929
  else width = this.#getCharX(line, endChar)[0] - left + paddingEnd;
2261
- this.#renderSelectionBlock(renderCtx, type, line, 0, left, width, extraDataset);
2930
+ this.#renderSelectionBlock(renderCtx, type, line, 0, left, width, extraDataset, {
2931
+ start: connectedCaret?.line === start.line && connectedCaret.character === start.character && line === start.line,
2932
+ end: connectedCaret?.line === end.line && connectedCaret.character === end.character && line === end.line
2933
+ });
2262
2934
  }
2263
2935
  }
2264
- #renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset) {
2936
+ #renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset, connectedCaret) {
2265
2937
  const wrapOffsets = this.#wrapLineTextOrWholeLine(line);
2266
2938
  const segmentCount = wrapOffsets.length - 1;
2267
2939
  const offsetLeft = this.#getGutterWidth() + this.#metrics.ch + (this.#activeContentOffset?.left ?? 0);
@@ -2276,7 +2948,10 @@ var Editor = class {
2276
2948
  let paddingEnd = 0;
2277
2949
  if (!isLastLine && wrapLine === segmentCount - 1 && type === "selection") paddingEnd = this.#metrics.ch;
2278
2950
  const segmentWidth = wrapStartChar === wrapEndChar ? paddingEnd : this.#segmentTextWidth(lineText, segmentStart, wrapEndChar) - segmentStartWidth + paddingEnd;
2279
- this.#renderSelectionBlock(renderCtx, type, line, wrapLine, segmentLeft, segmentWidth, extraDataset);
2951
+ this.#renderSelectionBlock(renderCtx, type, line, wrapLine, segmentLeft, segmentWidth, extraDataset, {
2952
+ start: connectedCaret?.line === line && connectedCaret.character === startChar && wrapStartChar === startChar,
2953
+ end: connectedCaret?.line === line && connectedCaret.character === endChar && wrapEndChar === endChar
2954
+ });
2280
2955
  }
2281
2956
  }
2282
2957
  #segmentTextWidth(lineText, segmentStart, character) {
@@ -2285,13 +2960,13 @@ var Editor = class {
2285
2960
  const asciiColumns = getExpandedAsciiTextColumns(segmentText, this.#metrics.tabSize);
2286
2961
  return asciiColumns !== -1 ? asciiColumns * this.#metrics.ch : this.#metrics.measureTextWidth(segmentText);
2287
2962
  }
2288
- #renderSelectionBlock(renderCtx, type, line, wrapLine, left, width, extraDataset) {
2963
+ #renderSelectionBlock(renderCtx, type, line, wrapLine, left, width, extraDataset, connectedCaretEdge) {
2289
2964
  if (width === 0) return;
2290
2965
  const { ch, lineHeight } = this.#metrics;
2291
2966
  const y = this.#getLineY(line) + wrapLine * lineHeight;
2292
2967
  const cacheKey = `${type}-${line}/${wrapLine}-${left}-${width} ${extraDataset ?? ""}`;
2293
2968
  const overlayEls = this.#overlayElements;
2294
- const rounded = (this.#options.roundedSelection ?? true) && type === "selection";
2969
+ const rounded = (this.#options.roundedSelection ?? true) && (type === "selection" || type === "caretHighlight");
2295
2970
  const addRoundedCorner = (line, wrapLine, left, radius) => {
2296
2971
  const top = this.#getLineY(line) + wrapLine * lineHeight;
2297
2972
  const lineElement = this.#getLineElement(line);
@@ -2321,7 +2996,7 @@ var Editor = class {
2321
2996
  cornerEl.style.cssText = css;
2322
2997
  overlayEls.delete(cacheKey);
2323
2998
  } else cornerEl = h("div", {
2324
- dataset: "selectionRange",
2999
+ dataset: type === "caretHighlight" ? "caretHighlightRange" : "selectionRange",
2325
3000
  style: { cssText: css },
2326
3001
  children: [h("div", { dataset })]
2327
3002
  }, renderCtx.fragment);
@@ -2366,6 +3041,14 @@ var Editor = class {
2366
3041
  dataset.rbl = "";
2367
3042
  dataset.rbr = "";
2368
3043
  }
3044
+ if (connectedCaretEdge?.start === true) {
3045
+ delete dataset.rtl;
3046
+ delete dataset.rbl;
3047
+ }
3048
+ if (connectedCaretEdge?.end === true) {
3049
+ delete dataset.rtr;
3050
+ delete dataset.rbr;
3051
+ }
2369
3052
  };
2370
3053
  let rangeEl = renderCtx.elements.get(cacheKey);
2371
3054
  if (rangeEl !== void 0) {
@@ -2378,6 +3061,10 @@ var Editor = class {
2378
3061
  } else rangeEl = h("div", { dataset: extraDataset ? [type + "Range", extraDataset] : type + "Range" }, renderCtx.fragment);
2379
3062
  rangeEl.style.width = `${width}px`;
2380
3063
  rangeEl.style.transform = `translateX(${left}px) translateY(${y}px)`;
3064
+ if (type === "editPredictionInsertion" || type === "editPredictionReplacement") {
3065
+ const lineElement = this.#getLineElement(line);
3066
+ if (lineElement !== void 0) rangeEl.style.setProperty("--diffs-edit-prediction-bg", getComputedStyle(lineElement).getPropertyValue("--diffs-line-bg"));
3067
+ } else rangeEl.style.removeProperty("--diffs-edit-prediction-bg");
2381
3068
  if (rounded) addRadiusStyle(rangeEl);
2382
3069
  renderCtx.elements.set(cacheKey, rangeEl);
2383
3070
  }
@@ -2400,7 +3087,7 @@ var Editor = class {
2400
3087
  #updateSelectionActionPopover() {
2401
3088
  const primarySelection = this.#selections?.at(-1);
2402
3089
  const overlayElement = this.#overlayElement;
2403
- const textDocument = this.#textDocument;
3090
+ const textDocument = this.#editSession?.document;
2404
3091
  const renderSelectionAction = this.#options.renderSelectionAction;
2405
3092
  const cleanup = () => {
2406
3093
  this.#selectionAction?.cleanup();
@@ -2424,7 +3111,7 @@ var Editor = class {
2424
3111
  return getActiveSelection();
2425
3112
  },
2426
3113
  applyEdits: (edits) => this.applyEdits(edits),
2427
- getSelectionText: () => this.#textDocument?.getText(getActiveSelection()) ?? "",
3114
+ getSelectionText: () => this.#editSession?.document?.getText(getActiveSelection()) ?? "",
2428
3115
  replaceSelectionText: (text) => {
2429
3116
  this.#replaceSelectionText(text, [getActiveSelection()]);
2430
3117
  },
@@ -2494,7 +3181,7 @@ var Editor = class {
2494
3181
  }
2495
3182
  #renderSearchPanel(mode) {
2496
3183
  this.#searchPanel?.cleanup();
2497
- const textDocument = this.#textDocument;
3184
+ const textDocument = this.#editSession?.document;
2498
3185
  const preElement = this.#fileContainer?.shadowRoot?.querySelector("pre");
2499
3186
  const selections = this.#selections;
2500
3187
  if (textDocument === void 0 || preElement == null) return;
@@ -2577,13 +3264,13 @@ var Editor = class {
2577
3264
  this.#retainSearchPanelFocus = false;
2578
3265
  }
2579
3266
  #getSelectionText() {
2580
- const textDocument = this.#textDocument;
3267
+ const textDocument = this.#editSession?.document;
2581
3268
  const selections = this.#selections;
2582
3269
  if (textDocument === void 0 || selections === void 0) return "";
2583
3270
  return getSelectionText(textDocument, selections);
2584
3271
  }
2585
3272
  #getSelectionClipboardTexts() {
2586
- const textDocument = this.#textDocument;
3273
+ const textDocument = this.#editSession?.document;
2587
3274
  const selections = this.#selections;
2588
3275
  if (textDocument === void 0 || selections === void 0) return [];
2589
3276
  return getSelectionClipboardTexts(textDocument, selections);
@@ -2595,7 +3282,7 @@ var Editor = class {
2595
3282
  if (selectionTexts.length > 1) clipboardData.setData(MULTI_SELECTION_CLIPBOARD_TYPE, JSON.stringify(selectionTexts));
2596
3283
  }
2597
3284
  #cutSelectionText() {
2598
- const textDocument = this.#textDocument;
3285
+ const textDocument = this.#editSession?.document;
2599
3286
  const selections = this.#selections;
2600
3287
  if (textDocument === void 0 || selections === void 0 || selections.length === 0) return "";
2601
3288
  if (selections.some((selection) => isCollapsedSelection(selection))) {
@@ -2608,7 +3295,7 @@ var Editor = class {
2608
3295
  return text;
2609
3296
  }
2610
3297
  #applySelectionCutEdits(edits, nextSelectionOffsets) {
2611
- const textDocument = this.#textDocument;
3298
+ const textDocument = this.#editSession?.document;
2612
3299
  const selections = this.#selections;
2613
3300
  if (textDocument === void 0 || selections === void 0 || edits.length === 0) return;
2614
3301
  const change = textDocument.applyResolvedEdits(edits, true, selections, void 0, true);
@@ -2626,7 +3313,7 @@ var Editor = class {
2626
3313
  }
2627
3314
  #replaceSelectionText(text, selections = this.#selections, undoBoundary = false, textOrder = "selection") {
2628
3315
  if (selections === void 0) return;
2629
- const textDocument = this.#textDocument;
3316
+ const textDocument = this.#editSession?.document;
2630
3317
  const primarySelection = selections.at(-1);
2631
3318
  if (textDocument === void 0 || primarySelection === void 0) return;
2632
3319
  const { nextSelections, change } = Array.isArray(text) && text.length === selections.length ? applyTextReplaceToSelections(textDocument, selections, text, this.#lineAnnotations, undoBoundary, textOrder) : applyTextChangeToSelections(textDocument, selections, {
@@ -2638,14 +3325,14 @@ var Editor = class {
2638
3325
  }
2639
3326
  #deleteSelectionText(forward = false) {
2640
3327
  const selections = this.#selections;
2641
- const textDocument = this.#textDocument;
3328
+ const textDocument = this.#editSession?.document;
2642
3329
  if (selections === void 0 || textDocument === void 0) return;
2643
3330
  const { nextSelections, change } = applyDeleteCharacterToSelections(textDocument, selections, forward, this.#lineAnnotations, this.#metrics.tabSize);
2644
3331
  if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2645
3332
  }
2646
3333
  #deleteSoftLineBackward() {
2647
3334
  const selections = this.#selections;
2648
- const textDocument = this.#textDocument;
3335
+ const textDocument = this.#editSession?.document;
2649
3336
  if (selections === void 0 || textDocument === void 0) return;
2650
3337
  const { nextSelections, change } = applyDeleteSoftLineBackwardToSelections(textDocument, selections, this.#isWrap ? (line, character) => {
2651
3338
  const wrapOffsets = this.#wrapLineTextOrWholeLine(line);
@@ -2660,35 +3347,33 @@ var Editor = class {
2660
3347
  }
2661
3348
  #deleteWordBackward() {
2662
3349
  const selections = this.#selections;
2663
- const textDocument = this.#textDocument;
3350
+ const textDocument = this.#editSession?.document;
2664
3351
  if (selections === void 0 || textDocument === void 0) return;
2665
3352
  const { nextSelections, change } = applyDeleteWordBackwardToSelections(textDocument, selections, this.#lineAnnotations);
2666
3353
  if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2667
3354
  }
2668
3355
  #deleteHardLineForward() {
2669
3356
  const selections = this.#selections;
2670
- const textDocument = this.#textDocument;
3357
+ const textDocument = this.#editSession?.document;
2671
3358
  if (selections === void 0 || textDocument === void 0) return;
2672
3359
  const { nextSelections, change } = applyDeleteHardLineForwardToSelections(textDocument, selections, this.#lineAnnotations);
2673
3360
  if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2674
3361
  }
2675
3362
  #insertTranspose() {
2676
3363
  const selections = this.#selections;
2677
- const textDocument = this.#textDocument;
3364
+ const textDocument = this.#editSession?.document;
2678
3365
  if (selections === void 0 || textDocument === void 0) return;
2679
3366
  const { nextSelections, change } = applyTransposeToSelections(textDocument, selections, this.#lineAnnotations);
2680
3367
  if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
2681
3368
  }
2682
3369
  #applyChange(change, newSelections, newLineAnnotations, options) {
2683
- const fileRef = this.getFile();
2684
- const onChange = this.#options.onChange;
2685
- if (fileRef !== void 0 && onChange !== void 0) {
2686
- const lineAnnotations = newLineAnnotations ?? this.#lineAnnotations;
2687
- onChange(fileRef, lineAnnotations, {
2688
- changes: change.changes,
2689
- file: fileRef,
2690
- lineAnnotations
2691
- });
3370
+ if (this.#editPrediction != null) this.#cancelEditPrediction(true);
3371
+ const textDocument = this.#editSession?.document;
3372
+ if (textDocument !== void 0 && this.#carets !== void 0) for (const trackedCaret of this.#carets) {
3373
+ trackedCaret.anchorOffset = remapOffsetThroughEdits(trackedCaret.anchorOffset, change.changes);
3374
+ trackedCaret.focusOffset = remapOffsetThroughEdits(trackedCaret.focusOffset, change.changes);
3375
+ trackedCaret.caret.anchor = textDocument.positionAt(trackedCaret.anchorOffset);
3376
+ trackedCaret.caret.focus = textDocument.positionAt(trackedCaret.focusOffset);
2692
3377
  }
2693
3378
  if (change.lineDelta !== 0 || this.#isWrap) {
2694
3379
  for (const line of this.#lineYCache.keys()) if (line >= change.startLine) this.#lineYCache.delete(line);
@@ -2717,9 +3402,13 @@ var Editor = class {
2717
3402
  }
2718
3403
  }
2719
3404
  this.#rerender(change, newLineAnnotations, renderRange, shouldUpdateBuffer);
3405
+ if (newSelections != null) this.#updateSelections(newSelections);
3406
+ this.#checkpointEditSessionState();
3407
+ this.#recordEditPredictionHistory(change, options?.editSource ?? "user");
3408
+ this.#scheduleEditPrediction();
3409
+ this.#emitChange(change.changes, newLineAnnotations ?? this.#lineAnnotations);
2720
3410
  if (options?.skipSearchRefresh !== true && this.#searchPanel !== void 0 && this.#matches !== void 0) this.#searchPanel.updateMatches({ syncSelection: false });
2721
- if (newSelections !== void 0) {
2722
- this.#updateSelections(newSelections);
3411
+ if (newSelections != null) {
2723
3412
  if (options?.skipFocus !== true) {
2724
3413
  const revealTarget = newSelections.at(-1);
2725
3414
  if (revealTarget !== void 0) this.#revealLineIfCollapsed(getCaretPosition(revealTarget).line);
@@ -2737,11 +3426,17 @@ var Editor = class {
2737
3426
  }
2738
3427
  }
2739
3428
  }
3429
+ #emitChange(changes, lineAnnotations) {
3430
+ const file = this.getFile();
3431
+ const publishChange = this.#publishChange;
3432
+ if (file == null || publishChange == null) return;
3433
+ publishChange(changes, file, lineAnnotations);
3434
+ }
2740
3435
  #applyChangeToLineAnnotations(change) {
2741
- if (this.#lineAnnotations !== void 0) {
3436
+ if (this.#lineAnnotations != null) {
2742
3437
  const nextLineAnnotations = applyDocumentChangeToLineAnnotations(change, this.#lineAnnotations);
2743
- if (nextLineAnnotations !== void 0) {
2744
- this.#textDocument?.setLastUndoLineAnnotations(this.#lineAnnotations, nextLineAnnotations);
3438
+ if (nextLineAnnotations != null) {
3439
+ this.#editSession?.document?.setLastUndoLineAnnotations(this.#lineAnnotations, nextLineAnnotations);
2745
3440
  return nextLineAnnotations;
2746
3441
  }
2747
3442
  }
@@ -2762,11 +3457,15 @@ var Editor = class {
2762
3457
  }
2763
3458
  return ranges;
2764
3459
  }
3460
+ #isLineInRenderRange(line) {
3461
+ const renderRange = this.#renderRange;
3462
+ return renderRange == null || line >= renderRange.startingLine && line < renderRange.startingLine + renderRange.totalLines;
3463
+ }
2765
3464
  #getLineElement(line) {
2766
3465
  let lineElement = this.#lineElementsCache.get(line);
2767
3466
  if (lineElement !== void 0) return lineElement ?? void 0;
2768
3467
  const renderRange = this.#renderRange;
2769
- if (renderRange !== void 0 && (line < renderRange.startingLine || line >= renderRange.startingLine + renderRange.totalLines)) return;
3468
+ if (!this.#isLineInRenderRange(line)) return;
2770
3469
  const contentElement = this.#contentElement;
2771
3470
  if (contentElement === void 0) return;
2772
3471
  if (renderRange !== void 0) {
@@ -2817,7 +3516,7 @@ var Editor = class {
2817
3516
  }
2818
3517
  #getCharX(line, char) {
2819
3518
  if (this.#lastAccessedCharX !== void 0 && this.#lastAccessedCharX[0] === line && this.#lastAccessedCharX[1] === char) return [this.#lastAccessedCharX[2], this.#lastAccessedCharX[3]];
2820
- const lineText = this.#textDocument?.getLineText(line);
3519
+ const lineText = this.#editSession?.document?.getLineText(line);
2821
3520
  const offsetLeft = this.#getGutterWidth() + this.#metrics.ch;
2822
3521
  if (lineText === void 0 || lineText.length === 0 || char <= 0) return [offsetLeft + (this.#activeContentOffset?.left ?? 0), 0];
2823
3522
  const boundedCharacter = snapTextOffsetToUnicodeBoundary(lineText, Math.min(char, lineText.length));
@@ -2863,7 +3562,7 @@ var Editor = class {
2863
3562
  const cachedOffsets = this.#wrapLineOffsetsCache.get(line);
2864
3563
  if (cachedOffsets !== void 0) return cachedOffsets;
2865
3564
  if (this.#wrapLineOffsetsCache.size >= MAX_WRAP_OFFSETS_CACHE_LINES) this.#wrapLineOffsetsCache.clear();
2866
- const lineText = this.#textDocument?.getLineText(line);
3565
+ const lineText = this.#editSession?.document?.getLineText(line);
2867
3566
  if (lineText === void 0 || lineText.length === 0) {
2868
3567
  const offsets = new Uint32Array([0]);
2869
3568
  this.#wrapLineOffsetsCache.set(line, offsets);
@@ -2901,7 +3600,8 @@ var Editor = class {
2901
3600
  const measureGrapheme = (index) => {
2902
3601
  range.setStart(textNode, graphemeStart(index));
2903
3602
  range.setEnd(textNode, graphemeStart(index + 1));
2904
- return range.getBoundingClientRect();
3603
+ const clientRects = range.getClientRects();
3604
+ return clientRects.item(clientRects.length - 1) ?? range.getBoundingClientRect();
2905
3605
  };
2906
3606
  starts.push(0);
2907
3607
  let lastTop = measureGrapheme(0).top;
@@ -2933,7 +3633,7 @@ var Editor = class {
2933
3633
  #wrapLineTextOrWholeLine(line) {
2934
3634
  const offsets = this.#wrapLineText(line);
2935
3635
  if (offsets !== void 0) return offsets;
2936
- const length = this.#textDocument?.getLineText(line)?.length ?? 0;
3636
+ const length = this.#editSession?.document?.getLineText(line)?.length ?? 0;
2937
3637
  return Uint32Array.of(0, length);
2938
3638
  }
2939
3639
  #rangeBelongsToEditor({ startContainer, endContainer }) {
@@ -2942,11 +3642,31 @@ var Editor = class {
2942
3642
  return contentEl.contains(startContainer) && contentEl.contains(endContainer);
2943
3643
  }
2944
3644
  #isLineVisible(line) {
2945
- const lineCount = this.#textDocument?.lineCount ?? 0;
3645
+ const lineCount = this.#editSession?.document?.lineCount ?? 0;
2946
3646
  if (line < 0 || line >= lineCount) return false;
2947
3647
  return this.#getLineElement(line) !== void 0;
2948
3648
  }
2949
3649
  };
3650
+ function getEditSession({ type, editStateKey, owner, initialState, previousSession }) {
3651
+ if (initialState != null && initialState.type !== type) throw new TypeError(`Editor: initialState: a ${initialState.type} state cannot initialize a ${type} editor`);
3652
+ const initialSession = initialState;
3653
+ if (editStateKey != null) return EditStateManager.activate(type, editStateKey, owner, initialSession);
3654
+ if (initialSession != null) return initialSession;
3655
+ if (previousSession != null) return previousSession;
3656
+ return type === "file" ? { type: "file" } : { type: "file-diff" };
3657
+ }
3658
+ function gridTrackSpan(child) {
3659
+ if (!(child instanceof HTMLElement)) return 1;
3660
+ return Number(child.dataset.bufferSize ?? 1);
3661
+ }
3662
+ function isValidEditPredictionPosition(document, position) {
3663
+ return position !== void 0 && Number.isInteger(position.line) && Number.isInteger(position.character) && position.line >= 0 && position.line < document.lineCount && position.character >= 0 && position.character <= document.getLineLength(position.line);
3664
+ }
3665
+ function splitsSurrogatePair(text, offset) {
3666
+ const previous = text.charCodeAt(offset - 1);
3667
+ const next = text.charCodeAt(offset);
3668
+ return offset > 0 && offset < text.length && previous >= 55296 && previous <= 56319 && next >= 56320 && next <= 57343;
3669
+ }
2950
3670
  //#endregion
2951
3671
  export { Editor };
2952
3672