@pierre/diffs 1.3.0-beta.3 → 1.3.0-beta.4

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 (132) hide show
  1. package/dist/components/CodeView.d.ts.map +1 -1
  2. package/dist/components/File.d.ts +2 -2
  3. package/dist/components/File.d.ts.map +1 -1
  4. package/dist/components/File.js +11 -11
  5. package/dist/components/File.js.map +1 -1
  6. package/dist/components/FileDiff.d.ts +7 -4
  7. package/dist/components/FileDiff.d.ts.map +1 -1
  8. package/dist/components/FileDiff.js +56 -47
  9. package/dist/components/FileDiff.js.map +1 -1
  10. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  11. package/dist/components/UnresolvedFile.js +1 -1
  12. package/dist/components/VirtualizedFile.d.ts +1 -1
  13. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  14. package/dist/components/VirtualizedFile.js +7 -3
  15. package/dist/components/VirtualizedFile.js.map +1 -1
  16. package/dist/components/VirtualizedFileDiff.d.ts +2 -1
  17. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  18. package/dist/components/VirtualizedFileDiff.js +14 -0
  19. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  20. package/dist/components/VirtulizerDevelopment.d.ts.map +1 -1
  21. package/dist/editor/editStack.d.ts +1 -1
  22. package/dist/editor/editor.d.ts +10 -8
  23. package/dist/editor/editor.d.ts.map +1 -1
  24. package/dist/editor/editor.js +386 -339
  25. package/dist/editor/editor.js.map +1 -1
  26. package/dist/editor/editor2.js +1 -1
  27. package/dist/editor/editor2.js.map +1 -1
  28. package/dist/editor/lineAnnotations.d.ts +2 -1
  29. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  30. package/dist/editor/lineAnnotations.js +111 -1
  31. package/dist/editor/lineAnnotations.js.map +1 -1
  32. package/dist/editor/marker.d.ts +33 -0
  33. package/dist/editor/marker.d.ts.map +1 -0
  34. package/dist/editor/marker.js +185 -0
  35. package/dist/editor/marker.js.map +1 -0
  36. package/dist/editor/pieceTable.d.ts +2 -2
  37. package/dist/editor/pieceTable.d.ts.map +1 -1
  38. package/dist/editor/pieceTable.js +42 -11
  39. package/dist/editor/pieceTable.js.map +1 -1
  40. package/dist/editor/searchPanel.js +2 -2
  41. package/dist/editor/searchPanel.js.map +1 -1
  42. package/dist/editor/selection.d.ts +19 -3
  43. package/dist/editor/selection.d.ts.map +1 -1
  44. package/dist/editor/selection.js +188 -37
  45. package/dist/editor/selection.js.map +1 -1
  46. package/dist/editor/{quickEdit.d.ts → selectionAction.d.ts} +8 -8
  47. package/dist/editor/selectionAction.d.ts.map +1 -0
  48. package/dist/editor/{quickEdit.js → selectionAction.js} +18 -18
  49. package/dist/editor/selectionAction.js.map +1 -0
  50. package/dist/editor/sprite.d.ts +3 -2
  51. package/dist/editor/sprite.d.ts.map +1 -1
  52. package/dist/editor/sprite.js +9 -2
  53. package/dist/editor/sprite.js.map +1 -1
  54. package/dist/editor/textDocument.d.ts +4 -4
  55. package/dist/editor/textDocument.d.ts.map +1 -1
  56. package/dist/editor/textDocument.js +7 -7
  57. package/dist/editor/textDocument.js.map +1 -1
  58. package/dist/editor/tokenzier.js +11 -3
  59. package/dist/editor/tokenzier.js.map +1 -1
  60. package/dist/editor/utils.d.ts +3 -1
  61. package/dist/editor/utils.d.ts.map +1 -1
  62. package/dist/editor/utils.js +16 -1
  63. package/dist/editor/utils.js.map +1 -1
  64. package/dist/highlighter/shared_highlighter.js +3 -29
  65. package/dist/highlighter/shared_highlighter.js.map +1 -1
  66. package/dist/highlighter/themes/attachResolvedThemes.js +4 -3
  67. package/dist/highlighter/themes/attachResolvedThemes.js.map +1 -1
  68. package/dist/highlighter/themes/cleanUpResolvedThemes.js +3 -2
  69. package/dist/highlighter/themes/cleanUpResolvedThemes.js.map +1 -1
  70. package/dist/highlighter/themes/constants.d.ts +1 -7
  71. package/dist/highlighter/themes/constants.d.ts.map +1 -1
  72. package/dist/highlighter/themes/constants.js +1 -4
  73. package/dist/highlighter/themes/constants.js.map +1 -1
  74. package/dist/highlighter/themes/getResolvedOrResolveTheme.js +2 -2
  75. package/dist/highlighter/themes/getResolvedOrResolveTheme.js.map +1 -1
  76. package/dist/highlighter/themes/getResolvedThemes.js +2 -8
  77. package/dist/highlighter/themes/getResolvedThemes.js.map +1 -1
  78. package/dist/highlighter/themes/hasResolvedThemes.js +2 -3
  79. package/dist/highlighter/themes/hasResolvedThemes.js.map +1 -1
  80. package/dist/highlighter/themes/registerCustomCSSVariableTheme.js +1 -1
  81. package/dist/highlighter/themes/registerCustomTheme.d.ts +5 -3
  82. package/dist/highlighter/themes/registerCustomTheme.d.ts.map +1 -1
  83. package/dist/highlighter/themes/registerCustomTheme.js +15 -5
  84. package/dist/highlighter/themes/registerCustomTheme.js.map +1 -1
  85. package/dist/highlighter/themes/resolveTheme.js +6 -27
  86. package/dist/highlighter/themes/resolveTheme.js.map +1 -1
  87. package/dist/highlighter/themes/resolveThemes.js +5 -12
  88. package/dist/highlighter/themes/resolveThemes.js.map +1 -1
  89. package/dist/highlighter/themes/themeResolution.d.ts +8 -0
  90. package/dist/highlighter/themes/themeResolution.d.ts.map +1 -0
  91. package/dist/highlighter/themes/themeResolution.js +22 -0
  92. package/dist/highlighter/themes/themeResolution.js.map +1 -0
  93. package/dist/highlighter/themes/themeResolver.d.ts +8 -0
  94. package/dist/highlighter/themes/themeResolver.d.ts.map +1 -0
  95. package/dist/highlighter/themes/themeResolver.js +8 -0
  96. package/dist/highlighter/themes/themeResolver.js.map +1 -0
  97. package/dist/index.d.ts +4 -4
  98. package/dist/index.js +3 -3
  99. package/dist/react/index.d.ts +2 -2
  100. package/dist/react/jsx.d.ts.map +1 -1
  101. package/dist/react/utils/useFileDiffInstance.js +1 -0
  102. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  103. package/dist/renderers/DiffHunksRenderer.d.ts +4 -1
  104. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  105. package/dist/renderers/DiffHunksRenderer.js +134 -10
  106. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  107. package/dist/renderers/FileRenderer.d.ts +2 -2
  108. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  109. package/dist/renderers/FileRenderer.js +5 -5
  110. package/dist/renderers/FileRenderer.js.map +1 -1
  111. package/dist/ssr/index.d.ts +2 -2
  112. package/dist/types.d.ts +9 -7
  113. package/dist/types.d.ts.map +1 -1
  114. package/dist/utils/getHighlighterThemeStyles.js +16 -12
  115. package/dist/utils/getHighlighterThemeStyles.js.map +1 -1
  116. package/dist/utils/parsePatchFiles.js +93 -4
  117. package/dist/utils/parsePatchFiles.js.map +1 -1
  118. package/dist/utils/updateDiffHunks.d.ts +13 -0
  119. package/dist/utils/updateDiffHunks.d.ts.map +1 -0
  120. package/dist/utils/updateDiffHunks.js +171 -0
  121. package/dist/utils/updateDiffHunks.js.map +1 -0
  122. package/dist/utils/virtualDiffLayout.d.ts +2 -1
  123. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  124. package/dist/utils/virtualDiffLayout.js +9 -1
  125. package/dist/utils/virtualDiffLayout.js.map +1 -1
  126. package/dist/worker/worker-portable.js +727 -22
  127. package/dist/worker/worker-portable.js.map +1 -1
  128. package/dist/worker/worker.js +23 -15
  129. package/dist/worker/worker.js.map +1 -1
  130. package/package.json +2 -1
  131. package/dist/editor/quickEdit.d.ts.map +0 -1
  132. package/dist/editor/quickEdit.js.map +0 -1
@@ -2,36 +2,29 @@ import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
2
2
  import { isMoveCursorShortcut, isPrimaryModifier, isSafari } from "./platform.js";
3
3
  import { resolveEditorCommandFromKeyboardEvent } from "./command.js";
4
4
  import editor_default from "./editor2.js";
5
- import { applyDocumentChangeToLineAnnotations } from "./lineAnnotations.js";
6
- import { SVGSpriteSheet } from "./sprite.js";
7
- import { addEventListener, debounce, extend, h, round } from "./utils.js";
8
- import { QuickEditWidget } from "./quickEdit.js";
5
+ import { addEventListener, clampDomOffset, extend, getLineNumberAttr, h, round } from "./utils.js";
6
+ import { applyDocumentChangeToLineAnnotations, renderLineAnnotations } from "./lineAnnotations.js";
7
+ import { DirectionBackward, DirectionForward, DirectionNone, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNexMatch, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectionAnchor, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, resolveIndentEdits, selectionIntersects } from "./selection.js";
8
+ import { MarkerManager, markerSeverityDatasetKey } from "./marker.js";
9
+ import { createSpriteElement } from "./sprite.js";
9
10
  import { SearchPanelWidget } from "./searchPanel.js";
10
- import { DirectionBackward, DirectionForward, DirectionNone, applyDeleteHardLineForwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNexMatch, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectionAnchor, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, resolveIndentEdits, selectionIntersects } from "./selection.js";
11
+ import { SelectionActionWidget } from "./selectionAction.js";
11
12
  import { TextDocument } from "./textDocument.js";
12
13
  import { Metrics, getExpandedAsciiTextColumns, getUnicodeMeasurementOffsets, snapTextOffsetToUnicodeBoundary } from "./textMeasure.js";
13
14
  import { EditorTokenizer, renderLineTokens } from "./tokenzier.js";
14
15
 
15
16
  //#region src/editor/editor.ts
16
- function clampDomOffset(node, offset) {
17
- if (node.nodeType === 3) {
18
- const length = node.textContent?.length ?? 0;
19
- return Math.max(0, Math.min(offset, length));
20
- }
21
- if (node.nodeType === 1) return Math.max(0, Math.min(offset, node.childNodes.length));
22
- return 0;
23
- }
24
17
  var Editor = class {
25
18
  #options;
26
19
  #wrap = false;
27
20
  #metrics = new Metrics();
28
21
  #tokenizer;
22
+ #markerManager;
29
23
  #editorEventDisposes;
30
24
  #globalEventDisposes;
31
- #mouseUpDisposes;
25
+ #selectEventDisposes;
32
26
  #detach;
33
27
  #fileInstance;
34
- #fileInstanceType;
35
28
  #fileContents;
36
29
  #lineAnnotations;
37
30
  #textDocument;
@@ -41,17 +34,19 @@ var Editor = class {
41
34
  #contentWidthCache;
42
35
  #lineYCache = /* @__PURE__ */ new Map();
43
36
  #wrapLineOffsetsCache = /* @__PURE__ */ new Map();
44
- #lastCharX;
37
+ #lastAccessedLineElement;
38
+ #lastAccessedCharX;
45
39
  #globalStyleElement;
46
40
  #editorStyleElement;
47
41
  #themeStyleElement;
48
42
  #spriteElement;
49
43
  #fileContainer;
44
+ #gutterElement;
50
45
  #contentElement;
51
46
  #overlayElement;
52
47
  #primaryCaretElement;
53
- #selectionElements;
54
- #quickEdit;
48
+ #overlayElements;
49
+ #selectionAction;
55
50
  #searchPanel;
56
51
  #resizeObserver;
57
52
  #shouldIgnoreSelectionChange = false;
@@ -67,11 +62,8 @@ var Editor = class {
67
62
  #scrollingToLineChar;
68
63
  #scrollingToLineNoFocus = false;
69
64
  #retainSearchPanelFocus = false;
70
- #emitChange = debounce((fileContents, lineAnnotations) => {
71
- this.#options.onChange?.(fileContents, lineAnnotations);
72
- }, 500);
73
65
  #onDeferTokenize = (lines, themeType) => {
74
- this.#fileInstance?.applyLineChange?.(lines, themeType);
66
+ this.#fileInstance?.updateRenderCache(lines, themeType);
75
67
  if (this.#renderRange !== void 0 && this.#renderRange.totalLines !== Infinity) {
76
68
  const { startingLine, totalLines } = this.#renderRange;
77
69
  const endLine = Math.min(startingLine + totalLines, this.#textDocument?.lineCount ?? 0);
@@ -85,14 +77,15 @@ var Editor = class {
85
77
  this.#options = options;
86
78
  }
87
79
  edit(component) {
88
- const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, lineHoverHighlight,...rest } = component.options;
89
- if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || expandUnchanged !== true && Object.hasOwn(component, "fileDiff") || lineHoverHighlight !== "disabled") {
80
+ const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, diffStyle, lineHoverHighlight,...rest } = component.options;
81
+ if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || expandUnchanged !== true && Object.hasOwn(component, "fileDiff") || diffStyle === "unified" || lineHoverHighlight !== "disabled") {
90
82
  component.setOptions({
91
83
  ...rest,
92
84
  useTokenTransformer: true,
93
85
  enableGutterUtility: false,
94
86
  enableLineSelection: false,
95
87
  expandUnchanged: true,
88
+ diffStyle: "split",
96
89
  lineHoverHighlight: "disabled"
97
90
  });
98
91
  component.rerender();
@@ -102,59 +95,34 @@ var Editor = class {
102
95
  this.#detach = component.attachEditor(this);
103
96
  return () => this.cleanUp();
104
97
  }
105
- syncToRenderedView(highlighter, fileInstanceType, fileContainer, fileContents, lineAnnotations, renderRange) {
98
+ syncToRenderedView(highlighter, fileContainer, fileContents, didFileChange, lineAnnotations, renderRange) {
106
99
  const shadowRoot = fileContainer.shadowRoot;
107
100
  if (shadowRoot == null) {
108
101
  console.error("[editor] Could not find the shadow root.");
109
102
  return;
110
103
  }
111
104
  let codeElement;
105
+ let gutterEl;
106
+ let contentEl;
112
107
  for (const el of shadowRoot.querySelectorAll("[data-code]")) if (el.dataset.deletions === void 0) {
113
108
  codeElement = el;
114
- break;
115
- }
116
- if (codeElement === void 0) return;
117
- const contentEl = codeElement.children[1];
118
- if (contentEl === void 0 || contentEl.dataset.content === void 0) return;
119
- this.#fileInstanceType = fileInstanceType;
120
- this.#wrap = this.#fileInstance?.options.overflow === "wrap";
121
- if (fileInstanceType === "diff" || lineAnnotations !== void 0 && lineAnnotations.length > 0) {
122
- let startingLine;
123
- let endLine;
124
- for (const child of contentEl.children) {
125
- const el = child;
126
- const line = el.dataset.line;
127
- const lineType = el.dataset.lineType;
128
- if (line !== void 0) {
129
- const lineNumber = parseInt(line, 10);
130
- if (!Number.isNaN(lineNumber)) {
131
- const lineIndex = lineNumber - 1;
132
- startingLine ??= lineIndex;
133
- endLine = lineIndex;
134
- }
135
- }
136
- if (lineType === void 0 || !isLineEditable(lineType)) el.contentEditable = "false";
137
- }
138
- if (endLine !== void 0 && renderRange !== void 0) {
139
- const { startingLine: startingLine$1, totalLines } = renderRange;
140
- endLine = Math.max(endLine, startingLine$1 + totalLines);
109
+ for (const child of el.children) {
110
+ const el$1 = child;
111
+ const { gutter, content } = el$1.dataset;
112
+ if (gutter !== void 0) gutterEl = el$1;
113
+ else if (content !== void 0) contentEl = el$1;
141
114
  }
142
- if (startingLine !== void 0 && endLine !== void 0) renderRange = {
143
- startingLine,
144
- totalLines: endLine - startingLine,
145
- bufferBefore: 0,
146
- bufferAfter: 0
147
- };
115
+ break;
148
116
  }
117
+ if (codeElement === void 0 || contentEl === void 0) return;
149
118
  if (this.#fileContainer !== fileContainer) {
150
119
  this.#fileContainer = fileContainer;
151
- const codePaddingTop = parseInt(getComputedStyle(codeElement).paddingTop.slice(0, -2), 10);
152
- this.#codePaddingTop = Number.isNaN(codePaddingTop) ? 0 : codePaddingTop;
153
120
  if (this.#globalStyleElement !== void 0) fileContainer.appendChild(this.#globalStyleElement);
154
121
  if (this.#editorStyleElement !== void 0) shadowRoot.appendChild(this.#editorStyleElement);
155
122
  if (this.#themeStyleElement !== void 0) shadowRoot.appendChild(this.#themeStyleElement);
123
+ if (this.#spriteElement !== void 0) shadowRoot.prepend(this.#spriteElement);
156
124
  }
157
- if (this.#textDocument === void 0 || this.#fileContents === void 0 || this.#fileContents.name !== fileContents.name || this.#fileContents.lang !== fileContents.lang) {
125
+ if (this.#textDocument === void 0 || this.#fileContents === void 0 || didFileChange) {
158
126
  const textDocument = new TextDocument(fileContents.name, fileContents.contents, fileContents.lang ?? getFiletypeFromFileName(fileContents.name));
159
127
  this.#fileContents = fileContents;
160
128
  this.#textDocument = textDocument;
@@ -169,21 +137,26 @@ var Editor = class {
169
137
  },
170
138
  __debug: this.#options.__debug
171
139
  });
172
- this.#shouldIgnoreSelectionChange = false;
173
- this.#selectionElements?.forEach((el) => el.remove());
174
- this.#selectionElements?.clear();
175
140
  this.#fileInstance?.setSelectedLines(null);
176
- this.#selectionElements = void 0;
141
+ this.#shouldIgnoreSelectionChange = false;
142
+ this.#overlayElements?.forEach((el) => el.remove());
143
+ this.#overlayElements?.clear();
144
+ this.#overlayElements = void 0;
177
145
  this.#selections = void 0;
178
146
  this.#scrollingToLine = void 0;
179
147
  this.#reservedSelections = void 0;
180
148
  this.#searchPanel?.cleanup();
181
149
  this.#searchPanel = void 0;
182
- this.#quickEdit?.cleanup();
183
- this.#quickEdit = void 0;
150
+ this.#selectionAction?.cleanup();
151
+ this.#selectionAction = void 0;
184
152
  }
185
153
  if (this.#contentElement !== contentEl) {
186
- this.#metrics.init(contentEl);
154
+ if (this.#contentElement !== void 0 && this.#options.__debug === true) console.log("[diffs/editor] full re-render triggered !!!");
155
+ const codePaddingTop = parseInt(getComputedStyle(codeElement).paddingTop.slice(0, -2), 10);
156
+ this.#codePaddingTop = Number.isNaN(codePaddingTop) ? 0 : codePaddingTop;
157
+ this.#gutterWidthCache = void 0;
158
+ this.#contentWidthCache = void 0;
159
+ this.#gutterElement = gutterEl;
187
160
  this.#contentElement = extend(contentEl, {
188
161
  contentEditable: "true",
189
162
  role: "textbox",
@@ -195,11 +168,14 @@ var Editor = class {
195
168
  translate: false
196
169
  });
197
170
  if (this.#overlayElement !== void 0) contentEl.after(this.#overlayElement);
198
- this.#listenContentElement(contentEl);
171
+ this.#metrics.init(contentEl);
172
+ this.#listenContentElement(contentEl, gutterEl);
199
173
  }
200
174
  this.#lineYCache.clear();
201
175
  this.#wrapLineOffsetsCache.clear();
202
- this.#lastCharX = void 0;
176
+ this.#lastAccessedLineElement = void 0;
177
+ this.#lastAccessedCharX = void 0;
178
+ this.#wrap = this.#fileInstance?.options.overflow === "wrap";
203
179
  this.#lineAnnotations = lineAnnotations;
204
180
  this.#renderRange = renderRange;
205
181
  this.#tokenizer?.prebuildStateStack(renderRange);
@@ -207,19 +183,15 @@ var Editor = class {
207
183
  this.setSelections(this.#initSelections);
208
184
  this.#scrollToPrimaryCaret();
209
185
  this.#initSelections = void 0;
210
- } else if (this.#selections !== void 0 && this.#selections.length > 0) this.#updateSelections(this.#selections);
186
+ } else if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerManager !== void 0) this.#updateSelections(this.#selections ?? []);
211
187
  if (this.#options.__debug === true && renderRange !== void 0) {
212
188
  const { startingLine, totalLines } = renderRange;
213
189
  console.log("[diffs/editor] render file:", fileContents.name, "RenderRange:", startingLine + "-" + (startingLine + totalLines), "of", this.#textDocument.lineCount, "lines");
214
190
  }
215
- if (this.#scrollingToLine !== void 0) {
216
- this.#scrollToLine(this.#scrollingToLine, this.#scrollingToLineChar, this.#scrollingToLineNoFocus);
217
- this.#scrollingToLine = void 0;
218
- this.#scrollingToLineChar = void 0;
219
- this.#scrollingToLineNoFocus = false;
220
- } else if (this.#selections !== void 0 && this.#selections.length > 0 && !this.#retainSearchPanelFocus) this.focus({ preventScroll: true });
191
+ if (this.#scrollingToLine !== void 0) this.#scrollToLine(this.#scrollingToLine, this.#scrollingToLineChar, this.#scrollingToLineNoFocus);
192
+ else if (this.#selections !== void 0 && this.#selections.length > 0 && !this.#retainSearchPanelFocus) this.focus({ preventScroll: true });
221
193
  if (this.#retainSearchPanelFocus) this.#searchPanel?.focus();
222
- if (this.#quickEdit !== void 0 && this.#isLineVisible(this.#quickEdit.line) && this.#contentElement !== void 0) this.#quickEdit.render(this.#contentElement);
194
+ if (this.#selectionAction !== void 0 && this.#isLineVisible(this.#selectionAction.line) && this.#contentElement !== void 0) this.#selectionAction.render(this.#contentElement);
223
195
  }
224
196
  postponeBackgroundTokenizeToNextFrame() {
225
197
  const tokenizer = this.#tokenizer;
@@ -246,6 +218,26 @@ var Editor = class {
246
218
  this.#scrollToPrimaryCaret();
247
219
  } else this.#initSelections = selections;
248
220
  }
221
+ setMarkers(markers) {
222
+ const textDocument = this.#textDocument;
223
+ if (textDocument === void 0) throw new Error("Text document is not initialized");
224
+ if (markers.length === 0) {
225
+ this.#markerManager?.cleanup();
226
+ this.#markerManager = void 0;
227
+ this.#updateSelections(this.#selections ?? []);
228
+ return;
229
+ }
230
+ this.#markerManager ??= new MarkerManager({
231
+ getLineHeight: () => this.#metrics.lineHeight,
232
+ getFileContainer: () => this.#fileContainer,
233
+ getCharX: (line, character) => this.#getCharX(line, character),
234
+ getLineY: (line) => this.#getLineY(line),
235
+ isMouseDown: () => this.#isContentMouseDown || this.#isGutterMouseDown
236
+ });
237
+ this.#markerManager.setMarkers(markers, textDocument);
238
+ if (this.#contentElement !== void 0) this.#markerManager.listenHover(this.#contentElement);
239
+ this.#updateSelections(this.#selections ?? []);
240
+ }
249
241
  focus(options) {
250
242
  const preventScroll = options?.preventScroll ?? false;
251
243
  const primarySelection = this.#selections?.at(-1);
@@ -261,6 +253,10 @@ var Editor = class {
261
253
  this.#globalEventDisposes = void 0;
262
254
  this.#editorEventDisposes?.forEach((dispose) => dispose());
263
255
  this.#editorEventDisposes = void 0;
256
+ this.#selectEventDisposes?.forEach((dispose) => dispose());
257
+ this.#selectEventDisposes = void 0;
258
+ this.#markerManager?.cleanup();
259
+ this.#markerManager = void 0;
264
260
  this.#detach?.();
265
261
  this.#detach = void 0;
266
262
  this.#fileInstance?.setSelectedLines(null);
@@ -273,7 +269,8 @@ var Editor = class {
273
269
  this.#contentWidthCache = void 0;
274
270
  this.#lineYCache.clear();
275
271
  this.#wrapLineOffsetsCache.clear();
276
- this.#lastCharX = void 0;
272
+ this.#lastAccessedLineElement = void 0;
273
+ this.#lastAccessedCharX = void 0;
277
274
  this.#globalStyleElement?.remove();
278
275
  this.#globalStyleElement = void 0;
279
276
  this.#editorStyleElement?.remove();
@@ -283,19 +280,18 @@ var Editor = class {
283
280
  this.#spriteElement?.remove();
284
281
  this.#spriteElement = void 0;
285
282
  this.#fileContainer = void 0;
283
+ this.#gutterElement = void 0;
286
284
  this.#contentElement?.removeAttribute("contentEditable");
287
285
  this.#contentElement = void 0;
288
286
  this.#overlayElement?.remove();
289
287
  this.#overlayElement = void 0;
290
- this.#primaryCaretElement?.remove();
288
+ this.#overlayElements?.forEach((el) => el.remove());
289
+ this.#overlayElements = void 0;
291
290
  this.#primaryCaretElement = void 0;
292
- this.#selectionElements?.forEach((el) => el.remove());
293
- this.#selectionElements?.clear();
294
- this.#selectionElements = void 0;
295
291
  this.#searchPanel?.cleanup();
296
292
  this.#searchPanel = void 0;
297
- this.#quickEdit?.cleanup();
298
- this.#quickEdit = void 0;
293
+ this.#selectionAction?.cleanup();
294
+ this.#selectionAction = void 0;
299
295
  this.#resizeObserver?.disconnect();
300
296
  this.#resizeObserver = void 0;
301
297
  this.#shouldIgnoreSelectionChange = false;
@@ -318,10 +314,7 @@ var Editor = class {
318
314
  textContent: editor_default
319
315
  });
320
316
  this.#themeStyleElement = h("style", { dataset: "editorThemeCss" });
321
- const fragment = document.createElement("div");
322
- fragment.innerHTML = SVGSpriteSheet;
323
- const sprite = fragment.firstElementChild;
324
- this.#spriteElement = sprite instanceof SVGSVGElement ? sprite : void 0;
317
+ this.#spriteElement = createSpriteElement();
325
318
  this.#overlayElement = h("div", { dataset: "editorOverlay" });
326
319
  this.#globalEventDisposes = [
327
320
  addEventListener(document, "selectionchange", () => {
@@ -345,8 +338,8 @@ var Editor = class {
345
338
  }, { passive: true }),
346
339
  addEventListener(document, "pointerup", (e) => {
347
340
  if (e.pointerType !== "mouse") return;
348
- this.#mouseUpDisposes?.forEach((dispose) => dispose());
349
- this.#mouseUpDisposes = void 0;
341
+ this.#selectEventDisposes?.forEach((dispose) => dispose());
342
+ this.#selectEventDisposes = void 0;
350
343
  if (this.#isGutterMouseDown) {
351
344
  this.#isGutterMouseDown = false;
352
345
  this.#focus();
@@ -356,8 +349,8 @@ var Editor = class {
356
349
  this.#shiftKeyPressed = false;
357
350
  this.#selectionStart = void 0;
358
351
  this.#reservedSelections = void 0;
359
- this.#selectionElements?.forEach((el, key) => {
360
- if (key.startsWith("quickEditIcon-")) el.dataset.visible = "true";
352
+ this.#overlayElements?.forEach((el, key) => {
353
+ if (key.startsWith("selectionActionIcon-")) el.dataset.visible = "true";
361
354
  });
362
355
  }, { passive: true }),
363
356
  addEventListener(document, "keydown", (e) => {
@@ -368,17 +361,17 @@ var Editor = class {
368
361
  }, { passive: true })
369
362
  ];
370
363
  }
371
- #listenContentElement(contentEl) {
372
- const gutterEl = contentEl.previousElementSibling;
364
+ #listenContentElement(contentEl, gutterEl) {
373
365
  const targetIsContentElement = (e) => {
374
366
  const target = e.composedPath()[0];
375
- return target === contentEl || contentEl.contains(target);
367
+ return target !== void 0 && (target === contentEl || contentEl.contains(target));
376
368
  };
377
369
  this.#editorEventDisposes?.forEach((dispose) => dispose());
378
370
  this.#editorEventDisposes = [
379
371
  addEventListener(contentEl, "pointerdown", (e) => {
380
372
  if (e.pointerType !== "mouse") return;
381
- if (isSafari() && this.#lineAnnotations !== void 0 && this.#lineAnnotations.length > 0) this.#mouseUpDisposes = [...contentEl.querySelectorAll("[data-line-annotation]")].map((el) => [addEventListener(el, "mouseenter", () => {
373
+ this.#markerManager?.removePopup();
374
+ if (isSafari() && this.#lineAnnotations !== void 0 && this.#lineAnnotations.length > 0) this.#selectEventDisposes = [...contentEl.querySelectorAll("[data-line-annotation]")].map((el) => [addEventListener(el, "mouseenter", () => {
382
375
  this.#shouldIgnoreSelectionChange = true;
383
376
  }), addEventListener(el, "mouseleave", () => {
384
377
  this.#shouldIgnoreSelectionChange = false;
@@ -405,8 +398,8 @@ var Editor = class {
405
398
  this.#searchPanel?.cleanup();
406
399
  this.#searchPanel = void 0;
407
400
  this.#retainSearchPanelFocus = false;
408
- this.#quickEdit?.cleanup();
409
- this.#quickEdit = void 0;
401
+ this.#selectionAction?.cleanup();
402
+ this.#selectionAction = void 0;
410
403
  if (this.#selections !== void 0 && this.#selections.length > 0) {
411
404
  const primarySelection = this.#selections.at(-1);
412
405
  if (!isCollapsedSelection(primarySelection) || this.#selections.length > 1) {
@@ -459,6 +452,10 @@ var Editor = class {
459
452
  e.preventDefault();
460
453
  this.#handleInput(e.inputType, e.data);
461
454
  }),
455
+ addEventListener(contentEl, "drop", (e) => {
456
+ if (!targetIsContentElement(e)) return;
457
+ e.preventDefault();
458
+ }),
462
459
  addEventListener(contentEl, "compositionstart", (e) => {
463
460
  if (!targetIsContentElement(e)) return;
464
461
  this.#shouldIgnoreSelectionChange = true;
@@ -469,51 +466,53 @@ var Editor = class {
469
466
  this.#handleInput("insertText", e.data);
470
467
  }, { passive: true })
471
468
  ];
472
- if (gutterEl !== null && gutterEl.dataset.gutter !== void 0) this.#editorEventDisposes.push(addEventListener(gutterEl, "pointerdown", (e) => {
473
- let target = e.composedPath()[0];
474
- if (target?.dataset.lineNumberContent !== void 0) target = target.parentElement ?? void 0;
475
- const textDocument = this.#textDocument;
476
- if (target === void 0 || textDocument === void 0) return;
477
- const columnNumber = target.dataset.columnNumber;
478
- const lineType = target.dataset.lineType;
479
- if (columnNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) return;
480
- const lineNumber = parseInt(columnNumber, 10);
481
- if (Number.isNaN(lineNumber)) return;
482
- const line = lineNumber - 1;
483
- const selection = {
484
- start: {
485
- line,
486
- character: 0
487
- },
488
- end: {
489
- line,
490
- character: textDocument.getLineText(line).length
491
- },
492
- direction: DirectionForward
469
+ if (gutterEl !== void 0) {
470
+ const resolveGutterTarget = (eventTarget, includeContentLine = false) => {
471
+ let target = eventTarget;
472
+ if (target?.dataset.lineNumberContent !== void 0) target = target.parentElement ?? void 0;
473
+ else if (includeContentLine && target?.tagName === "SPAN") target = target.closest("[data-line]");
474
+ return target;
493
475
  };
494
- this.#isGutterMouseDown = true;
495
- this.#selectionStart = selection;
496
- this.#updateSelections([selection]);
497
- this.#focus(selection.end);
498
- this.#mouseUpDisposes = [addEventListener(document, "mousemove", (e$1) => {
499
- let target$1 = e$1.composedPath()[0];
500
- if (target$1?.dataset.lineNumberContent !== void 0) target$1 = target$1?.parentElement ?? void 0;
501
- else if (target$1?.tagName === "SPAN") target$1 = target$1?.closest("[data-line]");
502
- if (target$1 === void 0) return;
503
- const line$1 = target$1.dataset.columnNumber ?? target$1.dataset.line;
504
- const lineType$1 = target$1.dataset.lineType;
505
- if (this.#isGutterMouseDown && this.#textDocument !== void 0 && line$1 !== void 0 && lineType$1 !== void 0 && isLineEditable(lineType$1)) {
506
- const lineNumber$1 = parseInt(line$1, 10);
507
- if (Number.isNaN(lineNumber$1)) return;
508
- const lineIndex = lineNumber$1 - 1;
476
+ const resolveEditableLine = (target) => {
477
+ if (target === void 0) return;
478
+ const lineType = target.dataset.lineType;
479
+ const lineNumber = getLineNumberAttr(target) ?? getLineNumberAttr(target, "columnNumber");
480
+ if (lineNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) return;
481
+ return lineNumber - 1;
482
+ };
483
+ this.#editorEventDisposes.push(addEventListener(gutterEl, "pointerdown", (e) => {
484
+ const textDocument = this.#textDocument;
485
+ const lineIndex = resolveEditableLine(resolveGutterTarget(e.composedPath()[0]));
486
+ if (lineIndex === void 0 || textDocument === void 0) return;
487
+ this.#markerManager?.removePopup();
488
+ const selection = {
489
+ start: {
490
+ line: lineIndex,
491
+ character: 0
492
+ },
493
+ end: {
494
+ line: lineIndex,
495
+ character: textDocument.getLineText(lineIndex).length
496
+ },
497
+ direction: DirectionForward
498
+ };
499
+ this.#isGutterMouseDown = true;
500
+ this.#selectionStart = selection;
501
+ this.#updateSelections([selection]);
502
+ this.#focus(selection.end);
503
+ this.#selectEventDisposes = [addEventListener(document, "mousemove", (e$1) => {
504
+ if (!this.#isGutterMouseDown) return;
505
+ const textDocument$1 = this.#textDocument;
506
+ const lineIndex$1 = resolveEditableLine(resolveGutterTarget(e$1.composedPath()[0], true));
507
+ if (lineIndex$1 === void 0 || textDocument$1 === void 0) return;
509
508
  let selection$1 = {
510
509
  start: {
511
- line: lineIndex,
510
+ line: lineIndex$1,
512
511
  character: 0
513
512
  },
514
513
  end: {
515
- line: lineIndex,
516
- character: this.#textDocument.getLineText(lineIndex).length
514
+ line: lineIndex$1,
515
+ character: textDocument$1.getLineText(lineIndex$1).length
517
516
  },
518
517
  direction: DirectionForward
519
518
  };
@@ -521,9 +520,10 @@ var Editor = class {
521
520
  else this.#selectionStart = selection$1;
522
521
  this.#updateSelections([selection$1]);
523
522
  this.#focus(selection$1.end);
524
- }
525
- }, { passive: true })];
526
- }, { passive: true }));
523
+ }, { passive: true })];
524
+ }, { passive: true }));
525
+ }
526
+ this.#markerManager?.listenHover(contentEl);
527
527
  this.#resizeObserver?.disconnect();
528
528
  this.#resizeObserver = new ResizeObserver(() => {
529
529
  this.#handleLayoutResize();
@@ -540,17 +540,16 @@ var Editor = class {
540
540
  break;
541
541
  case "findNextMatch": {
542
542
  const selections = this.#selections;
543
- const textDocument$1 = this.#textDocument;
544
- if (selections === void 0 || textDocument$1 === void 0) break;
543
+ if (selections === void 0) break;
545
544
  if (selections.some(isCollapsedSelection)) {
546
545
  const expanded = selections.map((sel) => {
547
- if (isCollapsedSelection(sel)) return expandCollapsedSelectionToWord(textDocument$1, sel);
546
+ if (isCollapsedSelection(sel)) return expandCollapsedSelectionToWord(textDocument, sel);
548
547
  return sel;
549
548
  });
550
549
  this.#updateSelections(expanded);
551
550
  this.focus();
552
551
  } else {
553
- const nextMatch = findNexMatch(textDocument$1, selections);
552
+ const nextMatch = findNexMatch(textDocument, selections);
554
553
  if (nextMatch !== void 0) {
555
554
  this.#updateSelections(nextMatch);
556
555
  this.#scrollToPrimaryCaret();
@@ -628,66 +627,49 @@ var Editor = class {
628
627
  const gutterWidthChanged = this.#getGutterWidth() !== prevGutterWidth;
629
628
  const contentWidthChanged = this.#getContentWidth() !== prevContentWidth;
630
629
  if (!gutterWidthChanged && !contentWidthChanged) return;
631
- this.#lastCharX = void 0;
630
+ this.#lastAccessedLineElement = void 0;
631
+ this.#lastAccessedCharX = void 0;
632
632
  if (contentWidthChanged && (this.#wrap || lineAnnotations > 0)) {
633
633
  this.#lineYCache.clear();
634
634
  this.#wrapLineOffsetsCache.clear();
635
635
  }
636
- if (this.#selections !== void 0) {
637
- this.#updateSelections(this.#selections);
638
- this.focus();
636
+ if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerManager !== void 0) {
637
+ this.#updateSelections(this.#selections ?? []);
638
+ if (this.#selections !== void 0) this.focus();
639
639
  }
640
+ this.#markerManager?.removePopup();
640
641
  }
641
642
  #rerender(change, newLineAnnotations, renderRange = this.#renderRange, shouldUpdateBuffer) {
642
643
  const tokenizer = this.#tokenizer;
643
- const component = this.#fileInstance;
644
+ const fileInstance = this.#fileInstance;
644
645
  const fileContents = this.#fileContents;
645
646
  const textDocument = this.#textDocument;
647
+ const gutterEl = this.#gutterElement;
646
648
  const contentEl = this.#contentElement;
647
- const gutterEl = this.#contentElement?.previousElementSibling ?? void 0;
648
- if (tokenizer === void 0 || component === void 0 || fileContents === void 0 || textDocument === void 0 || contentEl === void 0 || gutterEl === void 0 || !(gutterEl instanceof HTMLElement) || gutterEl.dataset.gutter === void 0) return;
649
+ if (tokenizer === void 0 || fileInstance === void 0 || fileContents === void 0 || textDocument === void 0 || contentEl === void 0) return;
649
650
  tokenizer.stopBackgroundTokenize();
650
651
  const t = performance.now();
651
- const isFileDiff = this.#fileInstanceType === "diff";
652
652
  const dirtyLines = tokenizer.tokenize(change, renderRange);
653
653
  const t2 = performance.now();
654
654
  if (dirtyLines.size > 0) {
655
655
  const children = contentEl.children;
656
656
  const dirtyLineIndexes = new Set(dirtyLines.keys());
657
- if (isFileDiff) for (const child of children) {
658
- const el = child;
659
- const line = el.dataset.line;
660
- if (line !== void 0) {
661
- const lineNumber = parseInt(line, 10);
662
- if (!Number.isNaN(lineNumber)) {
657
+ const startingLine = renderRange?.startingLine ?? 0;
658
+ for (let i = change.startLine - startingLine; i < children.length; i++) {
659
+ const child = children[i];
660
+ if (child !== void 0) {
661
+ const lineNumber = getLineNumberAttr(child);
662
+ if (lineNumber !== void 0) {
663
663
  const lineIndex = lineNumber - 1;
664
- const tokens = dirtyLines.get(lineIndex);
665
- if (tokens !== void 0) {
666
- el.replaceChildren(...renderLineTokens(tokens, tokenizer.themeType));
664
+ if (dirtyLines.has(lineIndex)) {
665
+ const tokens = dirtyLines.get(lineIndex);
666
+ child.replaceChildren(...renderLineTokens(tokens, tokenizer.themeType));
667
667
  dirtyLineIndexes.delete(lineIndex);
668
668
  if (dirtyLineIndexes.size === 0) break;
669
669
  }
670
670
  }
671
671
  }
672
672
  }
673
- else {
674
- const startingLine = renderRange?.startingLine ?? 0;
675
- for (let i = change.startLine - startingLine; i < children.length; i++) {
676
- const child = children[i];
677
- if (child !== void 0 && child.dataset.line !== void 0) {
678
- const lineNumber = parseInt(child.dataset.line, 10);
679
- if (!Number.isNaN(lineNumber)) {
680
- const lineIndex = lineNumber - 1;
681
- if (dirtyLines.has(lineIndex)) {
682
- const tokens = dirtyLines.get(lineIndex);
683
- child.replaceChildren(...renderLineTokens(tokens, tokenizer.themeType));
684
- dirtyLineIndexes.delete(lineIndex);
685
- if (dirtyLineIndexes.size === 0) break;
686
- }
687
- }
688
- }
689
- }
690
- }
691
673
  if (dirtyLineIndexes.size > 0) for (const lineIndex of dirtyLineIndexes) {
692
674
  const tokens = dirtyLines.get(lineIndex);
693
675
  const lineNumber = String(lineIndex + 1);
@@ -699,7 +681,7 @@ var Editor = class {
699
681
  },
700
682
  children: renderLineTokens(tokens, tokenizer.themeType)
701
683
  }, contentEl);
702
- h("div", {
684
+ if (gutterEl !== void 0) h("div", {
703
685
  dataset: {
704
686
  lineType: "context",
705
687
  columnNumber: lineNumber,
@@ -712,25 +694,30 @@ var Editor = class {
712
694
  }, gutterEl);
713
695
  }
714
696
  }
715
- if (change.lineDelta < 0) for (const parent of [contentEl, gutterEl]) {
716
- const children = parent.children;
717
- for (let i = children.length - 1; i >= 0; i--) {
718
- const child = children[i];
719
- const { line, columnNumber, lineAnnotation } = child.dataset;
720
- if (line === void 0 && columnNumber === void 0 && lineAnnotation === void 0) continue;
721
- const lineIndex = lineAnnotation !== void 0 ? parseInt(lineAnnotation.split(",")[1], 10) : parseInt(line ?? columnNumber, 10) - 1;
722
- if (Number.isNaN(lineIndex)) continue;
723
- if (lineIndex < change.lineCount) break;
724
- child.remove();
697
+ if (change.lineDelta < 0) for (const children of [contentEl.children, gutterEl?.children ?? []]) for (let i = children.length - 1; i >= 0; i--) {
698
+ const child = children[i];
699
+ const lineNumber = getLineNumberAttr(child) ?? getLineNumberAttr(child, "columnNumber");
700
+ if (lineNumber === void 0) continue;
701
+ if (lineNumber - 1 < change.lineCount) break;
702
+ child.remove();
703
+ }
704
+ const isDiff = Object.hasOwn(fileInstance, "fileDiff");
705
+ const didLineCountChange = change.lineDelta !== 0;
706
+ if (didLineCountChange) {
707
+ let gridRow = contentEl.children.length;
708
+ for (const child of contentEl.children) {
709
+ const { bufferSize } = child.dataset;
710
+ if (bufferSize !== void 0) gridRow += parseInt(bufferSize) - 1;
725
711
  }
712
+ contentEl.style.gridRow = "span " + gridRow;
713
+ if (gutterEl !== void 0) gutterEl.style.gridRow = "span " + gridRow;
726
714
  }
727
- if (change.lineDelta !== 0) {
728
- gutterEl.style.gridRow = "span " + gutterEl.children.length;
729
- contentEl.style.gridRow = "span " + contentEl.children.length;
715
+ fileInstance.updateRenderCache(dirtyLines, tokenizer.themeType, isDiff ? !didLineCountChange : void 0);
716
+ if (didLineCountChange) fileInstance.applyDocumentChange(textDocument, newLineAnnotations, shouldUpdateBuffer);
717
+ if (newLineAnnotations !== void 0) {
718
+ this.#lineAnnotations = newLineAnnotations;
719
+ renderLineAnnotations(newLineAnnotations, contentEl, gutterEl);
730
720
  }
731
- component.applyLineChange?.(dirtyLines, tokenizer.themeType);
732
- if (change.lineDelta !== 0 || isFileDiff) component.applyLayoutChange(textDocument, newLineAnnotations, shouldUpdateBuffer);
733
- if (isFileDiff) this.#tokenizer?.stopBackgroundTokenize();
734
721
  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)`);
735
722
  }
736
723
  #handleInput(inputType, data) {
@@ -747,74 +734,38 @@ var Editor = class {
747
734
  case "deleteContentForward":
748
735
  this.#deleteSelectionText(true);
749
736
  break;
737
+ case "deleteSoftLineBackward":
738
+ this.#deleteSoftLineBackward();
739
+ break;
750
740
  case "deleteHardLineForward":
751
741
  this.#deleteHardLineForward();
752
742
  break;
743
+ case "deleteWordBackward":
744
+ this.#deleteWordBackward();
745
+ break;
753
746
  case "insertTranspose":
754
747
  this.#insertTranspose();
755
748
  break;
756
749
  default:
757
- console.warn(`[diffs] Unknown input type: ${inputType}`);
750
+ console.warn(`[diffs] Unknown input type: ${inputType}`, data);
758
751
  break;
759
752
  }
760
753
  }
761
- #updateSelections(selections) {
762
- this.postponeBackgroundTokenizeToNextFrame();
763
- const gutterBuffer = this.#contentElement?.previousElementSibling;
764
- this.#primaryCaretElement = void 0;
765
- this.#fileInstance?.setSelectedLines(null);
766
- gutterBuffer?.querySelectorAll("[data-active]").forEach((el) => el.removeAttribute("data-active"));
767
- if (selections.length === 0 && this.#matches === void 0) {
768
- this.#selections = void 0;
769
- this.#matches = void 0;
770
- this.#selectionElements?.forEach((el) => el.remove());
771
- this.#selectionElements?.clear();
772
- return;
773
- }
774
- const fragment = document.createDocumentFragment();
775
- const renderCtx = {
776
- fragment,
777
- elements: /* @__PURE__ */ new Map()
778
- };
779
- if (selections.length > 0) {
780
- const normalizedSelections = mergeOverlappingSelections(selections);
781
- const primarySelection = normalizedSelections.at(-1);
782
- this.#selections = normalizedSelections;
783
- if (isCollapsedSelection(primarySelection)) {
784
- const line = primarySelection.start.line + 1;
785
- this.#fileInstance?.setSelectedLines({
786
- start: line,
787
- end: line
754
+ #focus(position, preventScroll = true) {
755
+ if (position !== void 0) {
756
+ this.#shouldIgnoreSelectionChange = true;
757
+ this.#setWindowSelection({
758
+ start: position,
759
+ end: position,
760
+ direction: DirectionNone
761
+ });
762
+ requestAnimationFrame(() => {
763
+ this.#contentElement?.focus({ preventScroll });
764
+ requestAnimationFrame(() => {
765
+ this.#shouldIgnoreSelectionChange = false;
788
766
  });
789
- } else if (gutterBuffer !== void 0 && gutterBuffer instanceof HTMLElement) {
790
- const pos = getCaretPosition(primarySelection);
791
- gutterBuffer.querySelector(`[data-column-number="${pos.line + 1}"]`)?.setAttribute("data-active", "");
792
- }
793
- for (const selection of normalizedSelections) {
794
- if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, selection, "selection");
795
- this.#renderCaret(renderCtx, selection, selection === primarySelection);
796
- }
797
- if (this.#options.enabledQuickEdit === true && !isCollapsedSelection(primarySelection)) this.#renderQuickEditIcon(renderCtx, primarySelection);
798
- }
799
- const textDocument = this.#textDocument;
800
- if (this.#matches !== void 0 && textDocument !== void 0) {
801
- const primarySelection = this.#selections?.at(-1);
802
- const primaryStartOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.start) : -1;
803
- const primaryEndOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.end) : -1;
804
- for (const [startOffset, endOffset] of this.#matches) {
805
- const selection = {
806
- start: textDocument.positionAt(startOffset),
807
- end: textDocument.positionAt(endOffset),
808
- direction: DirectionNone
809
- };
810
- const isFocused = primaryStartOffset === startOffset && primaryEndOffset === endOffset;
811
- this.#renderSelection(renderCtx, selection, "match", isFocused);
812
- }
813
- }
814
- this.#overlayElement?.appendChild(fragment);
815
- this.#selectionElements?.forEach((el) => el.remove());
816
- this.#selectionElements?.clear();
817
- this.#selectionElements = renderCtx.elements;
767
+ });
768
+ } else this.#contentElement?.focus({ preventScroll });
818
769
  }
819
770
  #setWindowSelection(selection) {
820
771
  const winSelection = window.getSelection();
@@ -838,22 +789,6 @@ var Editor = class {
838
789
  console.error("[diffs/editor] failed to update window selection:", err);
839
790
  }
840
791
  }
841
- #focus(position, preventScroll = true) {
842
- if (position !== void 0) {
843
- this.#shouldIgnoreSelectionChange = true;
844
- this.#setWindowSelection({
845
- start: position,
846
- end: position,
847
- direction: DirectionNone
848
- });
849
- requestAnimationFrame(() => {
850
- this.#contentElement?.focus({ preventScroll });
851
- requestAnimationFrame(() => {
852
- this.#shouldIgnoreSelectionChange = false;
853
- });
854
- });
855
- } else this.#contentElement?.focus({ preventScroll });
856
- }
857
792
  #scrollToPrimaryCaret(noFocus = false) {
858
793
  const primaryCaretElement = this.#primaryCaretElement;
859
794
  const primarySelection = this.#selections?.at(-1);
@@ -899,23 +834,98 @@ var Editor = class {
899
834
  line,
900
835
  character: char
901
836
  });
837
+ this.#scrollingToLine = void 0;
838
+ this.#scrollingToLineChar = void 0;
839
+ this.#scrollingToLineNoFocus = false;
902
840
  } else {
903
- const approximateLineY = ((this.#lineAnnotations ?? []).filter((annotation) => annotation.lineNumber < line).length + line) * this.#metrics.lineHeight;
841
+ let yFix = 0;
842
+ if (this.#scrollingToLine === line && this.#contentElement !== void 0) for (let i = this.#contentElement.childElementCount - 1; i >= 0; i--) {
843
+ const child = this.#contentElement.children[i];
844
+ const lineType = child.dataset.lineType;
845
+ const lineNumber = getLineNumberAttr(child);
846
+ if (lineType !== void 0 && isLineEditable(lineType) && lineNumber !== void 0) {
847
+ yFix = (line - lineNumber) * this.#metrics.lineHeight;
848
+ break;
849
+ }
850
+ }
851
+ const approximateLineY = ((this.#lineAnnotations ?? []).filter((annotation) => annotation.lineNumber < line).length + line) * this.#metrics.lineHeight + yFix;
904
852
  virtualCaret.style.top = approximateLineY + "px";
905
853
  this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
906
- this.#scrollingToLine = line;
907
- this.#scrollingToLineChar = char;
908
- this.#scrollingToLineNoFocus = noFocus;
909
854
  virtualCaret.scrollIntoView({
910
855
  block: "center",
911
856
  inline: "nearest"
912
857
  });
858
+ if (this.#scrollingToLine === line && yFix === 0) {
859
+ this.#scrollingToLine = void 0;
860
+ this.#scrollingToLineChar = void 0;
861
+ this.#scrollingToLineNoFocus = false;
862
+ } else {
863
+ this.#scrollingToLine = line;
864
+ this.#scrollingToLineChar = char;
865
+ this.#scrollingToLineNoFocus = noFocus;
866
+ }
913
867
  }
914
868
  virtualCaret.remove();
915
869
  }
916
- #renderSelection(renderCtx, selection, type, isFocused) {
870
+ #updateSelections(selections) {
871
+ this.postponeBackgroundTokenizeToNextFrame();
872
+ this.#primaryCaretElement = void 0;
873
+ this.#fileInstance?.setSelectedLines(null);
874
+ this.#gutterElement?.querySelectorAll("[data-active]").forEach((el) => el.removeAttribute("data-active"));
875
+ if (selections.length === 0 && this.#matches === void 0 && this.#markerManager === void 0) {
876
+ this.#selections = void 0;
877
+ this.#overlayElements?.forEach((el) => el.remove());
878
+ this.#overlayElements?.clear();
879
+ return;
880
+ }
881
+ const fragment = document.createDocumentFragment();
882
+ const renderCtx = {
883
+ fragment,
884
+ elements: /* @__PURE__ */ new Map()
885
+ };
886
+ if (selections.length > 0) {
887
+ const normalizedSelections = mergeOverlappingSelections(selections);
888
+ const primarySelection = normalizedSelections.at(-1);
889
+ this.#selections = normalizedSelections;
890
+ if (isCollapsedSelection(primarySelection)) {
891
+ const line = primarySelection.start.line + 1;
892
+ this.#fileInstance?.setSelectedLines({
893
+ start: line,
894
+ end: line
895
+ });
896
+ } else if (this.#gutterElement !== void 0) {
897
+ const pos = getCaretPosition(primarySelection);
898
+ this.#gutterElement.querySelector(`[data-column-number="${pos.line + 1}"]`)?.setAttribute("data-active", "");
899
+ }
900
+ for (const selection of normalizedSelections) {
901
+ if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, "selection", selection);
902
+ this.#renderCaret(renderCtx, selection, selection === primarySelection);
903
+ }
904
+ if (this.#options.enabledSelectionAction === true && !isCollapsedSelection(primarySelection)) this.#renderSelectionActionIcon(renderCtx, primarySelection);
905
+ }
906
+ const textDocument = this.#textDocument;
907
+ if (this.#matches !== void 0 && textDocument !== void 0) {
908
+ const primarySelection = this.#selections?.at(-1);
909
+ const primaryStartOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.start) : -1;
910
+ const primaryEndOffset = primarySelection !== void 0 ? textDocument.offsetAt(primarySelection.end) : -1;
911
+ for (const [startOffset, endOffset] of this.#matches) {
912
+ const range = {
913
+ start: textDocument.positionAt(startOffset),
914
+ end: textDocument.positionAt(endOffset)
915
+ };
916
+ const isFocused = primaryStartOffset === startOffset && primaryEndOffset === endOffset;
917
+ this.#renderSelection(renderCtx, "match", range, isFocused ? "focus" : void 0);
918
+ }
919
+ }
920
+ if (this.#markerManager !== void 0 && textDocument !== void 0) for (const marker of this.#markerManager.markers) this.#renderSelection(renderCtx, "marker", marker, markerSeverityDatasetKey(marker.severity));
921
+ this.#overlayElement?.appendChild(fragment);
922
+ this.#overlayElements?.forEach((el) => el.remove());
923
+ this.#overlayElements?.clear();
924
+ this.#overlayElements = renderCtx.elements;
925
+ }
926
+ #renderSelection(renderCtx, type, range, extraDataset) {
917
927
  if (this.#textDocument === void 0) return;
918
- const { start, end } = selection;
928
+ const { start, end } = range;
919
929
  for (let line = start.line; line <= end.line; line++) {
920
930
  if (!this.#isLineVisible(line)) continue;
921
931
  const isLastLine = line === end.line;
@@ -925,20 +935,22 @@ var Editor = class {
925
935
  if (this.#wrap) {
926
936
  const contentWidth = this.#getContentWidth();
927
937
  if (2 * this.#metrics.ch + this.#metrics.measureTextWidth(lineText) > contentWidth) {
928
- this.#renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, isFocused);
938
+ this.#renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset);
929
939
  continue;
930
940
  }
931
941
  }
932
942
  let left = 0;
933
943
  let width = 0;
944
+ let paddingEnd = 0;
934
945
  if (startChar === 0) left = this.#getGutterWidth() + this.#metrics.ch;
935
946
  else left = this.#getCharX(line, startChar)[0];
936
- if (startChar === endChar) width = isLastLine ? 0 : this.#metrics.ch;
937
- else width = this.#getCharX(line, endChar)[0] - left + (isLastLine ? 0 : this.#metrics.ch);
938
- this.#renderSelectionBlock(renderCtx, line, 0, left, width, type, isFocused);
947
+ if (!isLastLine && type === "selection") paddingEnd = this.#metrics.ch;
948
+ if (startChar === endChar) width = paddingEnd;
949
+ else width = this.#getCharX(line, endChar)[0] - left + paddingEnd;
950
+ this.#renderSelectionBlock(renderCtx, type, line, 0, left, width, extraDataset);
939
951
  }
940
952
  }
941
- #renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, isFocused = false) {
953
+ #renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset) {
942
954
  const wrapOffsets = this.#wrapLineText(line);
943
955
  const segmentCount = wrapOffsets.length - 1;
944
956
  const offsetLeft = this.#getGutterWidth() + this.#metrics.ch;
@@ -950,29 +962,31 @@ var Editor = class {
950
962
  if (wrapStartChar > wrapEndChar) continue;
951
963
  let segmentLeft;
952
964
  let segmentWidth;
965
+ let paddingEnd = 0;
953
966
  if (wrapStartChar === 0) segmentLeft = offsetLeft;
954
967
  else {
955
968
  const prefixInSegment = lineText.slice(segmentStart, wrapStartChar);
956
969
  const prefixAsciiColumns = getExpandedAsciiTextColumns(prefixInSegment, this.#metrics.tabSize);
957
970
  segmentLeft = offsetLeft + (prefixAsciiColumns !== -1 ? prefixAsciiColumns * this.#metrics.ch : this.#metrics.measureTextWidth(prefixInSegment));
958
971
  }
959
- if (wrapStartChar === wrapEndChar) segmentWidth = wrapLine === segmentCount - 1 ? 0 : this.#metrics.ch;
972
+ if (!isLastLine && wrapLine === segmentCount - 1 && type === "selection") paddingEnd = this.#metrics.ch;
973
+ if (wrapStartChar === wrapEndChar) segmentWidth = paddingEnd;
960
974
  else {
961
975
  const selectionInSegment = lineText.slice(wrapStartChar, wrapEndChar);
962
976
  const selectionAsciiWidth = getExpandedAsciiTextColumns(selectionInSegment, this.#metrics.tabSize);
963
977
  segmentWidth = selectionAsciiWidth !== -1 ? selectionAsciiWidth * this.#metrics.ch : this.#metrics.measureTextWidth(selectionInSegment);
964
- if (!isLastLine && wrapLine === segmentCount - 1) segmentWidth += this.#metrics.ch;
978
+ segmentWidth += paddingEnd;
965
979
  }
966
- this.#renderSelectionBlock(renderCtx, line, wrapLine, segmentLeft, segmentWidth, type, isFocused);
980
+ this.#renderSelectionBlock(renderCtx, type, line, wrapLine, segmentLeft, segmentWidth, extraDataset);
967
981
  }
968
982
  }
969
- #renderSelectionBlock(renderCtx, line, wrapLine, left, width, type, isFocused = false) {
983
+ #renderSelectionBlock(renderCtx, type, line, wrapLine, left, width, extraDataset) {
970
984
  if (width === 0) return;
971
985
  const { ch, lineHeight } = this.#metrics;
972
986
  const y = this.#getLineY(line) + wrapLine * lineHeight;
973
987
  const css = `width:${width}px;transform:translateX(${left}px) translateY(${y}px);`;
974
- const cacheKey = `${type}-block-${left}-${y}-${width}-${isFocused ? "f" : ""}`;
975
- const selectionEls = this.#selectionElements;
988
+ const cacheKey = `${type}-${left}-${y}-${width}${extraDataset ?? ""}`;
989
+ const overlayEls = this.#overlayElements;
976
990
  const rounded = (this.#options.roundedSelection ?? true) && type === "selection";
977
991
  const addRoundedCorner = (line$1, wrapLine$1, left$1, radius) => {
978
992
  const top = this.#getLineY(line$1) + wrapLine$1 * lineHeight;
@@ -995,9 +1009,9 @@ var Editor = class {
995
1009
  }
996
1010
  let cornerEl = renderCtx.elements.get(cacheKey$1);
997
1011
  if (cornerEl !== void 0) return;
998
- if (selectionEls?.has(cacheKey$1) === true) {
999
- cornerEl = selectionEls.get(cacheKey$1);
1000
- selectionEls.delete(cacheKey$1);
1012
+ if (overlayEls?.has(cacheKey$1) === true) {
1013
+ cornerEl = overlayEls.get(cacheKey$1);
1014
+ overlayEls.delete(cacheKey$1);
1001
1015
  } else cornerEl = h("div", {
1002
1016
  dataset: "selectionRange",
1003
1017
  style: { cssText: css$1 },
@@ -1050,16 +1064,13 @@ var Editor = class {
1050
1064
  if (rounded) addRadiusStyle(rangeEl);
1051
1065
  return;
1052
1066
  }
1053
- if (selectionEls?.has(cacheKey) === true) {
1054
- rangeEl = selectionEls.get(cacheKey);
1055
- selectionEls.delete(cacheKey);
1056
- } else {
1057
- rangeEl = h("div", {
1058
- dataset: type + "Range",
1059
- style: { cssText: css }
1060
- }, renderCtx.fragment);
1061
- if (type === "match" && isFocused === true) rangeEl.dataset.focus = "";
1062
- }
1067
+ if (overlayEls?.has(cacheKey) === true) {
1068
+ rangeEl = overlayEls.get(cacheKey);
1069
+ overlayEls.delete(cacheKey);
1070
+ } else rangeEl = h("div", {
1071
+ dataset: extraDataset ? [type + "Range", extraDataset] : type + "Range",
1072
+ style: { cssText: css }
1073
+ }, renderCtx.fragment);
1063
1074
  if (rounded) addRadiusStyle(rangeEl);
1064
1075
  renderCtx.elements.set(cacheKey, rangeEl);
1065
1076
  }
@@ -1079,29 +1090,29 @@ var Editor = class {
1079
1090
  this.#primaryCaretElement = caretEl;
1080
1091
  }
1081
1092
  }
1082
- #renderQuickEditIcon(renderCtx, selection) {
1093
+ #renderSelectionActionIcon(renderCtx, selection) {
1083
1094
  const line = getCaretPosition(selection).line;
1084
1095
  if (!this.#isLineVisible(line)) return;
1085
1096
  const [left, wrapLine] = this.#getCharX(line, 0);
1086
- const cacheKey = "quickEditIcon-" + line + "(" + wrapLine + ")";
1097
+ const cacheKey = "selectionActionIcon-" + line + "(" + wrapLine + ")";
1087
1098
  if (renderCtx.elements.has(cacheKey)) return;
1088
- const quickEditIcon = QuickEditWidget.renderIcon(left, this.#getLineY(line) + wrapLine * this.#metrics.lineHeight, renderCtx.fragment, () => {
1089
- const cleanUpQuickEdit = () => {
1090
- this.#quickEdit?.cleanup();
1091
- this.#quickEdit = void 0;
1099
+ const selectionActionIcon = SelectionActionWidget.renderIcon(left, this.#getLineY(line) + wrapLine * this.#metrics.lineHeight, renderCtx.fragment, () => {
1100
+ const cleanUpSelectionAction = () => {
1101
+ this.#selectionAction?.cleanup();
1102
+ this.#selectionAction = void 0;
1092
1103
  };
1093
1104
  const handleWidgetDomResize = () => {
1094
1105
  this.#lineYCache.clear();
1095
1106
  if (this.#selections !== void 0) this.#updateSelections(this.#selections);
1096
1107
  };
1097
- cleanUpQuickEdit();
1108
+ cleanUpSelectionAction();
1098
1109
  const textDocument = this.#textDocument;
1099
- const renderQuickEdit = this.#options.renderQuickEdit;
1110
+ const renderSelectionAction = this.#options.renderSelectionAction;
1100
1111
  const fileContainer = this.#fileContainer;
1101
- if (textDocument === void 0 || renderQuickEdit === void 0 || fileContainer == null) return;
1112
+ if (textDocument === void 0 || renderSelectionAction === void 0 || fileContainer == null) return;
1102
1113
  const line$1 = selection.end.line;
1103
1114
  const lineText = textDocument.getLineText(line$1);
1104
- const quickEditElement = renderQuickEdit({
1115
+ const selectionActionElement = renderSelectionAction({
1105
1116
  textDocument,
1106
1117
  selection,
1107
1118
  applyEdits: (edits) => {
@@ -1115,7 +1126,7 @@ var Editor = class {
1115
1126
  this.#replaceSelectionText(text);
1116
1127
  },
1117
1128
  close: () => {
1118
- cleanUpQuickEdit();
1129
+ cleanUpSelectionAction();
1119
1130
  handleWidgetDomResize();
1120
1131
  this.#scrollToPrimaryCaret();
1121
1132
  }
@@ -1127,11 +1138,11 @@ var Editor = class {
1127
1138
  else if (charCode === 9) leadingWhitespaces += this.#metrics.tabSize;
1128
1139
  else break;
1129
1140
  }
1130
- this.#quickEdit = new QuickEditWidget(line$1, quickEditElement, fileContainer, leadingWhitespaces, handleWidgetDomResize);
1141
+ this.#selectionAction = new SelectionActionWidget(line$1, selectionActionElement, fileContainer, leadingWhitespaces, handleWidgetDomResize);
1131
1142
  this.#updateSelections([selection]);
1132
- if (this.#isLineVisible(line$1) && this.#contentElement !== void 0) this.#quickEdit.render(this.#contentElement);
1143
+ if (this.#isLineVisible(line$1) && this.#contentElement !== void 0) this.#selectionAction.render(this.#contentElement);
1133
1144
  });
1134
- renderCtx.elements.set(cacheKey, quickEditIcon);
1145
+ renderCtx.elements.set(cacheKey, selectionActionIcon);
1135
1146
  }
1136
1147
  #renderSearchPanel() {
1137
1148
  this.#searchPanel?.cleanup();
@@ -1181,8 +1192,7 @@ var Editor = class {
1181
1192
  break;
1182
1193
  }
1183
1194
  if (nextMatch !== void 0) scrollToMatch(nextMatch, true);
1184
- this.#matches = allMatches;
1185
- this.#updateSelections(this.#selections ?? []);
1195
+ else this.#updateSelections(this.#selections ?? []);
1186
1196
  return nextMatch;
1187
1197
  },
1188
1198
  onClose: () => {
@@ -1219,21 +1229,44 @@ var Editor = class {
1219
1229
  if (selections === void 0 || textDocument === void 0) return;
1220
1230
  const primarySelection = selections.at(-1);
1221
1231
  if (primarySelection === void 0) return;
1222
- const edit = isCollapsedSelection(primarySelection) ? (() => {
1232
+ let edit;
1233
+ if (isCollapsedSelection(primarySelection)) {
1223
1234
  const offset = textDocument.offsetAt(primarySelection.start);
1224
1235
  const nextOffset = forward ? Math.min(textDocument.getText().length, offset + 1) : Math.max(0, offset - 1);
1225
- return {
1236
+ edit = {
1226
1237
  start: Math.min(offset, nextOffset),
1227
1238
  end: Math.max(offset, nextOffset),
1228
1239
  text: ""
1229
1240
  };
1230
- })() : {
1241
+ } else edit = {
1231
1242
  start: textDocument.offsetAt(primarySelection.start),
1232
1243
  end: textDocument.offsetAt(primarySelection.end),
1233
1244
  text: ""
1234
1245
  };
1235
1246
  this.#applyResolvedTextEdit(edit);
1236
1247
  }
1248
+ #deleteSoftLineBackward() {
1249
+ const selections = this.#selections;
1250
+ const textDocument = this.#textDocument;
1251
+ if (selections === void 0 || textDocument === void 0) return;
1252
+ const { nextSelections, change } = applyDeleteSoftLineBackwardToSelections(textDocument, selections, this.#wrap ? (line, character) => {
1253
+ const wrapOffsets = this.#wrapLineText(line);
1254
+ for (let w = 0; w + 1 < wrapOffsets.length; w++) {
1255
+ const segmentStart = wrapOffsets[w];
1256
+ const segmentEnd = wrapOffsets[w + 1];
1257
+ if (character >= segmentStart && character <= segmentEnd) return segmentStart;
1258
+ }
1259
+ return 0;
1260
+ } : void 0, this.#lineAnnotations);
1261
+ if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
1262
+ }
1263
+ #deleteWordBackward() {
1264
+ const selections = this.#selections;
1265
+ const textDocument = this.#textDocument;
1266
+ if (selections === void 0 || textDocument === void 0) return;
1267
+ const { nextSelections, change } = applyDeleteWordBackwardToSelections(textDocument, selections, this.#lineAnnotations);
1268
+ if (change !== void 0) this.#applyChange(change, nextSelections, this.#applyChangeToLineAnnotations(change));
1269
+ }
1237
1270
  #deleteHardLineForward() {
1238
1271
  const selections = this.#selections;
1239
1272
  const textDocument = this.#textDocument;
@@ -1259,9 +1292,11 @@ var Editor = class {
1259
1292
  const onChange = this.#options.onChange;
1260
1293
  if (fileContents !== void 0 && textDocument !== void 0 && onChange !== void 0) {
1261
1294
  const { contents: _,...file } = fileContents;
1262
- let contents;
1263
- Object.defineProperty(file, "contents", { get: () => contents ??= textDocument.getText() });
1264
- this.#emitChange(file, newLineAnnotations ?? this.#lineAnnotations);
1295
+ Object.defineProperty(file, "contents", {
1296
+ enumerable: true,
1297
+ get: () => textDocument.getText()
1298
+ });
1299
+ onChange(file, newLineAnnotations ?? this.#lineAnnotations);
1265
1300
  }
1266
1301
  if (change.lineDelta !== 0) {
1267
1302
  for (const line of this.#lineYCache.keys()) if (line >= change.startLine) this.#lineYCache.delete(line);
@@ -1269,7 +1304,7 @@ var Editor = class {
1269
1304
  if (this.#wrap) {
1270
1305
  for (const line of this.#wrapLineOffsetsCache.keys()) if (line >= change.startLine) this.#wrapLineOffsetsCache.delete(line);
1271
1306
  }
1272
- this.#lastCharX = void 0;
1307
+ this.#lastAccessedCharX = void 0;
1273
1308
  let renderRange = this.#renderRange;
1274
1309
  let shouldUpdateBuffer;
1275
1310
  if (renderRange !== void 0 && selections !== void 0 && selections.length > 0) {
@@ -1305,28 +1340,40 @@ var Editor = class {
1305
1340
  }
1306
1341
  }
1307
1342
  #getLineElement(line) {
1343
+ const lastAccessed = this.#lastAccessedLineElement;
1344
+ if (lastAccessed !== void 0 && lastAccessed[0] === line) return lastAccessed[1];
1308
1345
  const contentElement = this.#contentElement;
1309
1346
  if (contentElement === void 0) return;
1310
- if (this.#renderRange !== void 0 && this.#fileInstanceType === "file") {
1347
+ let lineElement = null;
1348
+ if (this.#renderRange !== void 0) {
1311
1349
  const { startingLine } = this.#renderRange;
1312
1350
  const { children } = contentElement;
1313
1351
  for (let i = line - startingLine; i <= children.length; i++) {
1314
1352
  const child = children[i];
1315
- const lineNumber = child?.dataset.line;
1316
- const lineType = child?.dataset.lineType;
1317
- if (lineNumber !== void 0 && lineType !== void 0 && isLineEditable(lineType) && parseInt(lineNumber, 10) === line + 1) return child;
1353
+ if (child === void 0) break;
1354
+ const lineNumber = getLineNumberAttr(child);
1355
+ const lineType = child.dataset.lineType;
1356
+ if (lineNumber !== void 0 && lineNumber === line + 1 && lineType !== void 0 && isLineEditable(lineType)) {
1357
+ lineElement = child;
1358
+ break;
1359
+ }
1318
1360
  }
1319
1361
  }
1320
- if (this.#fileInstanceType === "diff") return contentElement.querySelector(`[data-line="${line + 1}"]:not([data-line-type="change-deletion"])`) ?? void 0;
1321
- return contentElement.querySelector(`[data-line="${line + 1}"]`) ?? void 0;
1362
+ lineElement ??= contentElement.querySelector(`[data-line="${line + 1}"]`);
1363
+ if (lineElement !== null) {
1364
+ if (lastAccessed !== void 0) {
1365
+ lastAccessed[0] = line;
1366
+ lastAccessed[1] = lineElement;
1367
+ } else this.#lastAccessedLineElement = [line, lineElement];
1368
+ return lineElement;
1369
+ }
1322
1370
  }
1323
1371
  #getGutterWidth() {
1324
- const gutterElement = this.#contentElement?.previousElementSibling;
1325
- if (gutterElement == null || !(gutterElement instanceof HTMLElement) || !gutterElement.hasAttribute("data-gutter")) return 0;
1372
+ if (this.#gutterElement === void 0) return 0;
1326
1373
  if (this.#gutterWidthCache === void 0) {
1327
1374
  const diffsColumnNumberWidth = this.#contentElement?.parentElement?.style.getPropertyValue("--diffs-column-number-width");
1328
1375
  if (diffsColumnNumberWidth !== void 0 && diffsColumnNumberWidth.length > 2 && diffsColumnNumberWidth.endsWith("px")) this.#gutterWidthCache = parseInt(diffsColumnNumberWidth.slice(0, -2), 10);
1329
- else this.#gutterWidthCache = gutterElement.offsetWidth;
1376
+ else this.#gutterWidthCache = this.#gutterElement.offsetWidth;
1330
1377
  }
1331
1378
  return this.#gutterWidthCache;
1332
1379
  }
@@ -1349,7 +1396,7 @@ var Editor = class {
1349
1396
  return y;
1350
1397
  }
1351
1398
  #getCharX(line, char) {
1352
- if (this.#lastCharX !== void 0 && this.#lastCharX[0] === line && this.#lastCharX[1] === char) return [this.#lastCharX[2], this.#lastCharX[3]];
1399
+ if (this.#lastAccessedCharX !== void 0 && this.#lastAccessedCharX[0] === line && this.#lastAccessedCharX[1] === char) return [this.#lastAccessedCharX[2], this.#lastAccessedCharX[3]];
1353
1400
  const lineText = this.#textDocument?.getLineText(line);
1354
1401
  const offsetLeft = this.#getGutterWidth() + this.#metrics.ch;
1355
1402
  if (lineText === void 0 || lineText.length === 0 || char <= 0) return [offsetLeft, 0];
@@ -1377,12 +1424,12 @@ var Editor = class {
1377
1424
  }
1378
1425
  }
1379
1426
  }
1380
- if (this.#lastCharX !== void 0) {
1381
- this.#lastCharX[0] = line;
1382
- this.#lastCharX[1] = char;
1383
- this.#lastCharX[2] = left;
1384
- this.#lastCharX[3] = wrapLine;
1385
- } else this.#lastCharX = [
1427
+ if (this.#lastAccessedCharX !== void 0) {
1428
+ this.#lastAccessedCharX[0] = line;
1429
+ this.#lastAccessedCharX[1] = char;
1430
+ this.#lastAccessedCharX[2] = left;
1431
+ this.#lastAccessedCharX[3] = wrapLine;
1432
+ } else this.#lastAccessedCharX = [
1386
1433
  line,
1387
1434
  char,
1388
1435
  left,