@pierre/diffs 1.3.0-beta.6 → 1.3.0-beta.8

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 (180) hide show
  1. package/dist/components/CodeView.d.ts +90 -5
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +291 -38
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +1 -0
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +20 -6
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +38 -23
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +144 -61
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/UnresolvedFile.d.ts +3 -2
  14. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  15. package/dist/components/UnresolvedFile.js +4 -2
  16. package/dist/components/UnresolvedFile.js.map +1 -1
  17. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  18. package/dist/components/VirtualizedFile.js +6 -8
  19. package/dist/components/VirtualizedFile.js.map +1 -1
  20. package/dist/components/VirtualizedFileDiff.d.ts +13 -5
  21. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  22. package/dist/components/VirtualizedFileDiff.js +192 -55
  23. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  24. package/dist/components/Virtualizer.d.ts +2 -0
  25. package/dist/components/Virtualizer.d.ts.map +1 -1
  26. package/dist/components/Virtualizer.js +16 -3
  27. package/dist/components/Virtualizer.js.map +1 -1
  28. package/dist/constants.d.ts +3 -1
  29. package/dist/constants.d.ts.map +1 -1
  30. package/dist/constants.js +3 -1
  31. package/dist/constants.js.map +1 -1
  32. package/dist/editor/command.d.ts +1 -1
  33. package/dist/editor/command.d.ts.map +1 -1
  34. package/dist/editor/command.js +8 -0
  35. package/dist/editor/command.js.map +1 -1
  36. package/dist/editor/editor.d.ts +10 -3
  37. package/dist/editor/editor.d.ts.map +1 -1
  38. package/dist/editor/editor.js +325 -135
  39. package/dist/editor/editor.js.map +1 -1
  40. package/dist/editor/editor2.js +1 -1
  41. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  42. package/dist/editor/lineAnnotations.js +84 -12
  43. package/dist/editor/lineAnnotations.js.map +1 -1
  44. package/dist/editor/marker.d.ts +5 -3
  45. package/dist/editor/marker.d.ts.map +1 -1
  46. package/dist/editor/marker.js +35 -12
  47. package/dist/editor/marker.js.map +1 -1
  48. package/dist/editor/matchBrackets.d.ts +8 -0
  49. package/dist/editor/matchBrackets.d.ts.map +1 -0
  50. package/dist/editor/matchBrackets.js +146 -0
  51. package/dist/editor/matchBrackets.js.map +1 -0
  52. package/dist/editor/popover.d.ts +45 -0
  53. package/dist/editor/popover.d.ts.map +1 -0
  54. package/dist/editor/popover.js +145 -0
  55. package/dist/editor/popover.js.map +1 -0
  56. package/dist/editor/selection.d.ts +14 -2
  57. package/dist/editor/selection.d.ts.map +1 -1
  58. package/dist/editor/selection.js +142 -41
  59. package/dist/editor/selection.js.map +1 -1
  60. package/dist/editor/selectionAction.d.ts +20 -3
  61. package/dist/editor/selectionAction.d.ts.map +1 -1
  62. package/dist/editor/selectionAction.js +41 -10
  63. package/dist/editor/selectionAction.js.map +1 -1
  64. package/dist/editor/textDocument.d.ts +2 -0
  65. package/dist/editor/textDocument.d.ts.map +1 -1
  66. package/dist/editor/textDocument.js +22 -6
  67. package/dist/editor/textDocument.js.map +1 -1
  68. package/dist/editor/textMeasure.d.ts.map +1 -1
  69. package/dist/editor/textMeasure.js +25 -7
  70. package/dist/editor/textMeasure.js.map +1 -1
  71. package/dist/editor/tokenzier.d.ts +6 -7
  72. package/dist/editor/tokenzier.d.ts.map +1 -1
  73. package/dist/editor/tokenzier.js +83 -34
  74. package/dist/editor/tokenzier.js.map +1 -1
  75. package/dist/editor/utils.d.ts +3 -1
  76. package/dist/editor/utils.d.ts.map +1 -1
  77. package/dist/editor/utils.js +14 -1
  78. package/dist/editor/utils.js.map +1 -1
  79. package/dist/index.d.ts +9 -7
  80. package/dist/index.js +6 -4
  81. package/dist/managers/InteractionManager.d.ts.map +1 -1
  82. package/dist/managers/InteractionManager.js +0 -1
  83. package/dist/managers/InteractionManager.js.map +1 -1
  84. package/dist/managers/ResizeManager.d.ts +10 -2
  85. package/dist/managers/ResizeManager.d.ts.map +1 -1
  86. package/dist/managers/ResizeManager.js +33 -13
  87. package/dist/managers/ResizeManager.js.map +1 -1
  88. package/dist/react/CodeView.d.ts +25 -1
  89. package/dist/react/CodeView.d.ts.map +1 -1
  90. package/dist/react/CodeView.js +76 -17
  91. package/dist/react/CodeView.js.map +1 -1
  92. package/dist/react/EditorContext.d.ts +2 -2
  93. package/dist/react/EditorContext.d.ts.map +1 -1
  94. package/dist/react/EditorContext.js.map +1 -1
  95. package/dist/react/MultiFileDiff.d.ts +3 -4
  96. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  97. package/dist/react/MultiFileDiff.js.map +1 -1
  98. package/dist/react/index.d.ts +2 -2
  99. package/dist/react/utils/useFileDiffInstance.js +14 -15
  100. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  101. package/dist/renderers/DiffHunksRenderer.d.ts +2 -2
  102. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  103. package/dist/renderers/DiffHunksRenderer.js +45 -23
  104. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  105. package/dist/renderers/FileRenderer.d.ts +2 -1
  106. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  107. package/dist/renderers/FileRenderer.js +58 -38
  108. package/dist/renderers/FileRenderer.js.map +1 -1
  109. package/dist/ssr/index.d.ts +2 -2
  110. package/dist/ssr/preloadDiffs.d.ts +11 -10
  111. package/dist/ssr/preloadDiffs.d.ts.map +1 -1
  112. package/dist/ssr/preloadDiffs.js +14 -6
  113. package/dist/ssr/preloadDiffs.js.map +1 -1
  114. package/dist/types.d.ts +100 -6
  115. package/dist/types.d.ts.map +1 -1
  116. package/dist/utils/applyLineTextWithNewline.d.ts +5 -0
  117. package/dist/utils/applyLineTextWithNewline.d.ts.map +1 -0
  118. package/dist/utils/applyLineTextWithNewline.js +11 -0
  119. package/dist/utils/applyLineTextWithNewline.js.map +1 -0
  120. package/dist/utils/areHunkDataEqual.js +1 -1
  121. package/dist/utils/areHunkDataEqual.js.map +1 -1
  122. package/dist/utils/areManagedSnapshotsEqual.d.ts +2 -2
  123. package/dist/utils/areManagedSnapshotsEqual.d.ts.map +1 -1
  124. package/dist/utils/areManagedSnapshotsEqual.js +5 -0
  125. package/dist/utils/areManagedSnapshotsEqual.js.map +1 -1
  126. package/dist/utils/awaitWithTimeout.d.ts +5 -0
  127. package/dist/utils/awaitWithTimeout.d.ts.map +1 -0
  128. package/dist/utils/awaitWithTimeout.js +15 -0
  129. package/dist/utils/awaitWithTimeout.js.map +1 -0
  130. package/dist/utils/cloneFileDiffMetadata.d.ts +7 -0
  131. package/dist/utils/cloneFileDiffMetadata.d.ts.map +1 -0
  132. package/dist/utils/cloneFileDiffMetadata.js +16 -0
  133. package/dist/utils/cloneFileDiffMetadata.js.map +1 -0
  134. package/dist/utils/computeEstimatedDiffHeights.d.ts +3 -1
  135. package/dist/utils/computeEstimatedDiffHeights.d.ts.map +1 -1
  136. package/dist/utils/computeEstimatedDiffHeights.js +8 -1
  137. package/dist/utils/computeEstimatedDiffHeights.js.map +1 -1
  138. package/dist/utils/createCodeViewHeaderFooterHostElement.d.ts +5 -0
  139. package/dist/utils/createCodeViewHeaderFooterHostElement.d.ts.map +1 -0
  140. package/dist/utils/createCodeViewHeaderFooterHostElement.js +19 -0
  141. package/dist/utils/createCodeViewHeaderFooterHostElement.js.map +1 -0
  142. package/dist/utils/createPreElement.js +0 -1
  143. package/dist/utils/createPreElement.js.map +1 -1
  144. package/dist/utils/getDiffFileInput.d.ts +14 -0
  145. package/dist/utils/getDiffFileInput.d.ts.map +1 -0
  146. package/dist/utils/getDiffFileInput.js +24 -0
  147. package/dist/utils/getDiffFileInput.js.map +1 -0
  148. package/dist/utils/getDiffHunksRendererOptions.js +1 -0
  149. package/dist/utils/getDiffHunksRendererOptions.js.map +1 -1
  150. package/dist/utils/getFiletypeFromFileName.d.ts.map +1 -1
  151. package/dist/utils/getFiletypeFromFileName.js +2 -0
  152. package/dist/utils/getFiletypeFromFileName.js.map +1 -1
  153. package/dist/utils/hydratePartialDiff.d.ts +10 -0
  154. package/dist/utils/hydratePartialDiff.d.ts.map +1 -0
  155. package/dist/utils/hydratePartialDiff.js +140 -0
  156. package/dist/utils/hydratePartialDiff.js.map +1 -0
  157. package/dist/utils/iterateOverDiff.js +3 -3
  158. package/dist/utils/iterateOverDiff.js.map +1 -1
  159. package/dist/utils/parseDiffFromFile.d.ts +1 -1
  160. package/dist/utils/parseDiffFromFile.d.ts.map +1 -1
  161. package/dist/utils/parseDiffFromFile.js +26 -5
  162. package/dist/utils/parseDiffFromFile.js.map +1 -1
  163. package/dist/utils/setWrapperNodeProps.js +0 -1
  164. package/dist/utils/setWrapperNodeProps.js.map +1 -1
  165. package/dist/utils/updateDiffHunks.d.ts +5 -1
  166. package/dist/utils/updateDiffHunks.d.ts.map +1 -1
  167. package/dist/utils/updateDiffHunks.js +48 -4
  168. package/dist/utils/updateDiffHunks.js.map +1 -1
  169. package/dist/worker/WorkerPoolManager.d.ts +7 -2
  170. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  171. package/dist/worker/WorkerPoolManager.js +78 -15
  172. package/dist/worker/WorkerPoolManager.js.map +1 -1
  173. package/dist/worker/index.d.ts +2 -2
  174. package/dist/worker/types.d.ts +7 -1
  175. package/dist/worker/types.d.ts.map +1 -1
  176. package/dist/worker/worker-portable.js +5 -3
  177. package/dist/worker/worker-portable.js.map +1 -1
  178. package/dist/worker/worker.js +5 -3
  179. package/dist/worker/worker.js.map +1 -1
  180. package/package.json +1 -1
@@ -6,8 +6,10 @@ import { EditStack } from "./editStack.js";
6
6
  import editor_default from "./editor2.js";
7
7
  import { addEventListener, clampDomOffset, extend, getLineNumberAttr, h, round } from "./utils.js";
8
8
  import { applyDocumentChangeToLineAnnotations, renderLineAnnotations } from "./lineAnnotations.js";
9
- import { applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNexMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectionAnchor, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapSelectionsAfterEdits, resolveIndentEdits, resolveSelectionCut, selectionIntersects } from "./selection.js";
9
+ import { PopoverManager } from "./popover.js";
10
+ import { applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNexMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapSelectionsAfterEdits, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines } from "./selection.js";
10
11
  import { MarkerRenderer, markerSeverityDatasetKey } from "./marker.js";
12
+ import { findBracketMatchRanges } from "./matchBrackets.js";
11
13
  import { createSpriteElement } from "./sprite.js";
12
14
  import { SearchPanelWidget } from "./searchPanel.js";
13
15
  import { SelectionActionWidget } from "./selectionAction.js";
@@ -20,6 +22,7 @@ var Editor = class {
20
22
  #options;
21
23
  #metrics = new Metrics();
22
24
  #tokenizer;
25
+ #popoverManager;
23
26
  #editorEventDisposes;
24
27
  #globalEventDisposes;
25
28
  #selectEventDisposes;
@@ -29,7 +32,7 @@ var Editor = class {
29
32
  #contentWidthCache;
30
33
  #lineYCache = /* @__PURE__ */ new Map();
31
34
  #wrapLineOffsetsCache = /* @__PURE__ */ new Map();
32
- #lastAccessedLineElement;
35
+ #lineElementsCache = /* @__PURE__ */ new Map();
33
36
  #lastAccessedCharX;
34
37
  #globalStyleElement;
35
38
  #editorStyleElement;
@@ -52,6 +55,7 @@ var Editor = class {
52
55
  #searchPanel;
53
56
  #selectionAction;
54
57
  #shouldIgnoreSelectionChange = false;
58
+ #suppressNativeSelectionSync = false;
55
59
  #contentHasFocus = false;
56
60
  #isComposing = false;
57
61
  #isGutterMouseDown = false;
@@ -65,11 +69,13 @@ var Editor = class {
65
69
  #matches;
66
70
  #scrollingToLine;
67
71
  #scrollingToLineChar;
72
+ #scrollingToLineFixed = false;
68
73
  #scrollingToLineNoFocus = false;
69
74
  #retainSearchPanelFocus = false;
70
75
  #fontRemeasureScheduled = false;
76
+ #themeSelectionRefreshFrame;
71
77
  #onDeferTokenize = (lines, themeType) => {
72
- this.#fileInstance?.updateRenderCache(lines, themeType);
78
+ this.#fileInstance?.updateRenderCache(lines, themeType, false);
73
79
  if (this.#renderRange !== void 0 && this.#renderRange.totalLines !== Infinity) {
74
80
  const { startingLine, totalLines } = this.#renderRange;
75
81
  const endLine = Math.min(startingLine + totalLines, this.#textDocument?.lineCount ?? 0);
@@ -82,11 +88,16 @@ var Editor = class {
82
88
  constructor(options = {}) {
83
89
  this.#options = options;
84
90
  }
85
- edit(component) {
86
- const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, lineHoverHighlight, ...rest } = component.options;
87
- const isDiff = component.type === "file-diff";
88
- if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || expandUnchanged !== true && isDiff || lineHoverHighlight !== "disabled") {
89
- component.setOptions({
91
+ setOptions(options) {
92
+ this.#options = {
93
+ ...this.#options,
94
+ ...options
95
+ };
96
+ }
97
+ edit(fileInstance) {
98
+ const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, lineHoverHighlight = "disabled", ...rest } = fileInstance.options;
99
+ if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || expandUnchanged !== true && fileInstance.type === "file-diff" || lineHoverHighlight !== "disabled") {
100
+ fileInstance.setOptions({
90
101
  ...rest,
91
102
  useTokenTransformer: true,
92
103
  enableGutterUtility: false,
@@ -94,11 +105,11 @@ var Editor = class {
94
105
  expandUnchanged: true,
95
106
  lineHoverHighlight: "disabled"
96
107
  });
97
- component.rerender();
108
+ fileInstance.rerender();
98
109
  }
99
- this.#fileInstance = component;
110
+ this.#fileInstance = fileInstance;
100
111
  this.#initialize();
101
- this.#detach = component.attachEditor(this);
112
+ this.#detach = fileInstance.attachEditor(this);
102
113
  return () => this.cleanUp();
103
114
  }
104
115
  /**
@@ -183,7 +194,7 @@ var Editor = class {
183
194
  };
184
195
  });
185
196
  this.#updateSelections(resolvedSelections);
186
- this.#scrollToPrimaryCaret(false, "center");
197
+ this.#scrollToPrimaryCaret();
187
198
  }
188
199
  setMarkers(markers) {
189
200
  const textDocument = this.#textDocument;
@@ -195,6 +206,7 @@ var Editor = class {
195
206
  return;
196
207
  }
197
208
  this.#markerRenderer ??= new MarkerRenderer({
209
+ popoverManager: this.#getPopoverManager(),
198
210
  getLineHeight: () => this.#metrics.lineHeight,
199
211
  getOverlayElement: () => this.#overlayElement,
200
212
  getGutterWidth: () => this.#getGutterWidth(),
@@ -217,9 +229,13 @@ var Editor = class {
217
229
  blur() {
218
230
  this.#contentElement?.blur();
219
231
  }
220
- cleanUp() {
232
+ cleanUp(recycle = false) {
221
233
  this.#tokenizer?.cleanUp();
222
234
  this.#tokenizer = void 0;
235
+ if (!recycle) {
236
+ this.#textDocument = void 0;
237
+ this.#fileInfo = void 0;
238
+ }
223
239
  this.#globalEventDisposes?.forEach((dispose) => dispose());
224
240
  this.#globalEventDisposes = void 0;
225
241
  this.#editorEventDisposes?.forEach((dispose) => dispose());
@@ -232,7 +248,7 @@ var Editor = class {
232
248
  this.#contentWidthCache = void 0;
233
249
  this.#lineYCache.clear();
234
250
  this.#wrapLineOffsetsCache.clear();
235
- this.#lastAccessedLineElement = void 0;
251
+ this.#lineElementsCache.clear();
236
252
  this.#lastAccessedCharX = void 0;
237
253
  this.#globalStyleElement?.remove();
238
254
  this.#globalStyleElement = void 0;
@@ -243,6 +259,8 @@ var Editor = class {
243
259
  this.#spriteElement?.remove();
244
260
  this.#spriteElement = void 0;
245
261
  this.#fileContainer = void 0;
262
+ this.#popoverManager?.cleanUp();
263
+ this.#popoverManager = void 0;
246
264
  this.#gutterElement = void 0;
247
265
  this.#contentElement?.removeAttribute("contentEditable");
248
266
  this.#contentElement = void 0;
@@ -252,10 +270,14 @@ var Editor = class {
252
270
  this.#resizeObserver?.disconnect();
253
271
  this.#resizeObserver = void 0;
254
272
  this.#fontRemeasureScheduled = false;
273
+ if (this.#themeSelectionRefreshFrame !== void 0) {
274
+ cancelAnimationFrame(this.#themeSelectionRefreshFrame);
275
+ this.#themeSelectionRefreshFrame = void 0;
276
+ }
255
277
  this.#resetState();
256
278
  }
257
279
  /** @internal */
258
- __postponeBackgroundTokenizeToNextFrame() {
280
+ __postponeBgTokenizeToNextFrame() {
259
281
  const tokenizer = this.#tokenizer;
260
282
  if (tokenizer !== void 0) {
261
283
  tokenizer.pauseBackgroundTokenize();
@@ -285,6 +307,7 @@ var Editor = class {
285
307
  break;
286
308
  }
287
309
  if (codeElement === void 0 || contentEl === void 0) return;
310
+ this.#getPopoverManager().setViewportElements(fileContainer, codeElement);
288
311
  if (this.#fileContainer !== fileContainer) {
289
312
  this.#fileContainer = fileContainer;
290
313
  if (this.#globalStyleElement !== void 0) fileContainer.appendChild(this.#globalStyleElement);
@@ -292,8 +315,7 @@ var Editor = class {
292
315
  if (this.#themeStyleElement !== void 0) shadowRoot.appendChild(this.#themeStyleElement);
293
316
  if (this.#spriteElement !== void 0) shadowRoot.prepend(this.#spriteElement);
294
317
  }
295
- const documentReplaced = this.#textDocument === void 0 || this.#fileInfo === void 0 || this.#fileInfo.name !== fileOrDiff.name || this.#fileInfo.lang !== fileOrDiff.lang || this.#fileInfo.cacheKey !== fileOrDiff.cacheKey;
296
- if (documentReplaced) {
318
+ if (this.#textDocument === void 0 || this.#fileInfo === void 0 || this.#fileInfo.name !== fileOrDiff.name || this.#fileInfo.lang !== fileOrDiff.lang || this.#fileInfo.cacheKey !== fileOrDiff.cacheKey) {
297
319
  let contents = "";
298
320
  if ("contents" in fileOrDiff) contents = fileOrDiff.contents;
299
321
  else contents = fileOrDiff.additionLines.join("");
@@ -307,26 +329,28 @@ var Editor = class {
307
329
  };
308
330
  this.#textDocument = textDocument;
309
331
  this.#tokenizer?.cleanUp();
310
- this.#tokenizer = new EditorTokenizer({
311
- highlighter,
312
- textDocument,
313
- codeOptions: this.#fileInstance?.options ?? {},
314
- onDeferTokenize: this.#onDeferTokenize,
315
- setStyle: (css) => {
316
- this.#themeStyleElement.textContent = css;
317
- },
318
- __debug: this.#options.__debug
319
- });
332
+ this.#tokenizer = void 0;
320
333
  this.#resetState();
321
334
  this.#selections = this.#initSelections;
322
335
  requestAnimationFrame(() => {
323
336
  this.#options.onAttach?.(this, this.#fileInstance);
324
337
  });
325
- if (this.#textDocument !== void 0 && this.#options.__debug === true) console.log("[diffs/editor] text document changed !!!");
338
+ if (this.#textDocument !== void 0 && this.#options.__debug === true) console.log("[diffs/editor] text document rebuilt from", fileOrDiff.name);
326
339
  }
327
- let fullRerender = false;
340
+ const textDocument = this.#textDocument;
341
+ if (this.#tokenizer == null && textDocument != null) this.#tokenizer = new EditorTokenizer({
342
+ highlighter,
343
+ textDocument,
344
+ codeOptions: this.#fileInstance?.options ?? {},
345
+ matchBrackets: this.#options.matchBrackets,
346
+ onDeferTokenize: this.#onDeferTokenize,
347
+ onThemeChange: () => this.#scheduleThemeSelectionRefresh(),
348
+ setStyle: (css) => {
349
+ this.#themeStyleElement.textContent = css;
350
+ },
351
+ __debug: this.#options.__debug
352
+ });
328
353
  if (this.#contentElement !== contentEl) {
329
- fullRerender = true;
330
354
  this.#gutterElement = gutterEl;
331
355
  this.#contentElement = extend(contentEl, {
332
356
  contentEditable: "true",
@@ -356,12 +380,6 @@ var Editor = class {
356
380
  this.#renderRange = renderRange;
357
381
  this.#viewportWindowLines = renderRange?.totalLines;
358
382
  this.#tokenizer?.prebuildStateStack(renderRange);
359
- const fileInstance = this.#fileInstance;
360
- const textDocument = this.#textDocument;
361
- if (!documentReplaced && fullRerender && fileInstance?.rerenderFromDocument !== void 0 && textDocument !== void 0 && this.#shouldRenderDivergeFromDocument(textDocument)) {
362
- fileInstance.rerenderFromDocument(textDocument);
363
- return;
364
- }
365
383
  this.#markerRenderer?.removePopup();
366
384
  if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
367
385
  if (this.#initSelections !== void 0 && this.#primaryCaretElement !== void 0) {
@@ -384,10 +402,16 @@ var Editor = class {
384
402
  get #isWrap() {
385
403
  return this.#fileInstance?.options.overflow === "wrap";
386
404
  }
405
+ #getPopoverManager() {
406
+ return this.#popoverManager ??= new PopoverManager({
407
+ hasActivePopover: () => this.#selectionAction !== void 0,
408
+ updateActivePopover: () => this.#updateSelectionActionPopover()
409
+ });
410
+ }
387
411
  #resetCache() {
388
412
  this.#lineYCache.clear();
389
413
  this.#wrapLineOffsetsCache.clear();
390
- this.#lastAccessedLineElement = void 0;
414
+ this.#lineElementsCache.clear();
391
415
  this.#lastAccessedCharX = void 0;
392
416
  }
393
417
  #resetState() {
@@ -427,7 +451,7 @@ var Editor = class {
427
451
  this.#globalEventDisposes = [
428
452
  addEventListener(document, "selectionchange", () => {
429
453
  const shadowRoot = this.#fileContainer?.shadowRoot;
430
- if (this.#shouldIgnoreSelectionChange || shadowRoot == null || !this.#contentHasFocus) return;
454
+ if (this.#shouldIgnoreSelectionChange || this.#suppressNativeSelectionSync || shadowRoot == null || !this.#contentHasFocus) return;
431
455
  if (this.#selections !== void 0 && this.#selections.length > 1 && !this.#isContentMouseDown) return;
432
456
  const selectionRaw = document.getSelection();
433
457
  if (selectionRaw == null || typeof selectionRaw.getComposedRanges !== "function") return;
@@ -443,6 +467,10 @@ var Editor = class {
443
467
  if (this.#isContentMouseDown) if (this.#selectionStart !== void 0) selection = createSelectionFrom(this.#selectionStart, selection);
444
468
  else this.#selectionStart = selection;
445
469
  else if (this.#selectionStart !== void 0) selection.direction = createSelectionFrom(this.#selectionStart, selection).direction;
470
+ else if (this.#selections !== void 0 && this.#selections.length === 1) {
471
+ const previous = this.#selections[0];
472
+ if (comparePosition(previous.start, selection.start) === 0 && comparePosition(previous.end, selection.end) === 0) selection.direction = previous.direction;
473
+ }
446
474
  if (this.#reservedSelections !== void 0) this.#updateSelections([...this.#reservedSelections.filter((reservedSelection) => !selectionIntersects(reservedSelection, selection)), selection]);
447
475
  else this.#updateSelections([selection]);
448
476
  }, { passive: true }),
@@ -474,6 +502,7 @@ var Editor = class {
474
502
  this.#selectEventDisposes = disposes;
475
503
  }
476
504
  #listenContentElement(contentEl, gutterEl) {
505
+ const { onFocus, onBlur } = this.#options;
477
506
  const targetIsContentElement = (e) => {
478
507
  const target = e.composedPath()[0];
479
508
  return target !== void 0 && (target === contentEl || contentEl.contains(target));
@@ -482,12 +511,15 @@ var Editor = class {
482
511
  this.#editorEventDisposes = [
483
512
  addEventListener(contentEl, "focus", () => {
484
513
  this.#contentHasFocus = true;
514
+ onFocus?.();
485
515
  if (!this.#isContentMouseDown && !this.#shouldIgnoreSelectionChange && this.#selections !== void 0 && this.#selections.length > 0) this.#setWindowSelection(this.#selections.at(-1));
486
516
  }, { passive: true }),
487
517
  addEventListener(contentEl, "blur", () => {
488
518
  this.#contentHasFocus = false;
519
+ onBlur?.();
489
520
  }, { passive: true }),
490
521
  addEventListener(contentEl, "pointerdown", (e) => {
522
+ this.#suppressNativeSelectionSync = false;
491
523
  if (e.pointerType !== "mouse") return;
492
524
  if (this.#isDeletedLineTarget(e)) {
493
525
  this.#setDeletedTextSelectionActive(true);
@@ -543,6 +575,7 @@ var Editor = class {
543
575
  return;
544
576
  }
545
577
  if (!targetIsContentElement(e)) return;
578
+ this.#suppressNativeSelectionSync = false;
546
579
  const mvShortcut = isMoveCursorShortcut(e);
547
580
  const textDocument = this.#textDocument;
548
581
  if (this.#selections !== void 0 && this.#selections.length > 0 && mvShortcut !== void 0 && textDocument !== void 0) {
@@ -552,10 +585,16 @@ var Editor = class {
552
585
  e.preventDefault();
553
586
  return;
554
587
  }
555
- if (e.key === "v" && isPrimaryModifier(e) && this.#options.clipboard !== void 0) {
556
- e.preventDefault();
557
- queueRender(this.#handleCustomPasteEvent);
558
- return;
588
+ if ((e.key.length === 1 ? e.key.toLowerCase() : e.key) === "v" && isPrimaryModifier(e)) {
589
+ if (e.repeat && this.#isDiff) {
590
+ e.preventDefault();
591
+ return;
592
+ }
593
+ if (this.#options.clipboard !== void 0) {
594
+ e.preventDefault();
595
+ queueRender(this.#handleCustomPasteEvent);
596
+ return;
597
+ }
559
598
  }
560
599
  if (this.#searchPanel !== void 0) {
561
600
  const findAgain = resolveFindAgainShortcut(e);
@@ -719,6 +758,10 @@ var Editor = class {
719
758
  }
720
759
  break;
721
760
  }
761
+ case "moveLineUp":
762
+ case "moveLineDown":
763
+ this.#moveSelectedLines(command === "moveLineUp" ? -1 : 1);
764
+ break;
722
765
  case "indent":
723
766
  case "outdent":
724
767
  if (this.#selections !== void 0) {
@@ -778,15 +821,32 @@ var Editor = class {
778
821
  if (change !== void 0) this.#applyChange(change, nextSelections);
779
822
  }
780
823
  break;
781
- case "selectAll":
782
- this.#updateSelections([getDocumentFullSelection(textDocument)]);
824
+ case "selectAll": {
825
+ const fullSelection = getDocumentFullSelection(textDocument);
826
+ this.#updateSelections([fullSelection]);
783
827
  this.focus();
828
+ const renderedLines = this.#getRenderedEditableLineRange();
829
+ if (renderedLines !== void 0) {
830
+ this.#suppressNativeSelectionSync = true;
831
+ this.#setWindowSelection({
832
+ start: {
833
+ line: renderedLines.first,
834
+ character: 0
835
+ },
836
+ end: {
837
+ line: renderedLines.last,
838
+ character: textDocument.getLineLength(renderedLines.last)
839
+ },
840
+ direction: 1
841
+ });
842
+ }
784
843
  break;
844
+ }
785
845
  case "moveCursorToDocStart":
786
846
  case "moveCursorToDocEnd":
787
847
  {
788
848
  const atEnd = command === "moveCursorToDocEnd";
789
- this.#updateSelections([getDocumentBoundarySelection(textDocument, atEnd)]);
849
+ this.#updateSelections([getDocumentBoundarySelection(textDocument, atEnd, this.#isDiff)]);
790
850
  this.#scrollToPrimaryCaret();
791
851
  }
792
852
  break;
@@ -796,7 +856,7 @@ var Editor = class {
796
856
  const atEnd = command === "expandSelectionDocEnd";
797
857
  const selections = this.#selections;
798
858
  if (selections !== void 0) {
799
- this.#updateSelections(extendSelections(selections, getDocumentBoundarySelection(textDocument, atEnd)));
859
+ this.#updateSelections(extendSelections(selections, getDocumentBoundarySelection(textDocument, atEnd, this.#isDiff)));
800
860
  this.#scrollToPrimaryCaret();
801
861
  }
802
862
  }
@@ -815,6 +875,62 @@ var Editor = class {
815
875
  break;
816
876
  }
817
877
  }
878
+ #moveSelectedLines(direction) {
879
+ const textDocument = this.#textDocument;
880
+ const selections = this.#selections;
881
+ if (textDocument === void 0 || selections === void 0) return;
882
+ const blocks = getSelectedLineBlocks(selections);
883
+ if (blocks.length === 0 || direction < 0 && blocks[0].startLine === 0 || direction > 0 && blocks.at(-1).endLine >= textDocument.lineCount - 1) return;
884
+ const lineCount = textDocument.lineCount;
885
+ const lineRangeEnd = (line) => line < lineCount - 1 ? {
886
+ line: line + 1,
887
+ character: 0
888
+ } : {
889
+ line,
890
+ character: textDocument.getLineLength(line)
891
+ };
892
+ const getLinesText = (lines, appendFinalLineBreak) => {
893
+ const text = lines.map((line) => textDocument.getLineText(line)).join(textDocument.eol);
894
+ return appendFinalLineBreak ? text + textDocument.eol : text;
895
+ };
896
+ const edits = [];
897
+ if (direction < 0) for (const block of blocks) {
898
+ const previousLine = block.startLine - 1;
899
+ const blockLines = [];
900
+ for (let line = block.startLine; line <= block.endLine; line++) blockLines.push(line);
901
+ edits.push({
902
+ range: {
903
+ start: {
904
+ line: previousLine,
905
+ character: 0
906
+ },
907
+ end: lineRangeEnd(block.endLine)
908
+ },
909
+ newText: getLinesText([...blockLines, previousLine], block.endLine < lineCount - 1)
910
+ });
911
+ }
912
+ else for (let index = blocks.length - 1; index >= 0; index--) {
913
+ const block = blocks[index];
914
+ const nextLine = block.endLine + 1;
915
+ const blockLines = [];
916
+ for (let line = block.startLine; line <= block.endLine; line++) blockLines.push(line);
917
+ edits.push({
918
+ range: {
919
+ start: {
920
+ line: block.startLine,
921
+ character: 0
922
+ },
923
+ end: lineRangeEnd(nextLine)
924
+ },
925
+ newText: getLinesText([nextLine, ...blockLines], nextLine < lineCount - 1)
926
+ });
927
+ }
928
+ const lastBlock = blocks.at(-1);
929
+ const lastLineLengthAfterMove = direction > 0 && lastBlock.endLine === lineCount - 2 ? textDocument.getLineLength(lastBlock.endLine) : textDocument.getLineLength(lineCount - 1);
930
+ const nextSelections = selections.map((selection) => shiftSelectionLines(selection, direction, lineCount, (line) => line === lineCount - 1 ? lastLineLengthAfterMove : textDocument.getLineLength(line)));
931
+ const change = textDocument.applyEdits(edits, true, selections, nextSelections, true);
932
+ if (change !== void 0) this.#applyChange(change, nextSelections);
933
+ }
818
934
  #handleLayoutResize = () => {
819
935
  const lineAnnotations = this.#lineAnnotations?.length ?? 0;
820
936
  const prevGutterWidth = this.#gutterWidthCache;
@@ -824,7 +940,7 @@ var Editor = class {
824
940
  const gutterWidthChanged = this.#getGutterWidth() !== prevGutterWidth;
825
941
  const contentWidthChanged = this.#getContentWidth() !== prevContentWidth;
826
942
  if (!gutterWidthChanged && !contentWidthChanged) return;
827
- this.#lastAccessedLineElement = void 0;
943
+ this.#lineElementsCache.clear();
828
944
  this.#lastAccessedCharX = void 0;
829
945
  this.#metrics.clearTextWidthCache();
830
946
  if (contentWidthChanged && (this.#isWrap || lineAnnotations > 0)) {
@@ -924,27 +1040,15 @@ var Editor = class {
924
1040
  contentEl.style.gridRow = "span " + gridRow;
925
1041
  if (gutterEl !== void 0) gutterEl.style.gridRow = "span " + gridRow;
926
1042
  }
927
- fileInstance.updateRenderCache(dirtyLines, tokenizer.themeType, this.#isDiff && !didLineCountChange, didLineCountChange);
1043
+ fileInstance.updateRenderCache(dirtyLines, tokenizer.themeType, !didLineCountChange);
928
1044
  if (didLineCountChange) fileInstance.applyDocumentChange(textDocument, newLineAnnotations, shouldUpdateBuffer);
1045
+ if (this.#isDiff && (this.#diffSyle === "unified" || didLineCountChange)) this.#resetCache();
929
1046
  if (newLineAnnotations !== void 0) {
930
1047
  this.#lineAnnotations = newLineAnnotations;
931
1048
  renderLineAnnotations(newLineAnnotations, contentEl, gutterEl);
932
1049
  }
933
1050
  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)`);
934
1051
  }
935
- #shouldRenderDivergeFromDocument(textDocument) {
936
- const contentEl = this.#contentElement;
937
- if (contentEl === void 0) return false;
938
- for (const child of contentEl.children) {
939
- const el = child;
940
- const lineType = el.dataset.lineType;
941
- const lineNumber = getLineNumberAttr(el);
942
- if (lineNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) continue;
943
- const lineIndex = lineNumber - 1;
944
- if (lineIndex >= textDocument.lineCount || el.textContent !== textDocument.getLineText(lineIndex)) return true;
945
- }
946
- return false;
947
- }
948
1052
  #handleInput(inputType, data) {
949
1053
  switch (inputType) {
950
1054
  case "insertText": {
@@ -967,6 +1071,7 @@ var Editor = class {
967
1071
  this.#deleteSelectionText(true);
968
1072
  break;
969
1073
  case "deleteSoftLineBackward":
1074
+ case "deleteHardLineBackward":
970
1075
  this.#deleteSoftLineBackward();
971
1076
  break;
972
1077
  case "deleteHardLineForward":
@@ -1038,14 +1143,12 @@ var Editor = class {
1038
1143
  }
1039
1144
  }
1040
1145
  #getScrollMargin() {
1041
- const componentTop = this.#fileInstance?.top ?? 0;
1042
1146
  const top = this.#searchPanel !== void 0 ? 48 : 0;
1043
1147
  const start = this.#getGutterWidth() + this.#metrics.ch;
1044
- const end = this.#metrics.ch;
1045
- return `${componentTop + top}px ${end}px 0 ${start}px`;
1148
+ return `${top}px ${this.#metrics.ch}px 0 ${start}px`;
1046
1149
  }
1047
1150
  #scrollToLine(line, char = 0, noFocus = false) {
1048
- this.__postponeBackgroundTokenizeToNextFrame();
1151
+ this.__postponeBgTokenizeToNextFrame();
1049
1152
  const virtualCaret = h("div", { style: {
1050
1153
  position: "absolute",
1051
1154
  left: "0",
@@ -1069,33 +1172,55 @@ var Editor = class {
1069
1172
  });
1070
1173
  this.#scrollingToLine = void 0;
1071
1174
  this.#scrollingToLineChar = void 0;
1175
+ this.#scrollingToLineFixed = false;
1072
1176
  this.#scrollingToLineNoFocus = false;
1073
1177
  } else {
1074
- let yFix = 0;
1075
- if (this.#scrollingToLine === line && this.#contentElement !== void 0) for (let i = this.#contentElement.childElementCount - 1; i >= 0; i--) {
1076
- const child = this.#contentElement.children[i];
1077
- const lineType = child.dataset.lineType;
1078
- const lineNumber = getLineNumberAttr(child);
1079
- if (lineType !== void 0 && isLineEditable(lineType) && lineNumber !== void 0) {
1080
- yFix = (line - lineNumber) * this.#metrics.lineHeight;
1081
- break;
1178
+ const modelLinePosition = this.#fileInstance?.getLinePosition?.(line + 1);
1179
+ if (modelLinePosition !== void 0) {
1180
+ virtualCaret.style.top = modelLinePosition.top + "px";
1181
+ this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
1182
+ virtualCaret.scrollIntoView({
1183
+ block: "center",
1184
+ inline: "nearest"
1185
+ });
1186
+ if (modelLinePosition.height > 0) {
1187
+ this.#scrollingToLine = line;
1188
+ this.#scrollingToLineChar = char;
1189
+ this.#scrollingToLineNoFocus = noFocus;
1190
+ } else {
1191
+ this.#scrollingToLine = void 0;
1192
+ this.#scrollingToLineChar = void 0;
1193
+ this.#scrollingToLineNoFocus = false;
1082
1194
  }
1083
- }
1084
- const approximateLineY = ((this.#lineAnnotations ?? []).filter((annotation) => annotation.lineNumber < line).length + line) * this.#metrics.lineHeight + yFix;
1085
- virtualCaret.style.top = approximateLineY + "px";
1086
- this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
1087
- virtualCaret.scrollIntoView({
1088
- block: "center",
1089
- inline: "nearest"
1090
- });
1091
- if (this.#scrollingToLine === line && yFix === 0) {
1092
- this.#scrollingToLine = void 0;
1093
- this.#scrollingToLineChar = void 0;
1094
- this.#scrollingToLineNoFocus = false;
1095
1195
  } else {
1096
- this.#scrollingToLine = line;
1097
- this.#scrollingToLineChar = char;
1098
- this.#scrollingToLineNoFocus = noFocus;
1196
+ let yFix = 0;
1197
+ if (this.#scrollingToLine === line && this.#contentElement !== void 0) for (let i = this.#contentElement.childElementCount - 1; i >= 0; i--) {
1198
+ const child = this.#contentElement.children[i];
1199
+ const lineType = child.dataset.lineType;
1200
+ const lineNumber = getLineNumberAttr(child);
1201
+ if (lineType !== void 0 && isLineEditable(lineType) && lineNumber !== void 0) {
1202
+ yFix = (line - (lineNumber - 1)) * this.#metrics.lineHeight;
1203
+ break;
1204
+ }
1205
+ }
1206
+ const approximateLineY = ((this.#lineAnnotations ?? []).filter((annotation) => annotation.lineNumber < line).length + line) * this.#metrics.lineHeight + yFix;
1207
+ virtualCaret.style.top = approximateLineY + "px";
1208
+ this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
1209
+ virtualCaret.scrollIntoView({
1210
+ block: "center",
1211
+ inline: "nearest"
1212
+ });
1213
+ if (this.#scrollingToLine === line && (yFix === 0 || this.#scrollingToLineFixed)) {
1214
+ this.#scrollingToLine = void 0;
1215
+ this.#scrollingToLineChar = void 0;
1216
+ this.#scrollingToLineFixed = false;
1217
+ this.#scrollingToLineNoFocus = false;
1218
+ } else {
1219
+ this.#scrollingToLine = line;
1220
+ this.#scrollingToLineChar = char;
1221
+ this.#scrollingToLineFixed = yFix !== 0;
1222
+ this.#scrollingToLineNoFocus = noFocus;
1223
+ }
1099
1224
  }
1100
1225
  }
1101
1226
  virtualCaret.remove();
@@ -1193,8 +1318,15 @@ var Editor = class {
1193
1318
  });
1194
1319
  } catch {}
1195
1320
  }
1321
+ #scheduleThemeSelectionRefresh() {
1322
+ if (this.#themeSelectionRefreshFrame !== void 0) return;
1323
+ this.#themeSelectionRefreshFrame = requestAnimationFrame(() => {
1324
+ this.#themeSelectionRefreshFrame = void 0;
1325
+ if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
1326
+ });
1327
+ }
1196
1328
  #updateSelections(selections) {
1197
- this.__postponeBackgroundTokenizeToNextFrame();
1329
+ this.__postponeBgTokenizeToNextFrame();
1198
1330
  this.#primaryCaretElement = void 0;
1199
1331
  this.#setSelectedLinesSafe(null);
1200
1332
  if (selections.length === 0 && this.#matches === void 0 && this.#markerRenderer === void 0) {
@@ -1224,6 +1356,8 @@ var Editor = class {
1224
1356
  if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, "selection", selection);
1225
1357
  this.#renderCaret(renderCtx, selection, selection === primarySelection);
1226
1358
  }
1359
+ 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;
1360
+ if (bracketMatchRanges !== void 0) for (const range of bracketMatchRanges) this.#renderSelection(renderCtx, "bracketMatch", range);
1227
1361
  }
1228
1362
  const textDocument = this.#textDocument;
1229
1363
  if (this.#matches !== void 0 && textDocument !== void 0) {
@@ -1252,11 +1386,11 @@ var Editor = class {
1252
1386
  for (let line = start.line; line <= end.line; line++) {
1253
1387
  if (!this.#isLineVisible(line)) continue;
1254
1388
  const isLastLine = line === end.line;
1255
- const lineText = this.#textDocument.getLineText(line);
1256
1389
  const startChar = line === start.line ? start.character : 0;
1257
- const endChar = isLastLine ? end.character : lineText.length;
1390
+ const endChar = isLastLine ? end.character : this.#textDocument.getLineLength(line);
1258
1391
  if (this.#isWrap) {
1259
1392
  const contentWidth = this.#getContentWidth();
1393
+ const lineText = this.#textDocument.getLineText(line);
1260
1394
  if (2 * this.#metrics.ch + this.#metrics.measureTextWidth(lineText) > contentWidth) {
1261
1395
  this.#renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset);
1262
1396
  continue;
@@ -1305,7 +1439,9 @@ var Editor = class {
1305
1439
  const overlayEls = this.#overlayElements;
1306
1440
  const rounded = (this.#options.roundedSelection ?? true) && type === "selection";
1307
1441
  const addRoundedCorner = (line, wrapLine, left, radius) => {
1308
- const css = `width:${ch}px;transform:translateX(${left}px) translateY(${this.#getLineY(line) + wrapLine * lineHeight}px);`;
1442
+ const top = this.#getLineY(line) + wrapLine * lineHeight;
1443
+ const cornerBg = this.#lineBackgroundColor(line);
1444
+ const css = `width:${ch}px;transform:translateX(${left}px) translateY(${top}px);` + (cornerBg !== void 0 ? `--diffs-selection-corner-bg:${cornerBg};` : "");
1309
1445
  const dataset = {
1310
1446
  selectionCorner: "",
1311
1447
  [radius]: ""
@@ -1395,14 +1531,10 @@ var Editor = class {
1395
1531
  const [left, wrapLine] = this.#getCharX(line, character);
1396
1532
  const cacheKey = "caret-" + line + "/" + wrapLine + ":" + character;
1397
1533
  if (renderCtx.elements.has(cacheKey)) return;
1398
- const x = left - 1;
1399
- const y = this.#getLineY(line) + wrapLine * this.#metrics.lineHeight;
1400
- let caretEl;
1401
- if (this.#overlayElements?.has(cacheKey) === true) {
1402
- caretEl = this.#overlayElements.get(cacheKey);
1403
- this.#overlayElements.delete(cacheKey);
1404
- } else caretEl = h("div", { dataset: "caret" }, renderCtx.fragment);
1405
- caretEl.style.transform = `translateX(${x}px) translateY(${y}px)`;
1534
+ const caretEl = h("div", {
1535
+ dataset: "caret",
1536
+ style: { transform: `translateX(${left - 1}px) translateY(${this.#getLineY(line) + wrapLine * this.#metrics.lineHeight}px)` }
1537
+ }, renderCtx.fragment);
1406
1538
  renderCtx.elements.set(cacheKey, caretEl);
1407
1539
  if (isPrimary) {
1408
1540
  caretEl.style.scrollMargin = this.#getScrollMargin();
@@ -1414,15 +1546,17 @@ var Editor = class {
1414
1546
  const overlayElement = this.#overlayElement;
1415
1547
  const textDocument = this.#textDocument;
1416
1548
  const renderSelectionAction = this.#options.renderSelectionAction;
1417
- if (this.#options.enabledSelectionAction !== true || renderSelectionAction === void 0 || primarySelection === void 0 || isCollapsedSelection(primarySelection) || this.#isContentMouseDown || overlayElement === void 0 || textDocument === void 0) {
1549
+ const cleanup = () => {
1418
1550
  this.#selectionAction?.cleanup();
1419
1551
  this.#selectionAction = void 0;
1552
+ };
1553
+ if (this.#options.enabledSelectionAction !== true || renderSelectionAction === void 0 || primarySelection === void 0 || isCollapsedSelection(primarySelection) || this.#isContentMouseDown || overlayElement === void 0 || textDocument === void 0) {
1554
+ cleanup();
1420
1555
  return;
1421
1556
  }
1422
1557
  const head = getCaretPosition(primarySelection);
1423
1558
  if (!this.#isLineVisible(head.line)) {
1424
- this.#selectionAction?.cleanup();
1425
- this.#selectionAction = void 0;
1559
+ cleanup();
1426
1560
  return;
1427
1561
  }
1428
1562
  if (this.#selectionAction === void 0) {
@@ -1438,18 +1572,56 @@ var Editor = class {
1438
1572
  this.#replaceSelectionText(text, [getActiveSelection()]);
1439
1573
  },
1440
1574
  close: () => {
1441
- this.#selectionAction?.cleanup();
1442
- this.#selectionAction = void 0;
1575
+ cleanup();
1443
1576
  this.#scrollToPrimaryCaret();
1444
1577
  }
1445
1578
  });
1446
- this.#selectionAction = new SelectionActionWidget(head.line, selectionActionElement, overlayElement);
1579
+ this.#selectionAction = new SelectionActionWidget(selectionActionElement, overlayElement, () => this.#updateSelectionActionPopover());
1580
+ this.#getPopoverManager().resetPlacement();
1447
1581
  }
1448
- const [left, wrapLine] = this.#getCharX(head.line, head.character);
1449
1582
  const lineHeight = this.#metrics.lineHeight;
1450
- const top = this.#getLineY(head.line) + wrapLine * lineHeight + lineHeight;
1451
- this.#selectionAction.line = head.line;
1452
- this.#selectionAction.reposition(left, top, this.#getGutterWidth());
1583
+ const isBackward = primarySelection.direction === -1;
1584
+ const preferred = {
1585
+ placeAbove: isBackward,
1586
+ anchor: head
1587
+ };
1588
+ const fallback = isBackward ? {
1589
+ placeAbove: false,
1590
+ anchor: primarySelection.end
1591
+ } : {
1592
+ placeAbove: true,
1593
+ anchor: primarySelection.start
1594
+ };
1595
+ const popoverHeight = this.#selectionAction.height;
1596
+ const candidateGeometry = (candidate) => {
1597
+ const [left, candidateWrapLine] = this.#getCharX(candidate.anchor.line, candidate.anchor.character);
1598
+ const rowTop = this.#getLineY(candidate.anchor.line) + candidateWrapLine * lineHeight;
1599
+ const anchorTop = candidate.placeAbove ? rowTop : rowTop + lineHeight;
1600
+ const top = candidate.placeAbove ? anchorTop - popoverHeight : anchorTop;
1601
+ return {
1602
+ top,
1603
+ bottom: top + popoverHeight,
1604
+ left,
1605
+ anchorTop
1606
+ };
1607
+ };
1608
+ const preferredGeometry = candidateGeometry(preferred);
1609
+ const fallbackGeometry = candidateGeometry(fallback);
1610
+ const lineCount = textDocument.lineCount;
1611
+ const atDocumentEdge = isBackward ? head.line < 3 : head.line >= lineCount - 3;
1612
+ const canUseFallback = this.#isLineVisible(fallback.anchor.line);
1613
+ const popoverManager = this.#getPopoverManager();
1614
+ const useFallback = canUseFallback && popoverManager.choosePlacement({
1615
+ preferred: preferredGeometry,
1616
+ fallback: fallbackGeometry,
1617
+ viewport: popoverManager.getPlacementBounds(),
1618
+ popoverHeight,
1619
+ atDocumentEdge
1620
+ }) === "fallback";
1621
+ if (!canUseFallback) popoverManager.setPlacement("preferred");
1622
+ const { placeAbove } = useFallback ? fallback : preferred;
1623
+ const { left, anchorTop } = useFallback ? fallbackGeometry : preferredGeometry;
1624
+ this.#selectionAction.reposition(left, anchorTop, this.#getGutterWidth(), placeAbove);
1453
1625
  }
1454
1626
  #openSearchPanel(mode) {
1455
1627
  if (this.#searchPanel !== void 0) {
@@ -1643,7 +1815,7 @@ var Editor = class {
1643
1815
  });
1644
1816
  return file;
1645
1817
  }
1646
- #applyChange(change, selections, newLineAnnotations, options) {
1818
+ #applyChange(change, newSelections, newLineAnnotations, options) {
1647
1819
  const fileRef = this.#getFileRef();
1648
1820
  const onChange = this.#options.onChange;
1649
1821
  if (fileRef !== void 0 && onChange !== void 0) onChange(fileRef, newLineAnnotations ?? this.#lineAnnotations);
@@ -1656,8 +1828,8 @@ var Editor = class {
1656
1828
  this.#lastAccessedCharX = void 0;
1657
1829
  let renderRange = this.#renderRange;
1658
1830
  let shouldUpdateBuffer;
1659
- if (renderRange !== void 0 && selections !== void 0 && selections.length > 0) {
1660
- const primarySelection = selections.at(-1);
1831
+ if (renderRange !== void 0 && newSelections !== void 0 && newSelections.length > 0) {
1832
+ const primarySelection = newSelections.at(-1);
1661
1833
  const renderRangeEndLine = renderRange.startingLine + renderRange.totalLines;
1662
1834
  if (primarySelection.end.line >= renderRangeEndLine) {
1663
1835
  const widenedTotalLines = primarySelection.end.line - renderRange.startingLine + 1;
@@ -1674,15 +1846,17 @@ var Editor = class {
1674
1846
  }
1675
1847
  this.#rerender(change, newLineAnnotations, renderRange, shouldUpdateBuffer);
1676
1848
  if (options?.skipSearchRefresh !== true && this.#searchPanel !== void 0 && this.#matches !== void 0) this.#searchPanel.updateMatches({ syncSelection: false });
1677
- if (selections !== void 0) {
1678
- this.#updateSelections(selections);
1849
+ if (newSelections !== void 0) {
1850
+ this.#updateSelections(newSelections);
1679
1851
  if (options?.skipFocus !== true) {
1680
- if (this.#primaryCaretElement !== void 0) this.#primaryCaretElement.scrollIntoView({
1681
- block: "nearest",
1682
- inline: "nearest"
1852
+ if (this.#primaryCaretElement !== void 0) requestAnimationFrame(() => {
1853
+ this.#primaryCaretElement?.scrollIntoView({
1854
+ block: "nearest",
1855
+ inline: "nearest"
1856
+ });
1683
1857
  });
1684
- else if (selections.length > 0) {
1685
- const pos = getCaretPosition(selections.at(-1));
1858
+ else if (newSelections.length > 0) {
1859
+ const pos = getCaretPosition(newSelections.at(-1));
1686
1860
  this.#scrollToLine(pos.line, pos.character);
1687
1861
  }
1688
1862
  this.focus({ preventScroll: true });
@@ -1698,12 +1872,37 @@ var Editor = class {
1698
1872
  }
1699
1873
  }
1700
1874
  }
1875
+ #lineBackgroundColor(line) {
1876
+ const lineElement = this.#getLineElement(line);
1877
+ if (lineElement === void 0) return;
1878
+ if (navigator.userAgent.includes("jsdom")) return;
1879
+ const backgroundColor = getComputedStyle(lineElement, "::after").backgroundColor;
1880
+ return backgroundColor === "" || backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)" ? void 0 : backgroundColor;
1881
+ }
1882
+ #getRenderedEditableLineRange() {
1883
+ const contentElement = this.#contentElement;
1884
+ if (contentElement === void 0) return;
1885
+ let first;
1886
+ let last;
1887
+ for (const child of contentElement.children) {
1888
+ const el = child;
1889
+ const lineType = el.dataset.lineType;
1890
+ const lineNumber = getLineNumberAttr(el);
1891
+ if (lineNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) continue;
1892
+ const line = lineNumber - 1;
1893
+ if (first === void 0 || line < first) first = line;
1894
+ if (last === void 0 || line > last) last = line;
1895
+ }
1896
+ return first === void 0 || last === void 0 ? void 0 : {
1897
+ first,
1898
+ last
1899
+ };
1900
+ }
1701
1901
  #getLineElement(line) {
1702
- const lastAccessed = this.#lastAccessedLineElement;
1703
- if (lastAccessed !== void 0 && lastAccessed[0] === line) return lastAccessed[1];
1902
+ let lineElement = this.#lineElementsCache.get(line);
1903
+ if (lineElement !== void 0) return lineElement ?? void 0;
1704
1904
  const contentElement = this.#contentElement;
1705
1905
  if (contentElement === void 0) return;
1706
- let lineElement = null;
1707
1906
  if (this.#renderRange !== void 0) {
1708
1907
  const { startingLine } = this.#renderRange;
1709
1908
  const { children } = contentElement;
@@ -1719,13 +1918,8 @@ var Editor = class {
1719
1918
  }
1720
1919
  }
1721
1920
  lineElement ??= contentElement.querySelector(`[data-line="${line + 1}"]` + (this.#diffSyle === "unified" ? ":not([data-line-type=\"change-deletion\"])" : ""));
1722
- if (lineElement !== null) {
1723
- if (lastAccessed !== void 0) {
1724
- lastAccessed[0] = line;
1725
- lastAccessed[1] = lineElement;
1726
- } else this.#lastAccessedLineElement = [line, lineElement];
1727
- return lineElement;
1728
- }
1921
+ if (lineElement !== void 0) this.#lineElementsCache.set(line, lineElement);
1922
+ return lineElement ?? void 0;
1729
1923
  }
1730
1924
  #getGutterWidth() {
1731
1925
  if (this.#gutterElement === void 0) return 0;
@@ -1863,11 +2057,7 @@ var Editor = class {
1863
2057
  #isLineVisible(line) {
1864
2058
  const lineCount = this.#textDocument?.lineCount ?? 0;
1865
2059
  if (line < 0 || line >= lineCount) return false;
1866
- if (this.#renderRange === void 0) return true;
1867
- const { startingLine, totalLines } = this.#renderRange;
1868
- if (line < startingLine) return false;
1869
- if (totalLines === Infinity) return true;
1870
- return line < startingLine + totalLines;
2060
+ return this.#getLineElement(line) !== void 0;
1871
2061
  }
1872
2062
  };
1873
2063
  //#endregion