@pierre/diffs 1.3.0-beta.7 → 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 (110) hide show
  1. package/dist/components/CodeView.d.ts +86 -4
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +246 -32
  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 +15 -4
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +2 -0
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +21 -6
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  14. package/dist/components/VirtualizedFile.js +3 -1
  15. package/dist/components/VirtualizedFile.js.map +1 -1
  16. package/dist/components/VirtualizedFileDiff.d.ts +3 -1
  17. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  18. package/dist/components/VirtualizedFileDiff.js +14 -6
  19. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  20. package/dist/components/Virtualizer.d.ts +2 -0
  21. package/dist/components/Virtualizer.d.ts.map +1 -1
  22. package/dist/components/Virtualizer.js +16 -3
  23. package/dist/components/Virtualizer.js.map +1 -1
  24. package/dist/constants.d.ts +3 -1
  25. package/dist/constants.d.ts.map +1 -1
  26. package/dist/constants.js +3 -1
  27. package/dist/constants.js.map +1 -1
  28. package/dist/editor/command.d.ts +1 -1
  29. package/dist/editor/command.d.ts.map +1 -1
  30. package/dist/editor/command.js +8 -0
  31. package/dist/editor/command.js.map +1 -1
  32. package/dist/editor/editor.d.ts +8 -1
  33. package/dist/editor/editor.d.ts.map +1 -1
  34. package/dist/editor/editor.js +171 -38
  35. package/dist/editor/editor.js.map +1 -1
  36. package/dist/editor/editor2.js +1 -1
  37. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  38. package/dist/editor/lineAnnotations.js +84 -12
  39. package/dist/editor/lineAnnotations.js.map +1 -1
  40. package/dist/editor/marker.d.ts +5 -3
  41. package/dist/editor/marker.d.ts.map +1 -1
  42. package/dist/editor/marker.js +35 -12
  43. package/dist/editor/marker.js.map +1 -1
  44. package/dist/editor/matchBrackets.d.ts +8 -0
  45. package/dist/editor/matchBrackets.d.ts.map +1 -0
  46. package/dist/editor/matchBrackets.js +146 -0
  47. package/dist/editor/matchBrackets.js.map +1 -0
  48. package/dist/editor/popover.d.ts +45 -0
  49. package/dist/editor/popover.d.ts.map +1 -0
  50. package/dist/editor/popover.js +145 -0
  51. package/dist/editor/popover.js.map +1 -0
  52. package/dist/editor/selection.d.ts +13 -1
  53. package/dist/editor/selection.d.ts.map +1 -1
  54. package/dist/editor/selection.js +55 -4
  55. package/dist/editor/selection.js.map +1 -1
  56. package/dist/editor/selectionAction.d.ts +20 -3
  57. package/dist/editor/selectionAction.d.ts.map +1 -1
  58. package/dist/editor/selectionAction.js +41 -10
  59. package/dist/editor/selectionAction.js.map +1 -1
  60. package/dist/editor/textDocument.d.ts +2 -0
  61. package/dist/editor/textDocument.d.ts.map +1 -1
  62. package/dist/editor/textDocument.js +22 -6
  63. package/dist/editor/textDocument.js.map +1 -1
  64. package/dist/editor/tokenzier.d.ts +4 -7
  65. package/dist/editor/tokenzier.d.ts.map +1 -1
  66. package/dist/editor/tokenzier.js +76 -35
  67. package/dist/editor/tokenzier.js.map +1 -1
  68. package/dist/index.d.ts +6 -6
  69. package/dist/index.js +4 -4
  70. package/dist/managers/ResizeManager.d.ts +10 -2
  71. package/dist/managers/ResizeManager.d.ts.map +1 -1
  72. package/dist/managers/ResizeManager.js +33 -13
  73. package/dist/managers/ResizeManager.js.map +1 -1
  74. package/dist/react/CodeView.d.ts +25 -1
  75. package/dist/react/CodeView.d.ts.map +1 -1
  76. package/dist/react/CodeView.js +76 -17
  77. package/dist/react/CodeView.js.map +1 -1
  78. package/dist/react/EditorContext.d.ts +2 -2
  79. package/dist/react/EditorContext.d.ts.map +1 -1
  80. package/dist/react/EditorContext.js.map +1 -1
  81. package/dist/react/index.d.ts +2 -2
  82. package/dist/renderers/DiffHunksRenderer.d.ts +1 -1
  83. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  84. package/dist/renderers/DiffHunksRenderer.js +24 -15
  85. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  86. package/dist/renderers/FileRenderer.d.ts +2 -1
  87. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  88. package/dist/renderers/FileRenderer.js +58 -38
  89. package/dist/renderers/FileRenderer.js.map +1 -1
  90. package/dist/ssr/index.d.ts +2 -2
  91. package/dist/types.d.ts +42 -2
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/utils/applyLineTextWithNewline.d.ts +5 -0
  94. package/dist/utils/applyLineTextWithNewline.d.ts.map +1 -0
  95. package/dist/utils/applyLineTextWithNewline.js +11 -0
  96. package/dist/utils/applyLineTextWithNewline.js.map +1 -0
  97. package/dist/utils/areManagedSnapshotsEqual.d.ts +2 -2
  98. package/dist/utils/areManagedSnapshotsEqual.d.ts.map +1 -1
  99. package/dist/utils/areManagedSnapshotsEqual.js +5 -0
  100. package/dist/utils/areManagedSnapshotsEqual.js.map +1 -1
  101. package/dist/utils/createCodeViewHeaderFooterHostElement.d.ts +5 -0
  102. package/dist/utils/createCodeViewHeaderFooterHostElement.d.ts.map +1 -0
  103. package/dist/utils/createCodeViewHeaderFooterHostElement.js +19 -0
  104. package/dist/utils/createCodeViewHeaderFooterHostElement.js.map +1 -0
  105. package/dist/utils/updateDiffHunks.d.ts.map +1 -1
  106. package/dist/utils/updateDiffHunks.js +23 -1
  107. package/dist/utils/updateDiffHunks.js.map +1 -1
  108. package/dist/worker/worker-portable.js.map +1 -1
  109. package/dist/worker/worker.js.map +1 -1
  110. 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;
@@ -85,6 +88,12 @@ var Editor = class {
85
88
  constructor(options = {}) {
86
89
  this.#options = options;
87
90
  }
91
+ setOptions(options) {
92
+ this.#options = {
93
+ ...this.#options,
94
+ ...options
95
+ };
96
+ }
88
97
  edit(fileInstance) {
89
98
  const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, lineHoverHighlight = "disabled", ...rest } = fileInstance.options;
90
99
  if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || expandUnchanged !== true && fileInstance.type === "file-diff" || lineHoverHighlight !== "disabled") {
@@ -197,6 +206,7 @@ var Editor = class {
197
206
  return;
198
207
  }
199
208
  this.#markerRenderer ??= new MarkerRenderer({
209
+ popoverManager: this.#getPopoverManager(),
200
210
  getLineHeight: () => this.#metrics.lineHeight,
201
211
  getOverlayElement: () => this.#overlayElement,
202
212
  getGutterWidth: () => this.#getGutterWidth(),
@@ -219,11 +229,13 @@ var Editor = class {
219
229
  blur() {
220
230
  this.#contentElement?.blur();
221
231
  }
222
- cleanUp() {
232
+ cleanUp(recycle = false) {
223
233
  this.#tokenizer?.cleanUp();
224
234
  this.#tokenizer = void 0;
225
- this.#textDocument = void 0;
226
- this.#fileInfo = void 0;
235
+ if (!recycle) {
236
+ this.#textDocument = void 0;
237
+ this.#fileInfo = void 0;
238
+ }
227
239
  this.#globalEventDisposes?.forEach((dispose) => dispose());
228
240
  this.#globalEventDisposes = void 0;
229
241
  this.#editorEventDisposes?.forEach((dispose) => dispose());
@@ -247,6 +259,8 @@ var Editor = class {
247
259
  this.#spriteElement?.remove();
248
260
  this.#spriteElement = void 0;
249
261
  this.#fileContainer = void 0;
262
+ this.#popoverManager?.cleanUp();
263
+ this.#popoverManager = void 0;
250
264
  this.#gutterElement = void 0;
251
265
  this.#contentElement?.removeAttribute("contentEditable");
252
266
  this.#contentElement = void 0;
@@ -293,6 +307,7 @@ var Editor = class {
293
307
  break;
294
308
  }
295
309
  if (codeElement === void 0 || contentEl === void 0) return;
310
+ this.#getPopoverManager().setViewportElements(fileContainer, codeElement);
296
311
  if (this.#fileContainer !== fileContainer) {
297
312
  this.#fileContainer = fileContainer;
298
313
  if (this.#globalStyleElement !== void 0) fileContainer.appendChild(this.#globalStyleElement);
@@ -314,17 +329,7 @@ var Editor = class {
314
329
  };
315
330
  this.#textDocument = textDocument;
316
331
  this.#tokenizer?.cleanUp();
317
- this.#tokenizer = new EditorTokenizer({
318
- highlighter,
319
- textDocument,
320
- codeOptions: this.#fileInstance?.options ?? {},
321
- onDeferTokenize: this.#onDeferTokenize,
322
- onThemeChange: () => this.#scheduleThemeSelectionRefresh(),
323
- setStyle: (css) => {
324
- this.#themeStyleElement.textContent = css;
325
- },
326
- __debug: this.#options.__debug
327
- });
332
+ this.#tokenizer = void 0;
328
333
  this.#resetState();
329
334
  this.#selections = this.#initSelections;
330
335
  requestAnimationFrame(() => {
@@ -332,6 +337,19 @@ var Editor = class {
332
337
  });
333
338
  if (this.#textDocument !== void 0 && this.#options.__debug === true) console.log("[diffs/editor] text document rebuilt from", fileOrDiff.name);
334
339
  }
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
+ });
335
353
  if (this.#contentElement !== contentEl) {
336
354
  this.#gutterElement = gutterEl;
337
355
  this.#contentElement = extend(contentEl, {
@@ -384,6 +402,12 @@ 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();
@@ -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,10 +511,12 @@ 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) => {
491
522
  this.#suppressNativeSelectionSync = false;
@@ -554,10 +585,16 @@ var Editor = class {
554
585
  e.preventDefault();
555
586
  return;
556
587
  }
557
- if (e.key === "v" && isPrimaryModifier(e) && this.#options.clipboard !== void 0) {
558
- e.preventDefault();
559
- queueRender(this.#handleCustomPasteEvent);
560
- 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
+ }
561
598
  }
562
599
  if (this.#searchPanel !== void 0) {
563
600
  const findAgain = resolveFindAgainShortcut(e);
@@ -721,6 +758,10 @@ var Editor = class {
721
758
  }
722
759
  break;
723
760
  }
761
+ case "moveLineUp":
762
+ case "moveLineDown":
763
+ this.#moveSelectedLines(command === "moveLineUp" ? -1 : 1);
764
+ break;
724
765
  case "indent":
725
766
  case "outdent":
726
767
  if (this.#selections !== void 0) {
@@ -834,6 +875,62 @@ var Editor = class {
834
875
  break;
835
876
  }
836
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
+ }
837
934
  #handleLayoutResize = () => {
838
935
  const lineAnnotations = this.#lineAnnotations?.length ?? 0;
839
936
  const prevGutterWidth = this.#gutterWidthCache;
@@ -1046,11 +1143,9 @@ var Editor = class {
1046
1143
  }
1047
1144
  }
1048
1145
  #getScrollMargin() {
1049
- const componentTop = this.#fileInstance?.top ?? 0;
1050
1146
  const top = this.#searchPanel !== void 0 ? 48 : 0;
1051
1147
  const start = this.#getGutterWidth() + this.#metrics.ch;
1052
- const end = this.#metrics.ch;
1053
- return `${componentTop + top}px ${end}px 0 ${start}px`;
1148
+ return `${top}px ${this.#metrics.ch}px 0 ${start}px`;
1054
1149
  }
1055
1150
  #scrollToLine(line, char = 0, noFocus = false) {
1056
1151
  this.__postponeBgTokenizeToNextFrame();
@@ -1115,12 +1210,7 @@ var Editor = class {
1115
1210
  block: "center",
1116
1211
  inline: "nearest"
1117
1212
  });
1118
- if (this.#scrollingToLine === line && yFix === 0) {
1119
- this.#scrollingToLine = void 0;
1120
- this.#scrollingToLineChar = void 0;
1121
- this.#scrollingToLineFixed = false;
1122
- this.#scrollingToLineNoFocus = false;
1123
- } else if (this.#scrollingToLine === line && this.#scrollingToLineFixed) {
1213
+ if (this.#scrollingToLine === line && (yFix === 0 || this.#scrollingToLineFixed)) {
1124
1214
  this.#scrollingToLine = void 0;
1125
1215
  this.#scrollingToLineChar = void 0;
1126
1216
  this.#scrollingToLineFixed = false;
@@ -1266,6 +1356,8 @@ var Editor = class {
1266
1356
  if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, "selection", selection);
1267
1357
  this.#renderCaret(renderCtx, selection, selection === primarySelection);
1268
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);
1269
1361
  }
1270
1362
  const textDocument = this.#textDocument;
1271
1363
  if (this.#matches !== void 0 && textDocument !== void 0) {
@@ -1454,15 +1546,17 @@ var Editor = class {
1454
1546
  const overlayElement = this.#overlayElement;
1455
1547
  const textDocument = this.#textDocument;
1456
1548
  const renderSelectionAction = this.#options.renderSelectionAction;
1457
- if (this.#options.enabledSelectionAction !== true || renderSelectionAction === void 0 || primarySelection === void 0 || isCollapsedSelection(primarySelection) || this.#isContentMouseDown || overlayElement === void 0 || textDocument === void 0) {
1549
+ const cleanup = () => {
1458
1550
  this.#selectionAction?.cleanup();
1459
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();
1460
1555
  return;
1461
1556
  }
1462
1557
  const head = getCaretPosition(primarySelection);
1463
1558
  if (!this.#isLineVisible(head.line)) {
1464
- this.#selectionAction?.cleanup();
1465
- this.#selectionAction = void 0;
1559
+ cleanup();
1466
1560
  return;
1467
1561
  }
1468
1562
  if (this.#selectionAction === void 0) {
@@ -1478,18 +1572,56 @@ var Editor = class {
1478
1572
  this.#replaceSelectionText(text, [getActiveSelection()]);
1479
1573
  },
1480
1574
  close: () => {
1481
- this.#selectionAction?.cleanup();
1482
- this.#selectionAction = void 0;
1575
+ cleanup();
1483
1576
  this.#scrollToPrimaryCaret();
1484
1577
  }
1485
1578
  });
1486
- this.#selectionAction = new SelectionActionWidget(head.line, selectionActionElement, overlayElement);
1579
+ this.#selectionAction = new SelectionActionWidget(selectionActionElement, overlayElement, () => this.#updateSelectionActionPopover());
1580
+ this.#getPopoverManager().resetPlacement();
1487
1581
  }
1488
- const [left, wrapLine] = this.#getCharX(head.line, head.character);
1489
1582
  const lineHeight = this.#metrics.lineHeight;
1490
- const top = this.#getLineY(head.line) + wrapLine * lineHeight + lineHeight;
1491
- this.#selectionAction.line = head.line;
1492
- 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);
1493
1625
  }
1494
1626
  #openSearchPanel(mode) {
1495
1627
  if (this.#searchPanel !== void 0) {
@@ -1743,6 +1875,7 @@ var Editor = class {
1743
1875
  #lineBackgroundColor(line) {
1744
1876
  const lineElement = this.#getLineElement(line);
1745
1877
  if (lineElement === void 0) return;
1878
+ if (navigator.userAgent.includes("jsdom")) return;
1746
1879
  const backgroundColor = getComputedStyle(lineElement, "::after").backgroundColor;
1747
1880
  return backgroundColor === "" || backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)" ? void 0 : backgroundColor;
1748
1881
  }