@pierre/diffs 1.3.0-beta.2 → 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 (158) hide show
  1. package/dist/components/CodeView.d.ts +4 -0
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +38 -0
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +2 -2
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +13 -13
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +7 -4
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +57 -47
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  14. package/dist/components/UnresolvedFile.js +1 -1
  15. package/dist/components/VirtualizedFile.d.ts +1 -1
  16. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  17. package/dist/components/VirtualizedFile.js +13 -4
  18. package/dist/components/VirtualizedFile.js.map +1 -1
  19. package/dist/components/VirtualizedFileDiff.d.ts +2 -1
  20. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  21. package/dist/components/VirtualizedFileDiff.js +36 -42
  22. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  23. package/dist/components/Virtualizer.js +5 -3
  24. package/dist/components/Virtualizer.js.map +1 -1
  25. package/dist/components/VirtulizerDevelopment.d.ts.map +1 -1
  26. package/dist/editor/editStack.d.ts +1 -1
  27. package/dist/editor/editor.d.ts +14 -6
  28. package/dist/editor/editor.d.ts.map +1 -1
  29. package/dist/editor/editor.js +745 -553
  30. package/dist/editor/editor.js.map +1 -1
  31. package/dist/editor/editor2.js +6 -0
  32. package/dist/editor/editor2.js.map +1 -0
  33. package/dist/editor/lineAnnotations.d.ts +2 -1
  34. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  35. package/dist/editor/lineAnnotations.js +111 -1
  36. package/dist/editor/lineAnnotations.js.map +1 -1
  37. package/dist/editor/marker.d.ts +33 -0
  38. package/dist/editor/marker.d.ts.map +1 -0
  39. package/dist/editor/marker.js +185 -0
  40. package/dist/editor/marker.js.map +1 -0
  41. package/dist/editor/pieceTable.d.ts +3 -3
  42. package/dist/editor/pieceTable.d.ts.map +1 -1
  43. package/dist/editor/pieceTable.js +44 -33
  44. package/dist/editor/pieceTable.js.map +1 -1
  45. package/dist/editor/searchPanel.d.ts +6 -7
  46. package/dist/editor/searchPanel.d.ts.map +1 -1
  47. package/dist/editor/searchPanel.js +103 -138
  48. package/dist/editor/searchPanel.js.map +1 -1
  49. package/dist/editor/selection.d.ts +19 -3
  50. package/dist/editor/selection.d.ts.map +1 -1
  51. package/dist/editor/selection.js +196 -39
  52. package/dist/editor/selection.js.map +1 -1
  53. package/dist/editor/{quickEdit.d.ts → selectionAction.d.ts} +8 -8
  54. package/dist/editor/selectionAction.d.ts.map +1 -0
  55. package/dist/editor/{quickEdit.js → selectionAction.js} +19 -21
  56. package/dist/editor/selectionAction.js.map +1 -0
  57. package/dist/editor/sprite.d.ts +8 -0
  58. package/dist/editor/sprite.d.ts.map +1 -0
  59. package/dist/editor/sprite.js +45 -0
  60. package/dist/editor/sprite.js.map +1 -0
  61. package/dist/editor/textDocument.d.ts +5 -5
  62. package/dist/editor/textDocument.d.ts.map +1 -1
  63. package/dist/editor/textDocument.js +9 -9
  64. package/dist/editor/textDocument.js.map +1 -1
  65. package/dist/editor/textMeasure.js +3 -3
  66. package/dist/editor/textMeasure.js.map +1 -1
  67. package/dist/editor/tokenzier.d.ts +6 -2
  68. package/dist/editor/tokenzier.d.ts.map +1 -1
  69. package/dist/editor/tokenzier.js +135 -85
  70. package/dist/editor/tokenzier.js.map +1 -1
  71. package/dist/editor/utils.d.ts +3 -1
  72. package/dist/editor/utils.d.ts.map +1 -1
  73. package/dist/editor/utils.js +16 -1
  74. package/dist/editor/utils.js.map +1 -1
  75. package/dist/highlighter/shared_highlighter.js +3 -29
  76. package/dist/highlighter/shared_highlighter.js.map +1 -1
  77. package/dist/highlighter/themes/attachResolvedThemes.js +4 -3
  78. package/dist/highlighter/themes/attachResolvedThemes.js.map +1 -1
  79. package/dist/highlighter/themes/cleanUpResolvedThemes.js +3 -2
  80. package/dist/highlighter/themes/cleanUpResolvedThemes.js.map +1 -1
  81. package/dist/highlighter/themes/constants.d.ts +1 -7
  82. package/dist/highlighter/themes/constants.d.ts.map +1 -1
  83. package/dist/highlighter/themes/constants.js +1 -4
  84. package/dist/highlighter/themes/constants.js.map +1 -1
  85. package/dist/highlighter/themes/getResolvedOrResolveTheme.js +2 -2
  86. package/dist/highlighter/themes/getResolvedOrResolveTheme.js.map +1 -1
  87. package/dist/highlighter/themes/getResolvedThemes.js +2 -8
  88. package/dist/highlighter/themes/getResolvedThemes.js.map +1 -1
  89. package/dist/highlighter/themes/hasResolvedThemes.js +2 -3
  90. package/dist/highlighter/themes/hasResolvedThemes.js.map +1 -1
  91. package/dist/highlighter/themes/registerCustomCSSVariableTheme.js +1 -1
  92. package/dist/highlighter/themes/registerCustomTheme.d.ts +5 -3
  93. package/dist/highlighter/themes/registerCustomTheme.d.ts.map +1 -1
  94. package/dist/highlighter/themes/registerCustomTheme.js +15 -5
  95. package/dist/highlighter/themes/registerCustomTheme.js.map +1 -1
  96. package/dist/highlighter/themes/resolveTheme.js +6 -27
  97. package/dist/highlighter/themes/resolveTheme.js.map +1 -1
  98. package/dist/highlighter/themes/resolveThemes.js +5 -12
  99. package/dist/highlighter/themes/resolveThemes.js.map +1 -1
  100. package/dist/highlighter/themes/themeResolution.d.ts +8 -0
  101. package/dist/highlighter/themes/themeResolution.d.ts.map +1 -0
  102. package/dist/highlighter/themes/themeResolution.js +22 -0
  103. package/dist/highlighter/themes/themeResolution.js.map +1 -0
  104. package/dist/highlighter/themes/themeResolver.d.ts +8 -0
  105. package/dist/highlighter/themes/themeResolver.d.ts.map +1 -0
  106. package/dist/highlighter/themes/themeResolver.js +8 -0
  107. package/dist/highlighter/themes/themeResolver.js.map +1 -0
  108. package/dist/index.d.ts +4 -4
  109. package/dist/index.js +3 -3
  110. package/dist/react/index.d.ts +2 -2
  111. package/dist/react/jsx.d.ts.map +1 -1
  112. package/dist/react/utils/useFileDiffInstance.js +1 -0
  113. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  114. package/dist/renderers/DiffHunksRenderer.d.ts +4 -1
  115. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  116. package/dist/renderers/DiffHunksRenderer.js +139 -19
  117. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  118. package/dist/renderers/FileRenderer.d.ts +2 -2
  119. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  120. package/dist/renderers/FileRenderer.js +5 -5
  121. package/dist/renderers/FileRenderer.js.map +1 -1
  122. package/dist/ssr/index.d.ts +2 -2
  123. package/dist/style.js +1 -1
  124. package/dist/style.js.map +1 -1
  125. package/dist/types.d.ts +19 -16
  126. package/dist/types.d.ts.map +1 -1
  127. package/dist/utils/computeEstimatedDiffHeights.js +9 -20
  128. package/dist/utils/computeEstimatedDiffHeights.js.map +1 -1
  129. package/dist/utils/getHighlighterThemeStyles.js +16 -12
  130. package/dist/utils/getHighlighterThemeStyles.js.map +1 -1
  131. package/dist/utils/iterateOverDiff.js +147 -182
  132. package/dist/utils/iterateOverDiff.js.map +1 -1
  133. package/dist/utils/parsePatchFiles.js +93 -4
  134. package/dist/utils/parsePatchFiles.js.map +1 -1
  135. package/dist/utils/updateDiffHunks.d.ts +13 -0
  136. package/dist/utils/updateDiffHunks.d.ts.map +1 -0
  137. package/dist/utils/updateDiffHunks.js +171 -0
  138. package/dist/utils/updateDiffHunks.js.map +1 -0
  139. package/dist/utils/virtualDiffLayout.d.ts +24 -2
  140. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  141. package/dist/utils/virtualDiffLayout.js +49 -1
  142. package/dist/utils/virtualDiffLayout.js.map +1 -1
  143. package/dist/worker/WorkerPoolManager.js +1 -1
  144. package/dist/worker/WorkerPoolManager.js.map +1 -1
  145. package/dist/worker/{wasm-D4DU5jgR.js → wasm-BaDzIkIn.js} +2 -2
  146. package/dist/worker/wasm-BaDzIkIn.js.map +1 -0
  147. package/dist/worker/worker-portable.js +1021 -314
  148. package/dist/worker/worker-portable.js.map +1 -1
  149. package/dist/worker/worker.js +202 -196
  150. package/dist/worker/worker.js.map +1 -1
  151. package/package.json +4 -2
  152. package/dist/editor/css.d.ts +0 -6
  153. package/dist/editor/css.d.ts.map +0 -1
  154. package/dist/editor/css.js +0 -218
  155. package/dist/editor/css.js.map +0 -1
  156. package/dist/editor/quickEdit.d.ts.map +0 -1
  157. package/dist/editor/quickEdit.js.map +0 -1
  158. package/dist/worker/wasm-D4DU5jgR.js.map +0 -1
@@ -1,36 +1,30 @@
1
1
  import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
2
2
  import { isMoveCursorShortcut, isPrimaryModifier, isSafari } from "./platform.js";
3
3
  import { resolveEditorCommandFromKeyboardEvent } from "./command.js";
4
- import { editorCSS, editorGlobalCSS } from "./css.js";
5
- import { applyDocumentChangeToLineAnnotations } from "./lineAnnotations.js";
6
- 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";
7
- import { TextDocument } from "./textDocument.js";
8
- import { addEventListener, debounce, extend, h, round } from "./utils.js";
9
- import { QuickEditWidget } from "./quickEdit.js";
4
+ import editor_default from "./editor2.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";
10
10
  import { SearchPanelWidget } from "./searchPanel.js";
11
+ import { SelectionActionWidget } from "./selectionAction.js";
12
+ import { TextDocument } from "./textDocument.js";
11
13
  import { Metrics, getExpandedAsciiTextColumns, getUnicodeMeasurementOffsets, snapTextOffsetToUnicodeBoundary } from "./textMeasure.js";
12
14
  import { EditorTokenizer, renderLineTokens } from "./tokenzier.js";
13
15
 
14
16
  //#region src/editor/editor.ts
15
- function clampDomOffset(node, offset) {
16
- if (node.nodeType === 3) {
17
- const length = node.textContent?.length ?? 0;
18
- return Math.max(0, Math.min(offset, length));
19
- }
20
- if (node.nodeType === 1) return Math.max(0, Math.min(offset, node.childNodes.length));
21
- return 0;
22
- }
23
17
  var Editor = class {
24
18
  #options;
25
- #editMode = "simple";
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
- #component;
27
+ #fileInstance;
34
28
  #fileContents;
35
29
  #lineAnnotations;
36
30
  #textDocument;
@@ -40,16 +34,19 @@ var Editor = class {
40
34
  #contentWidthCache;
41
35
  #lineYCache = /* @__PURE__ */ new Map();
42
36
  #wrapLineOffsetsCache = /* @__PURE__ */ new Map();
43
- #lastCharX;
37
+ #lastAccessedLineElement;
38
+ #lastAccessedCharX;
44
39
  #globalStyleElement;
45
40
  #editorStyleElement;
46
41
  #themeStyleElement;
47
- #componentContainer;
42
+ #spriteElement;
43
+ #fileContainer;
44
+ #gutterElement;
48
45
  #contentElement;
49
46
  #overlayElement;
50
47
  #primaryCaretElement;
51
- #selectionElements;
52
- #quickEdit;
48
+ #overlayElements;
49
+ #selectionAction;
53
50
  #searchPanel;
54
51
  #resizeObserver;
55
52
  #shouldIgnoreSelectionChange = false;
@@ -60,14 +57,13 @@ var Editor = class {
60
57
  #reservedSelections;
61
58
  #selections;
62
59
  #initSelections;
60
+ #matches;
63
61
  #scrollingToLine;
64
62
  #scrollingToLineChar;
63
+ #scrollingToLineNoFocus = false;
65
64
  #retainSearchPanelFocus = false;
66
- #emitChange = debounce((fileContents, lineAnnotations) => {
67
- this.#options.onChange?.(fileContents, lineAnnotations);
68
- }, 500);
69
65
  #onDeferTokenize = (lines, themeType) => {
70
- this.#component?.applyLineChange?.(lines, themeType);
66
+ this.#fileInstance?.updateRenderCache(lines, themeType);
71
67
  if (this.#renderRange !== void 0 && this.#renderRange.totalLines !== Infinity) {
72
68
  const { startingLine, totalLines } = this.#renderRange;
73
69
  const endLine = Math.min(startingLine + totalLines, this.#textDocument?.lineCount ?? 0);
@@ -81,143 +77,52 @@ var Editor = class {
81
77
  this.#options = options;
82
78
  }
83
79
  edit(component) {
84
- const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, lineHoverHighlight,...rest } = component.options;
85
- 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") {
86
82
  component.setOptions({
87
83
  ...rest,
88
84
  useTokenTransformer: true,
89
85
  enableGutterUtility: false,
90
86
  enableLineSelection: false,
91
87
  expandUnchanged: true,
88
+ diffStyle: "split",
92
89
  lineHoverHighlight: "disabled"
93
90
  });
94
91
  component.rerender();
95
92
  }
96
- this.#component = component;
93
+ this.#fileInstance = component;
97
94
  this.#initialize();
98
95
  this.#detach = component.attachEditor(this);
99
96
  return () => this.cleanUp();
100
97
  }
101
- setSelections(selections) {
102
- const textDocument = this.#textDocument;
103
- if (textDocument !== void 0) {
104
- const resolvedSelections = selections.map((selection) => {
105
- const start = textDocument.normalizePosition(selection.start);
106
- const end = textDocument.normalizePosition(selection.end);
107
- return {
108
- direction: selection.direction === "none" ? DirectionNone : selection.direction === "backward" ? DirectionBackward : DirectionForward,
109
- start,
110
- end
111
- };
112
- });
113
- this.#updateSelections(resolvedSelections);
114
- this.#scrollToPrimaryCaret();
115
- } else this.#initSelections = selections;
116
- }
117
- focus(options) {
118
- const preventScroll = options?.preventScroll ?? false;
119
- const primarySelection = this.#selections?.at(-1);
120
- if (primarySelection !== void 0) {
121
- const pos = primarySelection.direction === DirectionBackward ? primarySelection.end : primarySelection.start;
122
- this.#focus(pos, preventScroll);
123
- } else this.#focus(void 0, preventScroll);
124
- }
125
- cleanUp() {
126
- this.#tokenizer?.cleanUp();
127
- this.#tokenizer = void 0;
128
- this.#globalEventDisposes?.forEach((dispose) => dispose());
129
- this.#globalEventDisposes = void 0;
130
- this.#editorEventDisposes?.forEach((dispose) => dispose());
131
- this.#editorEventDisposes = void 0;
132
- this.#detach?.();
133
- this.#detach = void 0;
134
- this.#component?.setSelectedLines(null);
135
- this.#component = void 0;
136
- this.#fileContents = void 0;
137
- this.#lineAnnotations = void 0;
138
- this.#textDocument = void 0;
139
- this.#renderRange = void 0;
140
- this.#gutterWidthCache = void 0;
141
- this.#contentWidthCache = void 0;
142
- this.#lineYCache.clear();
143
- this.#wrapLineOffsetsCache.clear();
144
- this.#lastCharX = void 0;
145
- this.#globalStyleElement?.remove();
146
- this.#globalStyleElement = void 0;
147
- this.#editorStyleElement?.remove();
148
- this.#editorStyleElement = void 0;
149
- this.#themeStyleElement?.remove();
150
- this.#themeStyleElement = void 0;
151
- this.#componentContainer = void 0;
152
- this.#contentElement?.removeAttribute("contentEditable");
153
- this.#contentElement = void 0;
154
- this.#overlayElement?.remove();
155
- this.#overlayElement = void 0;
156
- this.#primaryCaretElement?.remove();
157
- this.#primaryCaretElement = void 0;
158
- this.#selectionElements?.forEach((el) => el.remove());
159
- this.#selectionElements?.clear();
160
- this.#selectionElements = void 0;
161
- this.#searchPanel?.cleanup();
162
- this.#searchPanel = void 0;
163
- this.#quickEdit?.cleanup();
164
- this.#quickEdit = void 0;
165
- this.#resizeObserver?.disconnect();
166
- this.#resizeObserver = void 0;
167
- this.#shouldIgnoreSelectionChange = false;
168
- this.#selectionStart = void 0;
169
- this.#selections = void 0;
170
- this.#reservedSelections = void 0;
171
- }
172
- syncWithRender(highlighter, fileContainer, fileContents, lineAnnotations, renderRange, editMode) {
98
+ syncToRenderedView(highlighter, fileContainer, fileContents, didFileChange, lineAnnotations, renderRange) {
173
99
  const shadowRoot = fileContainer.shadowRoot;
174
100
  if (shadowRoot == null) {
175
101
  console.error("[editor] Could not find the shadow root.");
176
102
  return;
177
103
  }
178
104
  let codeElement;
105
+ let gutterEl;
106
+ let contentEl;
179
107
  for (const el of shadowRoot.querySelectorAll("[data-code]")) if (el.dataset.deletions === void 0) {
180
108
  codeElement = el;
181
- break;
182
- }
183
- if (codeElement === void 0) return;
184
- const contentEl = codeElement.children[1];
185
- if (contentEl === void 0) return;
186
- this.#editMode = editMode ?? "simple";
187
- this.#wrap = this.#component?.options.overflow === "wrap";
188
- if (editMode === "advanced" || (lineAnnotations?.length ?? 0) > 0) {
189
- let startingLine;
190
- let endLine;
191
- for (const child of contentEl.children) {
192
- const el = child;
193
- const line = el.dataset.line;
194
- const lineType = el.dataset.lineType;
195
- if (line !== void 0) {
196
- const lineIndex = Number(line) - 1;
197
- startingLine ??= lineIndex;
198
- endLine = lineIndex;
199
- }
200
- if (lineType === void 0 || !isLineEditable(lineType)) el.contentEditable = "false";
201
- }
202
- if (endLine !== void 0 && renderRange !== void 0) {
203
- const { startingLine: startingLine$1, totalLines } = renderRange;
204
- 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;
205
114
  }
206
- if (startingLine !== void 0 && endLine !== void 0) renderRange = {
207
- startingLine,
208
- totalLines: endLine - startingLine,
209
- bufferBefore: 0,
210
- bufferAfter: 0
211
- };
115
+ break;
212
116
  }
213
- if (this.#componentContainer !== fileContainer) {
214
- this.#componentContainer = fileContainer;
215
- this.#codePaddingTop = Number(getComputedStyle(codeElement).paddingTop.slice(0, -2));
117
+ if (codeElement === void 0 || contentEl === void 0) return;
118
+ if (this.#fileContainer !== fileContainer) {
119
+ this.#fileContainer = fileContainer;
216
120
  if (this.#globalStyleElement !== void 0) fileContainer.appendChild(this.#globalStyleElement);
217
121
  if (this.#editorStyleElement !== void 0) shadowRoot.appendChild(this.#editorStyleElement);
218
122
  if (this.#themeStyleElement !== void 0) shadowRoot.appendChild(this.#themeStyleElement);
123
+ if (this.#spriteElement !== void 0) shadowRoot.prepend(this.#spriteElement);
219
124
  }
220
- if (this.#textDocument === void 0 || this.#fileContents === void 0 || this.#fileContents.name !== fileContents.name) {
125
+ if (this.#textDocument === void 0 || this.#fileContents === void 0 || didFileChange) {
221
126
  const textDocument = new TextDocument(fileContents.name, fileContents.contents, fileContents.lang ?? getFiletypeFromFileName(fileContents.name));
222
127
  this.#fileContents = fileContents;
223
128
  this.#textDocument = textDocument;
@@ -225,32 +130,33 @@ var Editor = class {
225
130
  this.#tokenizer = new EditorTokenizer({
226
131
  highlighter,
227
132
  textDocument,
228
- codeOptions: this.#component?.options ?? {},
133
+ codeOptions: this.#fileInstance?.options ?? {},
229
134
  onDeferTokenize: this.#onDeferTokenize,
230
135
  setStyle: (css) => {
231
136
  this.#themeStyleElement.textContent = css;
232
- }
137
+ },
138
+ __debug: this.#options.__debug
233
139
  });
140
+ this.#fileInstance?.setSelectedLines(null);
234
141
  this.#shouldIgnoreSelectionChange = false;
235
- this.#selectionElements?.forEach((el) => el.remove());
236
- this.#selectionElements?.clear();
237
- this.#component?.setSelectedLines(null);
238
- this.#selectionElements = void 0;
142
+ this.#overlayElements?.forEach((el) => el.remove());
143
+ this.#overlayElements?.clear();
144
+ this.#overlayElements = void 0;
239
145
  this.#selections = void 0;
240
146
  this.#scrollingToLine = void 0;
241
147
  this.#reservedSelections = void 0;
242
148
  this.#searchPanel?.cleanup();
243
149
  this.#searchPanel = void 0;
244
- this.#quickEdit?.cleanup();
245
- this.#quickEdit = void 0;
150
+ this.#selectionAction?.cleanup();
151
+ this.#selectionAction = void 0;
246
152
  }
247
153
  if (this.#contentElement !== contentEl) {
248
- const guttterEl = contentEl.previousElementSibling;
249
- const targetIsContentElement = (e) => {
250
- const target = e.composedPath()[0];
251
- return target === contentEl || contentEl.contains(target);
252
- };
253
- 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;
254
160
  this.#contentElement = extend(contentEl, {
255
161
  contentEditable: "true",
256
162
  role: "textbox",
@@ -262,204 +168,159 @@ var Editor = class {
262
168
  translate: false
263
169
  });
264
170
  if (this.#overlayElement !== void 0) contentEl.after(this.#overlayElement);
265
- this.#editorEventDisposes?.forEach((dispose) => dispose());
266
- this.#editorEventDisposes = [
267
- addEventListener(contentEl, "pointerdown", (e) => {
268
- if (e.pointerType !== "mouse") return;
269
- if (isSafari() && this.#lineAnnotations !== void 0 && this.#lineAnnotations.length > 0) this.#mouseUpDisposes = [...contentEl.querySelectorAll("[data-line-annotation]")].map((el) => [addEventListener(el, "mouseenter", () => {
270
- this.#shouldIgnoreSelectionChange = true;
271
- }), addEventListener(el, "mouseleave", () => {
272
- this.#shouldIgnoreSelectionChange = false;
273
- })]).flat();
274
- this.#isContentMouseDown = true;
275
- this.#selectionStart = void 0;
276
- if (e.button === 0 && isPrimaryModifier(e)) this.#reservedSelections = this.#selections?.map((selection) => ({ ...selection }));
277
- if (e.shiftKey) {
278
- const primarySelection = this.#selections?.at(-1);
279
- if (primarySelection !== void 0) {
280
- const pos = primarySelection.direction === DirectionBackward ? primarySelection.end : primarySelection.start;
281
- this.#updateWindowSelection({
282
- start: pos,
283
- end: pos,
284
- direction: DirectionNone
285
- });
286
- }
287
- this.#shiftKeyPressed = true;
288
- }
289
- }, { passive: true }),
290
- addEventListener(contentEl, "keydown", (e) => {
291
- if (e.key === "Escape") {
292
- e.preventDefault();
293
- this.#searchPanel?.cleanup();
294
- this.#searchPanel = void 0;
295
- this.#retainSearchPanelFocus = false;
296
- this.#quickEdit?.cleanup();
297
- this.#quickEdit = void 0;
298
- if (this.#selections !== void 0 && this.#selections.length > 0) {
299
- const primarySelection = this.#selections.at(-1);
300
- if (!isCollapsedSelection(primarySelection) || this.#selections.length > 1) {
301
- const pos = getCaretPosition(primarySelection);
302
- this.#updateSelections([{
303
- start: pos,
304
- end: pos,
305
- direction: DirectionNone
306
- }]);
307
- this.#focus(pos);
308
- }
309
- }
310
- return;
311
- }
312
- if (!targetIsContentElement(e)) return;
313
- const mvShortcut = isMoveCursorShortcut(e);
314
- const textDocument = this.#textDocument;
315
- if (this.#selections !== void 0 && this.#selections.length > 0 && mvShortcut !== void 0 && textDocument !== void 0) {
316
- if (e.shiftKey) this.#updateSelections(mapSelectionShift(textDocument, this.#selections, mvShortcut));
317
- else this.#updateSelections(mapCursorMove(textDocument, this.#selections, mvShortcut));
318
- this.#scrollToPrimaryCaret();
319
- e.preventDefault();
320
- return;
321
- }
322
- const command = resolveEditorCommandFromKeyboardEvent(e);
323
- if (command !== void 0) {
324
- e.preventDefault();
325
- this.#runCommand(command);
326
- }
327
- }),
328
- addEventListener(contentEl, "copy", (e) => {
329
- if (!targetIsContentElement(e)) return;
330
- e.preventDefault();
331
- e.clipboardData?.setData("text", this.#getSelectionText());
332
- }),
333
- addEventListener(contentEl, "cut", (e) => {
334
- if (!targetIsContentElement(e)) return;
335
- e.preventDefault();
336
- e.clipboardData?.setData("text", this.#getSelectionText());
337
- this.#replaceSelectionText("");
338
- }),
339
- addEventListener(contentEl, "paste", (e) => {
340
- if (!targetIsContentElement(e)) return;
341
- e.preventDefault();
342
- const text = e.clipboardData?.getData("text");
343
- if (text !== void 0) this.#replaceSelectionText(text);
344
- }),
345
- addEventListener(contentEl, "beforeinput", (e) => {
346
- if (!targetIsContentElement(e)) return;
347
- e.preventDefault();
348
- this.#handleInput(e.inputType, e.data);
349
- }),
350
- addEventListener(contentEl, "compositionstart", (e) => {
351
- if (!targetIsContentElement(e)) return;
352
- this.#shouldIgnoreSelectionChange = true;
353
- }, { passive: true }),
354
- addEventListener(contentEl, "compositionend", (e) => {
355
- if (!targetIsContentElement(e)) return;
356
- this.#shouldIgnoreSelectionChange = false;
357
- this.#handleInput("insertText", e.data);
358
- }, { passive: true })
359
- ];
360
- if (guttterEl !== null && guttterEl.dataset.gutter !== void 0) this.#editorEventDisposes.push(addEventListener(guttterEl, "pointerdown", (e) => {
361
- let target = e.composedPath()[0];
362
- if (target?.dataset.lineNumberContent !== void 0) target = target.parentElement ?? void 0;
363
- const textDocument = this.#textDocument;
364
- if (target === void 0 || textDocument === void 0) return;
365
- const lineNumber = target.dataset.columnNumber;
366
- const lineType = target.dataset.lineType;
367
- if (lineNumber === void 0 || lineType === void 0 || !isLineEditable(lineType)) return;
368
- const lineIndex = Number(lineNumber) - 1;
369
- const selection = {
370
- start: {
371
- line: lineIndex,
372
- character: 0
373
- },
374
- end: {
375
- line: lineIndex,
376
- character: textDocument.getLineText(lineIndex).length
377
- },
378
- direction: DirectionForward
379
- };
380
- this.#isGutterMouseDown = true;
381
- this.#selectionStart = selection;
382
- this.#updateSelections([selection]);
383
- this.#focus(selection.end);
384
- this.#mouseUpDisposes = [addEventListener(document, "mousemove", (e$1) => {
385
- let target$1 = e$1.composedPath()[0];
386
- if (target$1?.dataset.lineNumberContent !== void 0) target$1 = target$1?.parentElement ?? void 0;
387
- else if (target$1?.tagName === "SPAN") target$1 = target$1?.closest("[data-line]");
388
- if (target$1 === void 0) return;
389
- const lineNumber$1 = target$1.dataset.columnNumber ?? target$1.dataset.line;
390
- const lineType$1 = target$1.dataset.lineType;
391
- if (this.#isGutterMouseDown && this.#textDocument !== void 0 && lineNumber$1 !== void 0 && lineType$1 !== void 0 && isLineEditable(lineType$1)) {
392
- const lineIndex$1 = Number(lineNumber$1) - 1;
393
- let selection$1 = {
394
- start: {
395
- line: lineIndex$1,
396
- character: 0
397
- },
398
- end: {
399
- line: lineIndex$1,
400
- character: this.#textDocument.getLineText(lineIndex$1).length
401
- },
402
- direction: DirectionForward
403
- };
404
- if (this.#selectionStart !== void 0) selection$1 = createSelectionFrom(this.#selectionStart, selection$1);
405
- else this.#selectionStart = selection$1;
406
- this.#updateSelections([selection$1]);
407
- this.#focus(selection$1.end);
408
- }
409
- }, { passive: true })];
410
- }, { passive: true }));
411
- this.#resizeObserver?.disconnect();
412
- this.#resizeObserver = new ResizeObserver(() => {
413
- requestAnimationFrame(() => {
414
- this.#handleLayoutResize();
415
- });
416
- });
417
- this.#resizeObserver.observe(contentEl);
418
- this.#resizeObserver.observe(contentEl.parentElement);
171
+ this.#metrics.init(contentEl);
172
+ this.#listenContentElement(contentEl, gutterEl);
419
173
  }
420
174
  this.#lineYCache.clear();
421
175
  this.#wrapLineOffsetsCache.clear();
422
- this.#lastCharX = void 0;
176
+ this.#lastAccessedLineElement = void 0;
177
+ this.#lastAccessedCharX = void 0;
178
+ this.#wrap = this.#fileInstance?.options.overflow === "wrap";
423
179
  this.#lineAnnotations = lineAnnotations;
424
180
  this.#renderRange = renderRange;
425
- this.#tokenizer?.prebuildStateStackMap(renderRange);
181
+ this.#tokenizer?.prebuildStateStack(renderRange);
426
182
  if (this.#initSelections !== void 0) {
427
183
  this.setSelections(this.#initSelections);
428
184
  this.#scrollToPrimaryCaret();
429
185
  this.#initSelections = void 0;
430
- } 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 ?? []);
431
187
  if (this.#options.__debug === true && renderRange !== void 0) {
432
188
  const { startingLine, totalLines } = renderRange;
433
189
  console.log("[diffs/editor] render file:", fileContents.name, "RenderRange:", startingLine + "-" + (startingLine + totalLines), "of", this.#textDocument.lineCount, "lines");
434
190
  }
435
- if (this.#scrollingToLine !== void 0) {
436
- this.#scrollToLine(this.#scrollingToLine, this.#scrollingToLineChar);
437
- this.#scrollingToLine = void 0;
438
- this.#scrollingToLineChar = void 0;
439
- } else if (this.#selections !== void 0 && this.#selections.length > 0) this.focus({ preventScroll: true });
440
- if (this.#retainSearchPanelFocus) {
441
- this.#retainSearchPanelFocus = false;
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 });
193
+ if (this.#retainSearchPanelFocus) this.#searchPanel?.focus();
194
+ if (this.#selectionAction !== void 0 && this.#isLineVisible(this.#selectionAction.line) && this.#contentElement !== void 0) this.#selectionAction.render(this.#contentElement);
195
+ }
196
+ postponeBackgroundTokenizeToNextFrame() {
197
+ const tokenizer = this.#tokenizer;
198
+ if (tokenizer !== void 0) {
199
+ tokenizer.pauseBackgroundTokenize();
442
200
  requestAnimationFrame(() => {
443
- this.#searchPanel?.focus();
201
+ tokenizer.resumeBackgroundTokenize();
202
+ });
203
+ }
204
+ }
205
+ setSelections(selections) {
206
+ const textDocument = this.#textDocument;
207
+ if (textDocument !== void 0) {
208
+ const resolvedSelections = selections.map((selection) => {
209
+ const start = textDocument.normalizePosition(selection.start);
210
+ const end = textDocument.normalizePosition(selection.end);
211
+ return {
212
+ direction: selection.direction === "none" ? DirectionNone : selection.direction === "backward" ? DirectionBackward : DirectionForward,
213
+ start,
214
+ end
215
+ };
444
216
  });
217
+ this.#updateSelections(resolvedSelections);
218
+ this.#scrollToPrimaryCaret();
219
+ } else this.#initSelections = selections;
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;
445
229
  }
446
- if (this.#quickEdit !== void 0 && this.#isLineVisible(this.#quickEdit.line) && this.#contentElement !== void 0) this.#quickEdit.render(this.#contentElement);
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
+ }
241
+ focus(options) {
242
+ const preventScroll = options?.preventScroll ?? false;
243
+ const primarySelection = this.#selections?.at(-1);
244
+ if (primarySelection !== void 0) {
245
+ const pos = primarySelection.direction === DirectionBackward ? primarySelection.end : primarySelection.start;
246
+ this.#focus(pos, preventScroll);
247
+ } else this.#focus(void 0, preventScroll);
248
+ }
249
+ cleanUp() {
250
+ this.#tokenizer?.cleanUp();
251
+ this.#tokenizer = void 0;
252
+ this.#globalEventDisposes?.forEach((dispose) => dispose());
253
+ this.#globalEventDisposes = void 0;
254
+ this.#editorEventDisposes?.forEach((dispose) => dispose());
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;
260
+ this.#detach?.();
261
+ this.#detach = void 0;
262
+ this.#fileInstance?.setSelectedLines(null);
263
+ this.#fileInstance = void 0;
264
+ this.#fileContents = void 0;
265
+ this.#lineAnnotations = void 0;
266
+ this.#textDocument = void 0;
267
+ this.#renderRange = void 0;
268
+ this.#gutterWidthCache = void 0;
269
+ this.#contentWidthCache = void 0;
270
+ this.#lineYCache.clear();
271
+ this.#wrapLineOffsetsCache.clear();
272
+ this.#lastAccessedLineElement = void 0;
273
+ this.#lastAccessedCharX = void 0;
274
+ this.#globalStyleElement?.remove();
275
+ this.#globalStyleElement = void 0;
276
+ this.#editorStyleElement?.remove();
277
+ this.#editorStyleElement = void 0;
278
+ this.#themeStyleElement?.remove();
279
+ this.#themeStyleElement = void 0;
280
+ this.#spriteElement?.remove();
281
+ this.#spriteElement = void 0;
282
+ this.#fileContainer = void 0;
283
+ this.#gutterElement = void 0;
284
+ this.#contentElement?.removeAttribute("contentEditable");
285
+ this.#contentElement = void 0;
286
+ this.#overlayElement?.remove();
287
+ this.#overlayElement = void 0;
288
+ this.#overlayElements?.forEach((el) => el.remove());
289
+ this.#overlayElements = void 0;
290
+ this.#primaryCaretElement = void 0;
291
+ this.#searchPanel?.cleanup();
292
+ this.#searchPanel = void 0;
293
+ this.#selectionAction?.cleanup();
294
+ this.#selectionAction = void 0;
295
+ this.#resizeObserver?.disconnect();
296
+ this.#resizeObserver = void 0;
297
+ this.#shouldIgnoreSelectionChange = false;
298
+ this.#selectionStart = void 0;
299
+ this.#selections = void 0;
300
+ this.#reservedSelections = void 0;
447
301
  }
448
302
  #initialize() {
303
+ this.#globalStyleElement = h("style", {
304
+ dataset: "editorGlobalCss",
305
+ textContent: `
306
+ [data-annotation-slot] {
307
+ user-select: none;
308
+ -webkit-user-select: none;
309
+ }
310
+ `
311
+ });
449
312
  this.#editorStyleElement = h("style", {
450
313
  dataset: "editorCss",
451
- textContent: editorCSS
314
+ textContent: editor_default
452
315
  });
453
316
  this.#themeStyleElement = h("style", { dataset: "editorThemeCss" });
454
- this.#globalStyleElement = h("style", {
455
- dataset: "editorGlobalCss",
456
- textContent: editorGlobalCSS
457
- });
317
+ this.#spriteElement = createSpriteElement();
458
318
  this.#overlayElement = h("div", { dataset: "editorOverlay" });
459
319
  this.#globalEventDisposes = [
460
320
  addEventListener(document, "selectionchange", () => {
461
- const shadowRoot = this.#componentContainer?.shadowRoot;
321
+ const shadowRoot = this.#fileContainer?.shadowRoot;
462
322
  if (this.#shouldIgnoreSelectionChange || shadowRoot == null) return;
323
+ if (this.#selections !== void 0 && this.#selections.length > 1 && !this.#isContentMouseDown) return;
463
324
  const composedRange = document.getSelection()?.getComposedRanges({ shadowRoots: [shadowRoot] })?.[0];
464
325
  if (composedRange === void 0 || !this.#rangeBelongsToEditor(composedRange)) return;
465
326
  let selection = convertSelection(composedRange, DirectionNone);
@@ -477,8 +338,8 @@ var Editor = class {
477
338
  }, { passive: true }),
478
339
  addEventListener(document, "pointerup", (e) => {
479
340
  if (e.pointerType !== "mouse") return;
480
- this.#mouseUpDisposes?.forEach((dispose) => dispose());
481
- this.#mouseUpDisposes = void 0;
341
+ this.#selectEventDisposes?.forEach((dispose) => dispose());
342
+ this.#selectEventDisposes = void 0;
482
343
  if (this.#isGutterMouseDown) {
483
344
  this.#isGutterMouseDown = false;
484
345
  this.#focus();
@@ -488,8 +349,8 @@ var Editor = class {
488
349
  this.#shiftKeyPressed = false;
489
350
  this.#selectionStart = void 0;
490
351
  this.#reservedSelections = void 0;
491
- this.#selectionElements?.forEach((el, key) => {
492
- if (key.startsWith("quickEditIcon-")) el.dataset.visible = "true";
352
+ this.#overlayElements?.forEach((el, key) => {
353
+ if (key.startsWith("selectionActionIcon-")) el.dataset.visible = "true";
493
354
  });
494
355
  }, { passive: true }),
495
356
  addEventListener(document, "keydown", (e) => {
@@ -500,6 +361,176 @@ var Editor = class {
500
361
  }, { passive: true })
501
362
  ];
502
363
  }
364
+ #listenContentElement(contentEl, gutterEl) {
365
+ const targetIsContentElement = (e) => {
366
+ const target = e.composedPath()[0];
367
+ return target !== void 0 && (target === contentEl || contentEl.contains(target));
368
+ };
369
+ this.#editorEventDisposes?.forEach((dispose) => dispose());
370
+ this.#editorEventDisposes = [
371
+ addEventListener(contentEl, "pointerdown", (e) => {
372
+ if (e.pointerType !== "mouse") return;
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", () => {
375
+ this.#shouldIgnoreSelectionChange = true;
376
+ }), addEventListener(el, "mouseleave", () => {
377
+ this.#shouldIgnoreSelectionChange = false;
378
+ })]).flat();
379
+ this.#isContentMouseDown = true;
380
+ this.#selectionStart = void 0;
381
+ if (e.button === 0 && isPrimaryModifier(e)) this.#reservedSelections = this.#selections?.map((selection) => ({ ...selection }));
382
+ if (e.shiftKey) {
383
+ const primarySelection = this.#selections?.at(-1);
384
+ if (primarySelection !== void 0) {
385
+ const pos = primarySelection.direction === DirectionBackward ? primarySelection.end : primarySelection.start;
386
+ this.#setWindowSelection({
387
+ start: pos,
388
+ end: pos,
389
+ direction: DirectionNone
390
+ });
391
+ }
392
+ this.#shiftKeyPressed = true;
393
+ }
394
+ }, { passive: true }),
395
+ addEventListener(contentEl, "keydown", (e) => {
396
+ if (e.key === "Escape") {
397
+ e.preventDefault();
398
+ this.#searchPanel?.cleanup();
399
+ this.#searchPanel = void 0;
400
+ this.#retainSearchPanelFocus = false;
401
+ this.#selectionAction?.cleanup();
402
+ this.#selectionAction = void 0;
403
+ if (this.#selections !== void 0 && this.#selections.length > 0) {
404
+ const primarySelection = this.#selections.at(-1);
405
+ if (!isCollapsedSelection(primarySelection) || this.#selections.length > 1) {
406
+ const pos = getCaretPosition(primarySelection);
407
+ this.#updateSelections([{
408
+ start: pos,
409
+ end: pos,
410
+ direction: DirectionNone
411
+ }]);
412
+ this.#focus(pos);
413
+ }
414
+ }
415
+ return;
416
+ }
417
+ if (!targetIsContentElement(e)) return;
418
+ const mvShortcut = isMoveCursorShortcut(e);
419
+ const textDocument = this.#textDocument;
420
+ if (this.#selections !== void 0 && this.#selections.length > 0 && mvShortcut !== void 0 && textDocument !== void 0) {
421
+ if (e.shiftKey) this.#updateSelections(mapSelectionShift(textDocument, this.#selections, mvShortcut));
422
+ else this.#updateSelections(mapCursorMove(textDocument, this.#selections, mvShortcut));
423
+ this.#scrollToPrimaryCaret();
424
+ e.preventDefault();
425
+ return;
426
+ }
427
+ const command = resolveEditorCommandFromKeyboardEvent(e);
428
+ if (command !== void 0) {
429
+ e.preventDefault();
430
+ this.#runCommand(command);
431
+ }
432
+ }),
433
+ addEventListener(contentEl, "copy", (e) => {
434
+ if (!targetIsContentElement(e)) return;
435
+ e.preventDefault();
436
+ e.clipboardData?.setData("text", this.#getSelectionText());
437
+ }),
438
+ addEventListener(contentEl, "cut", (e) => {
439
+ if (!targetIsContentElement(e)) return;
440
+ e.preventDefault();
441
+ e.clipboardData?.setData("text", this.#getSelectionText());
442
+ this.#replaceSelectionText("");
443
+ }),
444
+ addEventListener(contentEl, "paste", (e) => {
445
+ if (!targetIsContentElement(e)) return;
446
+ e.preventDefault();
447
+ const text = e.clipboardData?.getData("text");
448
+ if (text !== void 0) this.#replaceSelectionText(text);
449
+ }),
450
+ addEventListener(contentEl, "beforeinput", (e) => {
451
+ if (!targetIsContentElement(e)) return;
452
+ e.preventDefault();
453
+ this.#handleInput(e.inputType, e.data);
454
+ }),
455
+ addEventListener(contentEl, "drop", (e) => {
456
+ if (!targetIsContentElement(e)) return;
457
+ e.preventDefault();
458
+ }),
459
+ addEventListener(contentEl, "compositionstart", (e) => {
460
+ if (!targetIsContentElement(e)) return;
461
+ this.#shouldIgnoreSelectionChange = true;
462
+ }, { passive: true }),
463
+ addEventListener(contentEl, "compositionend", (e) => {
464
+ if (!targetIsContentElement(e)) return;
465
+ this.#shouldIgnoreSelectionChange = false;
466
+ this.#handleInput("insertText", e.data);
467
+ }, { passive: true })
468
+ ];
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;
475
+ };
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;
508
+ let selection$1 = {
509
+ start: {
510
+ line: lineIndex$1,
511
+ character: 0
512
+ },
513
+ end: {
514
+ line: lineIndex$1,
515
+ character: textDocument$1.getLineText(lineIndex$1).length
516
+ },
517
+ direction: DirectionForward
518
+ };
519
+ if (this.#selectionStart !== void 0) selection$1 = createSelectionFrom(this.#selectionStart, selection$1);
520
+ else this.#selectionStart = selection$1;
521
+ this.#updateSelections([selection$1]);
522
+ this.#focus(selection$1.end);
523
+ }, { passive: true })];
524
+ }, { passive: true }));
525
+ }
526
+ this.#markerManager?.listenHover(contentEl);
527
+ this.#resizeObserver?.disconnect();
528
+ this.#resizeObserver = new ResizeObserver(() => {
529
+ this.#handleLayoutResize();
530
+ });
531
+ this.#resizeObserver.observe(contentEl);
532
+ this.#resizeObserver.observe(contentEl.parentElement);
533
+ }
503
534
  #runCommand(command) {
504
535
  const textDocument = this.#textDocument;
505
536
  if (textDocument === void 0) return;
@@ -509,17 +540,16 @@ var Editor = class {
509
540
  break;
510
541
  case "findNextMatch": {
511
542
  const selections = this.#selections;
512
- const textDocument$1 = this.#textDocument;
513
- if (selections === void 0 || textDocument$1 === void 0) break;
543
+ if (selections === void 0) break;
514
544
  if (selections.some(isCollapsedSelection)) {
515
545
  const expanded = selections.map((sel) => {
516
- if (isCollapsedSelection(sel)) return expandCollapsedSelectionToWord(textDocument$1, sel);
546
+ if (isCollapsedSelection(sel)) return expandCollapsedSelectionToWord(textDocument, sel);
517
547
  return sel;
518
548
  });
519
549
  this.#updateSelections(expanded);
520
550
  this.focus();
521
551
  } else {
522
- const nextMatch = findNexMatch(textDocument$1, selections);
552
+ const nextMatch = findNexMatch(textDocument, selections);
523
553
  if (nextMatch !== void 0) {
524
554
  this.#updateSelections(nextMatch);
525
555
  this.#scrollToPrimaryCaret();
@@ -597,50 +627,40 @@ var Editor = class {
597
627
  const gutterWidthChanged = this.#getGutterWidth() !== prevGutterWidth;
598
628
  const contentWidthChanged = this.#getContentWidth() !== prevContentWidth;
599
629
  if (!gutterWidthChanged && !contentWidthChanged) return;
600
- this.#lastCharX = void 0;
630
+ this.#lastAccessedLineElement = void 0;
631
+ this.#lastAccessedCharX = void 0;
601
632
  if (contentWidthChanged && (this.#wrap || lineAnnotations > 0)) {
602
633
  this.#lineYCache.clear();
603
634
  this.#wrapLineOffsetsCache.clear();
604
635
  }
605
- if (this.#selections !== void 0) {
606
- this.#updateSelections(this.#selections);
607
- 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();
608
639
  }
640
+ this.#markerManager?.removePopup();
609
641
  }
610
642
  #rerender(change, newLineAnnotations, renderRange = this.#renderRange, shouldUpdateBuffer) {
611
643
  const tokenizer = this.#tokenizer;
612
- const component = this.#component;
644
+ const fileInstance = this.#fileInstance;
613
645
  const fileContents = this.#fileContents;
614
646
  const textDocument = this.#textDocument;
647
+ const gutterEl = this.#gutterElement;
615
648
  const contentEl = this.#contentElement;
616
- const gutterEl = this.#contentElement?.previousElementSibling ?? void 0;
617
- 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;
618
650
  tokenizer.stopBackgroundTokenize();
619
651
  const t = performance.now();
620
- const isAdvancedMode = this.#editMode === "advanced";
621
652
  const dirtyLines = tokenizer.tokenize(change, renderRange);
622
653
  const t2 = performance.now();
623
654
  if (dirtyLines.size > 0) {
624
655
  const children = contentEl.children;
625
656
  const dirtyLineIndexes = new Set(dirtyLines.keys());
626
- if (isAdvancedMode) for (const child of children) {
627
- const el = child;
628
- if (el.dataset.line !== void 0) {
629
- const lineIndex = Number(el.dataset.line) - 1;
630
- const tokens = dirtyLines.get(lineIndex);
631
- if (tokens !== void 0) {
632
- el.replaceChildren(...renderLineTokens(tokens, tokenizer.themeType));
633
- dirtyLineIndexes.delete(lineIndex);
634
- if (dirtyLineIndexes.size === 0) break;
635
- }
636
- }
637
- }
638
- else {
639
- const startingLine = renderRange?.startingLine ?? 0;
640
- for (let i = change.startLine - startingLine; i < children.length; i++) {
641
- const child = children[i];
642
- if (child?.dataset.line !== void 0) {
643
- const lineIndex = Number(child.dataset.line) - 1;
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
+ const lineIndex = lineNumber - 1;
644
664
  if (dirtyLines.has(lineIndex)) {
645
665
  const tokens = dirtyLines.get(lineIndex);
646
666
  child.replaceChildren(...renderLineTokens(tokens, tokenizer.themeType));
@@ -661,7 +681,7 @@ var Editor = class {
661
681
  },
662
682
  children: renderLineTokens(tokens, tokenizer.themeType)
663
683
  }, contentEl);
664
- h("div", {
684
+ if (gutterEl !== void 0) h("div", {
665
685
  dataset: {
666
686
  lineType: "context",
667
687
  columnNumber: lineNumber,
@@ -674,23 +694,30 @@ var Editor = class {
674
694
  }, gutterEl);
675
695
  }
676
696
  }
677
- if (change.lineDelta < 0) for (const parent of [contentEl, gutterEl]) {
678
- const children = parent.children;
679
- for (let i = children.length - 1; i >= 0; i--) {
680
- const child = children[i];
681
- const { lineIndex, lineAnnotation } = child.dataset;
682
- if (lineIndex !== void 0 || lineAnnotation !== void 0) {
683
- if (Number(lineAnnotation !== void 0 ? lineAnnotation.split(",")[1] : lineIndex) < change.lineCount) break;
684
- child.remove();
685
- }
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;
686
711
  }
712
+ contentEl.style.gridRow = "span " + gridRow;
713
+ if (gutterEl !== void 0) gutterEl.style.gridRow = "span " + gridRow;
687
714
  }
688
- if (change.lineDelta !== 0) {
689
- gutterEl.style.gridRow = "span " + gutterEl.children.length;
690
- 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);
691
720
  }
692
- component.applyLineChange?.(dirtyLines, tokenizer.themeType);
693
- if (change.lineDelta !== 0 || isAdvancedMode) component.applyLayoutChange(textDocument, newLineAnnotations, shouldUpdateBuffer);
694
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)`);
695
722
  }
696
723
  #handleInput(inputType, data) {
@@ -707,52 +734,40 @@ var Editor = class {
707
734
  case "deleteContentForward":
708
735
  this.#deleteSelectionText(true);
709
736
  break;
737
+ case "deleteSoftLineBackward":
738
+ this.#deleteSoftLineBackward();
739
+ break;
710
740
  case "deleteHardLineForward":
711
741
  this.#deleteHardLineForward();
712
742
  break;
743
+ case "deleteWordBackward":
744
+ this.#deleteWordBackward();
745
+ break;
713
746
  case "insertTranspose":
714
747
  this.#insertTranspose();
715
748
  break;
716
749
  default:
717
- console.warn(`[diffs] Unknown input type: ${inputType}`);
750
+ console.warn(`[diffs] Unknown input type: ${inputType}`, data);
718
751
  break;
719
752
  }
720
753
  }
721
- #updateSelections(selections) {
722
- if (selections.length === 0) return;
723
- const gutterBuffer = this.#contentElement?.previousElementSibling;
724
- const normalizedSelections = mergeOverlappingSelections(selections);
725
- const primarySelection = normalizedSelections.at(-1);
726
- this.#selections = normalizedSelections;
727
- this.#primaryCaretElement = void 0;
728
- this.#component?.setSelectedLines(null);
729
- gutterBuffer?.querySelectorAll("[data-active]").forEach((el) => el.removeAttribute("data-active"));
730
- if (isCollapsedSelection(primarySelection)) {
731
- const line = primarySelection.start.line + 1;
732
- this.#component?.setSelectedLines({
733
- start: line,
734
- 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
735
761
  });
736
- } else if (gutterBuffer !== void 0 && gutterBuffer instanceof HTMLElement) {
737
- const pos = getCaretPosition(primarySelection);
738
- gutterBuffer.querySelector(`[data-column-number="${pos.line + 1}"]`)?.setAttribute("data-active", "");
739
- }
740
- const fragment = document.createDocumentFragment();
741
- const renderCtx = {
742
- fragment,
743
- elements: /* @__PURE__ */ new Map()
744
- };
745
- for (const selection of normalizedSelections) {
746
- if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, selection);
747
- this.#renderCaret(renderCtx, selection, selection === primarySelection);
748
- }
749
- if (this.#options.enabledQuickEdit === true && !isCollapsedSelection(primarySelection)) this.#renderQuickEditIcon(renderCtx, primarySelection);
750
- this.#overlayElement?.appendChild(fragment);
751
- this.#selectionElements?.forEach((el) => el.remove());
752
- this.#selectionElements?.clear();
753
- this.#selectionElements = renderCtx.elements;
762
+ requestAnimationFrame(() => {
763
+ this.#contentElement?.focus({ preventScroll });
764
+ requestAnimationFrame(() => {
765
+ this.#shouldIgnoreSelectionChange = false;
766
+ });
767
+ });
768
+ } else this.#contentElement?.focus({ preventScroll });
754
769
  }
755
- #updateWindowSelection(selection) {
770
+ #setWindowSelection(selection) {
756
771
  const winSelection = window.getSelection();
757
772
  if (winSelection === null) return;
758
773
  let { start, end, direction } = selection;
@@ -774,25 +789,7 @@ var Editor = class {
774
789
  console.error("[diffs/editor] failed to update window selection:", err);
775
790
  }
776
791
  }
777
- #focus(position, preventScroll = true) {
778
- if (position !== void 0) {
779
- this.#shouldIgnoreSelectionChange = true;
780
- this.#updateWindowSelection({
781
- start: position,
782
- end: position,
783
- direction: DirectionNone
784
- });
785
- requestAnimationFrame(() => {
786
- this.#contentElement?.focus({ preventScroll });
787
- requestAnimationFrame(() => {
788
- this.#shouldIgnoreSelectionChange = false;
789
- });
790
- });
791
- } else requestAnimationFrame(() => {
792
- this.#contentElement?.focus({ preventScroll });
793
- });
794
- }
795
- #scrollToPrimaryCaret() {
792
+ #scrollToPrimaryCaret(noFocus = false) {
796
793
  const primaryCaretElement = this.#primaryCaretElement;
797
794
  const primarySelection = this.#selections?.at(-1);
798
795
  if (primarySelection === void 0) return;
@@ -801,20 +798,21 @@ var Editor = class {
801
798
  block: "nearest",
802
799
  inline: "nearest"
803
800
  });
804
- this.#focus(primarySelection.direction === DirectionBackward ? primarySelection.end : primarySelection.start);
801
+ if (!noFocus) this.#focus(primarySelection.direction === DirectionBackward ? primarySelection.end : primarySelection.start);
805
802
  } else {
806
803
  const pos = getCaretPosition(primarySelection);
807
- this.#scrollToLine(pos.line, pos.character);
804
+ this.#scrollToLine(pos.line, pos.character, noFocus);
808
805
  }
809
806
  }
810
807
  #getScrollMargin() {
811
- const componentTop = this.#component?.top ?? 0;
808
+ const componentTop = this.#fileInstance?.top ?? 0;
812
809
  const top = this.#searchPanel !== void 0 ? 48 : 0;
813
810
  const start = this.#getGutterWidth() + this.#metrics.ch;
814
811
  const end = this.#metrics.ch;
815
812
  return `${componentTop + top}px ${end}px 0 ${start}px`;
816
813
  }
817
- #scrollToLine(line, char = 0) {
814
+ #scrollToLine(line, char = 0, noFocus = false) {
815
+ this.postponeBackgroundTokenizeToNextFrame();
818
816
  const virtualCaret = h("div", { style: {
819
817
  position: "absolute",
820
818
  left: "0",
@@ -832,111 +830,259 @@ var Editor = class {
832
830
  block: "center",
833
831
  inline: "nearest"
834
832
  });
835
- this.#focus({
833
+ if (!noFocus) this.#focus({
836
834
  line,
837
835
  character: char
838
836
  });
839
- requestAnimationFrame(() => virtualCaret.remove());
837
+ this.#scrollingToLine = void 0;
838
+ this.#scrollingToLineChar = void 0;
839
+ this.#scrollingToLineNoFocus = false;
840
840
  } else {
841
- 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;
842
852
  virtualCaret.style.top = approximateLineY + "px";
843
- this.#componentContainer?.shadowRoot?.appendChild(virtualCaret);
844
- this.#scrollingToLine = line;
845
- this.#scrollingToLineChar = char;
853
+ this.#fileContainer?.shadowRoot?.appendChild(virtualCaret);
846
854
  virtualCaret.scrollIntoView({
847
855
  block: "center",
848
856
  inline: "nearest"
849
857
  });
850
- requestAnimationFrame(() => virtualCaret.remove());
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
+ }
867
+ }
868
+ virtualCaret.remove();
869
+ }
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
+ }
851
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;
852
925
  }
853
- #renderSelection(renderCtx, selection) {
926
+ #renderSelection(renderCtx, type, range, extraDataset) {
854
927
  if (this.#textDocument === void 0) return;
855
- const { start, end } = selection;
856
- for (let ln = start.line; ln <= end.line; ln++) {
857
- if (!this.#isLineVisible(ln)) continue;
858
- const lineText = this.#textDocument.getLineText(ln);
859
- const startChar = ln === start.line ? start.character : 0;
860
- const endChar = ln === end.line ? end.character : lineText.length;
928
+ const { start, end } = range;
929
+ for (let line = start.line; line <= end.line; line++) {
930
+ if (!this.#isLineVisible(line)) continue;
931
+ const isLastLine = line === end.line;
932
+ const lineText = this.#textDocument.getLineText(line);
933
+ const startChar = line === start.line ? start.character : 0;
934
+ const endChar = isLastLine ? end.character : lineText.length;
861
935
  if (this.#wrap) {
862
- const paddingInline = this.#metrics.ch;
863
936
  const contentWidth = this.#getContentWidth();
864
- if (2 * paddingInline + this.#metrics.measureTextWidth(lineText) > contentWidth) {
865
- this.#renderWrappedSelection(renderCtx, selection, ln, lineText, startChar, endChar, paddingInline);
937
+ if (2 * this.#metrics.ch + this.#metrics.measureTextWidth(lineText) > contentWidth) {
938
+ this.#renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset);
866
939
  continue;
867
940
  }
868
941
  }
869
942
  let left = 0;
870
943
  let width = 0;
871
- if (startChar === endChar && startChar === 0) {
872
- left = this.#getGutterWidth() + this.#metrics.ch;
873
- width = ln === end.line ? 0 : this.#metrics.ch;
874
- } else {
875
- left = this.#getCharX(ln, startChar)[0];
876
- width = endChar === startChar ? 0 : this.#getCharX(ln, endChar)[0] - left;
877
- }
878
- this.#renderSelectionRange(renderCtx, selection, ln, 0, startChar, endChar, width, left);
944
+ let paddingEnd = 0;
945
+ if (startChar === 0) left = this.#getGutterWidth() + this.#metrics.ch;
946
+ else left = this.#getCharX(line, startChar)[0];
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);
879
951
  }
880
952
  }
881
- #renderWrappedSelection(renderCtx, selection, line, lineText, startChar, endChar, paddingInline) {
953
+ #renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset) {
882
954
  const wrapOffsets = this.#wrapLineText(line);
883
955
  const segmentCount = wrapOffsets.length - 1;
884
- const lastSegmentIndex = segmentCount - 1;
885
- const offsetLeft = this.#getGutterWidth() + paddingInline;
886
- for (let w = 0; w < segmentCount; w++) {
887
- const segmentStart = wrapOffsets[w];
888
- const segmentEnd = wrapOffsets[w + 1];
956
+ const offsetLeft = this.#getGutterWidth() + this.#metrics.ch;
957
+ for (let wrapLine = 0; wrapLine < segmentCount; wrapLine++) {
958
+ const segmentStart = wrapOffsets[wrapLine];
959
+ const segmentEnd = wrapOffsets[wrapLine + 1];
889
960
  const wrapStartChar = Math.max(startChar, segmentStart);
890
961
  const wrapEndChar = Math.min(endChar, segmentEnd);
891
962
  if (wrapStartChar > wrapEndChar) continue;
892
- if (wrapStartChar === wrapEndChar) {
893
- const isAtLineStart = wrapStartChar === 0 && w === 0;
894
- const isAtLineEnd = wrapEndChar === lineText.length && w === lastSegmentIndex;
895
- if (!isAtLineStart && !isAtLineEnd) continue;
896
- }
897
963
  let segmentLeft;
898
964
  let segmentWidth;
899
- if (wrapStartChar === 0 && wrapEndChar === 0) {
900
- segmentLeft = offsetLeft;
901
- segmentWidth = line === selection.end.line ? 0 : paddingInline;
902
- } else {
965
+ let paddingEnd = 0;
966
+ if (wrapStartChar === 0) segmentLeft = offsetLeft;
967
+ else {
903
968
  const prefixInSegment = lineText.slice(segmentStart, wrapStartChar);
904
969
  const prefixAsciiColumns = getExpandedAsciiTextColumns(prefixInSegment, this.#metrics.tabSize);
905
970
  segmentLeft = offsetLeft + (prefixAsciiColumns !== -1 ? prefixAsciiColumns * this.#metrics.ch : this.#metrics.measureTextWidth(prefixInSegment));
906
- if (wrapStartChar === wrapEndChar) segmentWidth = 0;
907
- else {
908
- const selectionInSegment = lineText.slice(wrapStartChar, wrapEndChar);
909
- const selectionAsciiWidth = getExpandedAsciiTextColumns(selectionInSegment, this.#metrics.tabSize);
910
- segmentWidth = selectionAsciiWidth !== -1 ? selectionAsciiWidth * this.#metrics.ch : this.#metrics.measureTextWidth(selectionInSegment);
911
- }
912
971
  }
913
- this.#renderSelectionRange(renderCtx, selection, line, w, wrapStartChar, wrapEndChar, segmentWidth, segmentLeft, w === lastSegmentIndex);
972
+ if (!isLastLine && wrapLine === segmentCount - 1 && type === "selection") paddingEnd = this.#metrics.ch;
973
+ if (wrapStartChar === wrapEndChar) segmentWidth = paddingEnd;
974
+ else {
975
+ const selectionInSegment = lineText.slice(wrapStartChar, wrapEndChar);
976
+ const selectionAsciiWidth = getExpandedAsciiTextColumns(selectionInSegment, this.#metrics.tabSize);
977
+ segmentWidth = selectionAsciiWidth !== -1 ? selectionAsciiWidth * this.#metrics.ch : this.#metrics.measureTextWidth(selectionInSegment);
978
+ segmentWidth += paddingEnd;
979
+ }
980
+ this.#renderSelectionBlock(renderCtx, type, line, wrapLine, segmentLeft, segmentWidth, extraDataset);
914
981
  }
915
982
  }
916
- #renderSelectionRange(renderCtx, selection, ln, wrapLine, startChar, endChar, width, left, applyEolSpacing = true) {
917
- const css = `width:${width + (!applyEolSpacing || selection.end.line === ln || startChar === endChar && ln !== selection.start.line ? 0 : this.#metrics.ch)}px;transform:translateY(${this.#getLineY(ln) + wrapLine * this.#metrics.lineHeight}px) translateX(${left}px);`;
918
- const cacheKey = "selection-range-" + css;
919
- const selectionEls = this.#selectionElements;
920
- if (renderCtx.elements.has(cacheKey)) return;
921
- let rangeEl;
922
- if (selectionEls?.has(cacheKey) === true) {
923
- rangeEl = selectionEls.get(cacheKey);
924
- selectionEls.delete(cacheKey);
983
+ #renderSelectionBlock(renderCtx, type, line, wrapLine, left, width, extraDataset) {
984
+ if (width === 0) return;
985
+ const { ch, lineHeight } = this.#metrics;
986
+ const y = this.#getLineY(line) + wrapLine * lineHeight;
987
+ const css = `width:${width}px;transform:translateX(${left}px) translateY(${y}px);`;
988
+ const cacheKey = `${type}-${left}-${y}-${width}${extraDataset ?? ""}`;
989
+ const overlayEls = this.#overlayElements;
990
+ const rounded = (this.#options.roundedSelection ?? true) && type === "selection";
991
+ const addRoundedCorner = (line$1, wrapLine$1, left$1, radius) => {
992
+ const top = this.#getLineY(line$1) + wrapLine$1 * lineHeight;
993
+ const css$1 = `width:${ch}px;transform:translateX(${left$1}px) translateY(${top}px);`;
994
+ const dataset = {
995
+ selectionCorner: "",
996
+ [radius]: ""
997
+ };
998
+ const cacheKeyPrefix = `${type}-block-${left$1}-${top}-1ch`;
999
+ let cacheKey$1 = cacheKeyPrefix + "-" + radius;
1000
+ if (radius === "rbl") {
1001
+ const prevCornerKey = cacheKeyPrefix + "-rtl";
1002
+ const prevCorner = renderCtx.elements.get(prevCornerKey);
1003
+ if (prevCorner !== void 0) {
1004
+ prevCorner.remove();
1005
+ renderCtx.elements.delete(prevCornerKey);
1006
+ cacheKey$1 += "-rtl";
1007
+ dataset.rtl = "";
1008
+ }
1009
+ }
1010
+ let cornerEl = renderCtx.elements.get(cacheKey$1);
1011
+ if (cornerEl !== void 0) return;
1012
+ if (overlayEls?.has(cacheKey$1) === true) {
1013
+ cornerEl = overlayEls.get(cacheKey$1);
1014
+ overlayEls.delete(cacheKey$1);
1015
+ } else cornerEl = h("div", {
1016
+ dataset: "selectionRange",
1017
+ style: { cssText: css$1 },
1018
+ children: [h("div", { dataset })]
1019
+ }, renderCtx.fragment);
1020
+ renderCtx.elements.set(cacheKey$1, cornerEl);
1021
+ };
1022
+ const addRadiusStyle = (element) => {
1023
+ const end = left + width;
1024
+ const dataset = element.dataset;
1025
+ const previousSelectionRange = renderCtx.previousSelectionRange;
1026
+ if (previousSelectionRange === void 0 || previousSelectionRange.line !== line || previousSelectionRange.wrapLine !== wrapLine) renderCtx.previousSelectionRange = {
1027
+ element,
1028
+ line,
1029
+ wrapLine,
1030
+ left,
1031
+ width
1032
+ };
1033
+ if (previousSelectionRange === void 0 || end <= previousSelectionRange.left) [
1034
+ "rtl",
1035
+ "rtr",
1036
+ "rbl",
1037
+ "rbr"
1038
+ ].forEach((key) => {
1039
+ dataset[key] = "";
1040
+ });
1041
+ else {
1042
+ const prevLine = previousSelectionRange.line;
1043
+ const prevWrapLine = previousSelectionRange.wrapLine;
1044
+ const prevLeft = previousSelectionRange.left;
1045
+ const prevDataset = previousSelectionRange.element.dataset;
1046
+ const prevEnd = prevLeft + previousSelectionRange.width;
1047
+ if (prevLeft > left) addRoundedCorner(prevLine, prevWrapLine, prevLeft - ch, "rbr");
1048
+ delete prevDataset.rbl;
1049
+ delete dataset.rtl;
1050
+ delete dataset.rtr;
1051
+ if (end >= prevEnd) delete prevDataset.rbr;
1052
+ if (end > prevEnd) {
1053
+ addRoundedCorner(prevLine, prevWrapLine, prevEnd, "rbl");
1054
+ dataset.rtr = "";
1055
+ }
1056
+ if (end < prevEnd) addRoundedCorner(line, wrapLine, end, "rtl");
1057
+ if (left < prevLeft) dataset.rtl = "";
1058
+ dataset.rbl = "";
1059
+ dataset.rbr = "";
1060
+ }
1061
+ };
1062
+ let rangeEl = renderCtx.elements.get(cacheKey);
1063
+ if (rangeEl !== void 0) {
1064
+ if (rounded) addRadiusStyle(rangeEl);
1065
+ return;
1066
+ }
1067
+ if (overlayEls?.has(cacheKey) === true) {
1068
+ rangeEl = overlayEls.get(cacheKey);
1069
+ overlayEls.delete(cacheKey);
925
1070
  } else rangeEl = h("div", {
926
- dataset: "selectionRange",
1071
+ dataset: extraDataset ? [type + "Range", extraDataset] : type + "Range",
927
1072
  style: { cssText: css }
928
1073
  }, renderCtx.fragment);
1074
+ if (rounded) addRadiusStyle(rangeEl);
929
1075
  renderCtx.elements.set(cacheKey, rangeEl);
930
1076
  }
931
1077
  #renderCaret(renderCtx, selection, isPrimary) {
932
1078
  const { line, character } = getCaretPosition(selection);
933
1079
  if (!this.#isLineVisible(line)) return;
934
1080
  const [left, wrapLine] = this.#getCharX(line, character);
935
- const cacheKey = "caret-" + line + "(" + wrapLine + ")-" + character;
1081
+ const cacheKey = "caret-" + line + "/" + wrapLine + ":" + character;
936
1082
  if (renderCtx.elements.has(cacheKey)) return;
937
1083
  const caretEl = h("div", {
938
1084
  dataset: "caret",
939
- style: { transform: `translateY(${this.#getLineY(line) + wrapLine * this.#metrics.lineHeight}px) translateX(${left - 1}px)` }
1085
+ style: { transform: `translateX(${left - 1}px) translateY(${this.#getLineY(line) + wrapLine * this.#metrics.lineHeight}px)` }
940
1086
  }, renderCtx.fragment);
941
1087
  renderCtx.elements.set(cacheKey, caretEl);
942
1088
  if (isPrimary) {
@@ -944,29 +1090,29 @@ var Editor = class {
944
1090
  this.#primaryCaretElement = caretEl;
945
1091
  }
946
1092
  }
947
- #renderQuickEditIcon(renderCtx, selection) {
1093
+ #renderSelectionActionIcon(renderCtx, selection) {
948
1094
  const line = getCaretPosition(selection).line;
949
1095
  if (!this.#isLineVisible(line)) return;
950
1096
  const [left, wrapLine] = this.#getCharX(line, 0);
951
- const cacheKey = "quickEditIcon-" + line + "(" + wrapLine + ")";
1097
+ const cacheKey = "selectionActionIcon-" + line + "(" + wrapLine + ")";
952
1098
  if (renderCtx.elements.has(cacheKey)) return;
953
- const quickEditIcon = QuickEditWidget.renderIcon(left, this.#getLineY(line) + wrapLine * this.#metrics.lineHeight, renderCtx.fragment, () => {
954
- const cleanUpQuickEdit = () => {
955
- this.#quickEdit?.cleanup();
956
- 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;
957
1103
  };
958
1104
  const handleWidgetDomResize = () => {
959
1105
  this.#lineYCache.clear();
960
1106
  if (this.#selections !== void 0) this.#updateSelections(this.#selections);
961
1107
  };
962
- cleanUpQuickEdit();
1108
+ cleanUpSelectionAction();
963
1109
  const textDocument = this.#textDocument;
964
- const renderQuickEdit = this.#options.renderQuickEdit;
965
- const fileContainer = this.#componentContainer;
966
- if (textDocument === void 0 || renderQuickEdit === void 0 || fileContainer == null) return;
1110
+ const renderSelectionAction = this.#options.renderSelectionAction;
1111
+ const fileContainer = this.#fileContainer;
1112
+ if (textDocument === void 0 || renderSelectionAction === void 0 || fileContainer == null) return;
967
1113
  const line$1 = selection.end.line;
968
1114
  const lineText = textDocument.getLineText(line$1);
969
- const quickEditElement = renderQuickEdit({
1115
+ const selectionActionElement = renderSelectionAction({
970
1116
  textDocument,
971
1117
  selection,
972
1118
  applyEdits: (edits) => {
@@ -980,7 +1126,7 @@ var Editor = class {
980
1126
  this.#replaceSelectionText(text);
981
1127
  },
982
1128
  close: () => {
983
- cleanUpQuickEdit();
1129
+ cleanUpSelectionAction();
984
1130
  handleWidgetDomResize();
985
1131
  this.#scrollToPrimaryCaret();
986
1132
  }
@@ -992,57 +1138,68 @@ var Editor = class {
992
1138
  else if (charCode === 9) leadingWhitespaces += this.#metrics.tabSize;
993
1139
  else break;
994
1140
  }
995
- this.#quickEdit = new QuickEditWidget(line$1, quickEditElement, fileContainer, leadingWhitespaces, handleWidgetDomResize);
1141
+ this.#selectionAction = new SelectionActionWidget(line$1, selectionActionElement, fileContainer, leadingWhitespaces, handleWidgetDomResize);
996
1142
  this.#updateSelections([selection]);
997
- 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);
998
1144
  });
999
- renderCtx.elements.set(cacheKey, quickEditIcon);
1145
+ renderCtx.elements.set(cacheKey, selectionActionIcon);
1000
1146
  }
1001
1147
  #renderSearchPanel() {
1002
1148
  this.#searchPanel?.cleanup();
1003
1149
  const textDocument = this.#textDocument;
1004
- const preElement = this.#componentContainer?.shadowRoot?.querySelector("pre");
1150
+ const preElement = this.#fileContainer?.shadowRoot?.querySelector("pre");
1151
+ const selections = this.#selections;
1005
1152
  if (textDocument === void 0 || preElement == null) return;
1006
1153
  let defaultQuery = "";
1007
1154
  let initialMatch = void 0;
1008
- const selections = this.#selections;
1009
1155
  if (selections !== void 0 && selections.length > 0) {
1010
1156
  let primarySelection = selections.at(-1);
1011
1157
  if (isCollapsedSelection(primarySelection)) {
1012
1158
  primarySelection = expandCollapsedSelectionToWord(textDocument, primarySelection);
1013
1159
  this.#updateSelections([...selections.slice(0, -1), primarySelection]);
1014
1160
  const selectionText = textDocument.getText(primarySelection);
1015
- if (!selectionText.includes("\n")) {
1161
+ if (selectionText !== "" && !selectionText.includes("\n")) {
1016
1162
  defaultQuery = selectionText;
1017
1163
  initialMatch = [textDocument.offsetAt(primarySelection.start), textDocument.offsetAt(primarySelection.end)];
1018
1164
  }
1019
1165
  }
1020
1166
  }
1167
+ const scrollToMatch = ([startOffset, endOffset], retainFocus) => {
1168
+ const nextSelection = createSelectionFromAnchorAndFocusOffsets(textDocument, startOffset, endOffset);
1169
+ this.#updateSelections([nextSelection]);
1170
+ this.#scrollToPrimaryCaret(true);
1171
+ this.#retainSearchPanelFocus = retainFocus;
1172
+ };
1021
1173
  this.#searchPanel = new SearchPanelWidget({
1022
1174
  textDocument,
1023
1175
  containerElement: preElement,
1024
1176
  defaultQuery,
1025
1177
  initialMatch,
1026
- getCurrentSearchRange: () => this.#selections?.at(-1),
1027
- postSearch: (kind, [startOffset, endOffset], retainFocus) => {
1028
- if (kind === "findNext" || kind === "findPrevious" || kind === "replace") {
1029
- const nextSelection = createSelectionFromAnchorAndFocusOffsets(textDocument, startOffset, endOffset);
1030
- this.#updateSelections([nextSelection]);
1031
- this.#scrollToPrimaryCaret();
1032
- if (retainFocus === true) {
1033
- this.#retainSearchPanelFocus = true;
1034
- requestAnimationFrame(() => {
1035
- this.#searchPanel?.focus();
1036
- });
1037
- }
1038
- } else if (kind === "findAll" || kind === "replaceAll") {
1039
- const { line, character } = textDocument.positionAt(startOffset);
1040
- this.#scrollToLine(line, character);
1178
+ scrollToMatch,
1179
+ onUpdate: (allMatches) => {
1180
+ if (allMatches.length === 0) {
1181
+ this.#matches = void 0;
1182
+ this.#updateSelections(this.#selections ?? []);
1183
+ return;
1041
1184
  }
1185
+ this.#matches = allMatches;
1186
+ const primarySelection = this.#selections?.at(-1);
1187
+ let searchOffset = 0;
1188
+ let nextMatch;
1189
+ if (primarySelection !== void 0) searchOffset = textDocument.offsetAt(primarySelection.start);
1190
+ for (const m of allMatches) if (m[0] >= searchOffset) {
1191
+ nextMatch = m;
1192
+ break;
1193
+ }
1194
+ if (nextMatch !== void 0) scrollToMatch(nextMatch, true);
1195
+ else this.#updateSelections(this.#selections ?? []);
1196
+ return nextMatch;
1042
1197
  },
1043
1198
  onClose: () => {
1044
1199
  this.#searchPanel = void 0;
1045
1200
  this.#retainSearchPanelFocus = false;
1201
+ this.#matches = void 0;
1202
+ this.#updateSelections(this.#selections ?? []);
1046
1203
  }
1047
1204
  });
1048
1205
  this.#retainSearchPanelFocus = false;
@@ -1072,21 +1229,44 @@ var Editor = class {
1072
1229
  if (selections === void 0 || textDocument === void 0) return;
1073
1230
  const primarySelection = selections.at(-1);
1074
1231
  if (primarySelection === void 0) return;
1075
- const edit = isCollapsedSelection(primarySelection) ? (() => {
1232
+ let edit;
1233
+ if (isCollapsedSelection(primarySelection)) {
1076
1234
  const offset = textDocument.offsetAt(primarySelection.start);
1077
1235
  const nextOffset = forward ? Math.min(textDocument.getText().length, offset + 1) : Math.max(0, offset - 1);
1078
- return {
1236
+ edit = {
1079
1237
  start: Math.min(offset, nextOffset),
1080
1238
  end: Math.max(offset, nextOffset),
1081
1239
  text: ""
1082
1240
  };
1083
- })() : {
1241
+ } else edit = {
1084
1242
  start: textDocument.offsetAt(primarySelection.start),
1085
1243
  end: textDocument.offsetAt(primarySelection.end),
1086
1244
  text: ""
1087
1245
  };
1088
1246
  this.#applyResolvedTextEdit(edit);
1089
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
+ }
1090
1270
  #deleteHardLineForward() {
1091
1271
  const selections = this.#selections;
1092
1272
  const textDocument = this.#textDocument;
@@ -1112,9 +1292,11 @@ var Editor = class {
1112
1292
  const onChange = this.#options.onChange;
1113
1293
  if (fileContents !== void 0 && textDocument !== void 0 && onChange !== void 0) {
1114
1294
  const { contents: _,...file } = fileContents;
1115
- let contents;
1116
- Object.defineProperty(file, "contents", { get: () => contents ??= textDocument.getText() });
1117
- 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);
1118
1300
  }
1119
1301
  if (change.lineDelta !== 0) {
1120
1302
  for (const line of this.#lineYCache.keys()) if (line >= change.startLine) this.#lineYCache.delete(line);
@@ -1122,7 +1304,7 @@ var Editor = class {
1122
1304
  if (this.#wrap) {
1123
1305
  for (const line of this.#wrapLineOffsetsCache.keys()) if (line >= change.startLine) this.#wrapLineOffsetsCache.delete(line);
1124
1306
  }
1125
- this.#lastCharX = void 0;
1307
+ this.#lastAccessedCharX = void 0;
1126
1308
  let renderRange = this.#renderRange;
1127
1309
  let shouldUpdateBuffer;
1128
1310
  if (renderRange !== void 0 && selections !== void 0 && selections.length > 0) {
@@ -1137,17 +1319,15 @@ var Editor = class {
1137
1319
  this.#rerender(change, newLineAnnotations, renderRange, shouldUpdateBuffer);
1138
1320
  if (selections !== void 0) {
1139
1321
  this.#updateSelections(selections);
1140
- this.focus({ preventScroll: true });
1141
- requestAnimationFrame(() => {
1142
- if (this.#primaryCaretElement !== void 0) this.#primaryCaretElement.scrollIntoView({
1143
- block: "nearest",
1144
- inline: "nearest"
1145
- });
1146
- else if (selections.length > 0) {
1147
- const pos = getCaretPosition(selections.at(-1));
1148
- this.#scrollToLine(pos.line, pos.character);
1149
- }
1322
+ if (this.#primaryCaretElement !== void 0) this.#primaryCaretElement.scrollIntoView({
1323
+ block: "nearest",
1324
+ inline: "nearest"
1150
1325
  });
1326
+ else if (selections.length > 0) {
1327
+ const pos = getCaretPosition(selections.at(-1));
1328
+ this.#scrollToLine(pos.line, pos.character);
1329
+ }
1330
+ this.focus({ preventScroll: true });
1151
1331
  }
1152
1332
  }
1153
1333
  #applyChangeToLineAnnotations(change) {
@@ -1160,28 +1340,40 @@ var Editor = class {
1160
1340
  }
1161
1341
  }
1162
1342
  #getLineElement(line) {
1343
+ const lastAccessed = this.#lastAccessedLineElement;
1344
+ if (lastAccessed !== void 0 && lastAccessed[0] === line) return lastAccessed[1];
1163
1345
  const contentElement = this.#contentElement;
1164
1346
  if (contentElement === void 0) return;
1165
- if (this.#renderRange !== void 0 && this.#editMode === "simple") {
1347
+ let lineElement = null;
1348
+ if (this.#renderRange !== void 0) {
1166
1349
  const { startingLine } = this.#renderRange;
1167
1350
  const { children } = contentElement;
1168
1351
  for (let i = line - startingLine; i <= children.length; i++) {
1169
1352
  const child = children[i];
1170
- const lineNumber = child?.dataset.line;
1171
- const lineType = child?.dataset.lineType;
1172
- if (lineNumber !== void 0 && lineType !== void 0 && isLineEditable(lineType) && Number(lineNumber) === 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
+ }
1173
1360
  }
1174
1361
  }
1175
- if (this.#editMode === "advanced") return contentElement.querySelector(`[data-line="${line + 1}"]:not([data-line-type="change-deletion"])`) ?? void 0;
1176
- 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
+ }
1177
1370
  }
1178
1371
  #getGutterWidth() {
1179
- const gutterElement = this.#contentElement?.previousElementSibling;
1180
- if (gutterElement == null || !(gutterElement instanceof HTMLElement) || !gutterElement.hasAttribute("data-gutter")) return 0;
1372
+ if (this.#gutterElement === void 0) return 0;
1181
1373
  if (this.#gutterWidthCache === void 0) {
1182
1374
  const diffsColumnNumberWidth = this.#contentElement?.parentElement?.style.getPropertyValue("--diffs-column-number-width");
1183
- if (diffsColumnNumberWidth !== void 0 && diffsColumnNumberWidth.length > 2 && diffsColumnNumberWidth.endsWith("px")) this.#gutterWidthCache = Number(diffsColumnNumberWidth.slice(0, -2));
1184
- else this.#gutterWidthCache = gutterElement.offsetWidth;
1375
+ if (diffsColumnNumberWidth !== void 0 && diffsColumnNumberWidth.length > 2 && diffsColumnNumberWidth.endsWith("px")) this.#gutterWidthCache = parseInt(diffsColumnNumberWidth.slice(0, -2), 10);
1376
+ else this.#gutterWidthCache = this.#gutterElement.offsetWidth;
1185
1377
  }
1186
1378
  return this.#gutterWidthCache;
1187
1379
  }
@@ -1189,7 +1381,7 @@ var Editor = class {
1189
1381
  if (this.#contentElement === void 0) return 0;
1190
1382
  if (this.#contentWidthCache === void 0) {
1191
1383
  const diffsColumnContentWidth = this.#contentElement.parentElement?.style.getPropertyValue("--diffs-column-content-width");
1192
- if (diffsColumnContentWidth !== void 0 && diffsColumnContentWidth.length > 2 && diffsColumnContentWidth.endsWith("px")) this.#contentWidthCache = Number(diffsColumnContentWidth.slice(0, -2));
1384
+ if (diffsColumnContentWidth !== void 0 && diffsColumnContentWidth.length > 2 && diffsColumnContentWidth.endsWith("px")) this.#contentWidthCache = parseFloat(diffsColumnContentWidth.slice(0, -2));
1193
1385
  else this.#contentWidthCache = this.#contentElement.offsetWidth;
1194
1386
  }
1195
1387
  return this.#contentWidthCache;
@@ -1204,7 +1396,7 @@ var Editor = class {
1204
1396
  return y;
1205
1397
  }
1206
1398
  #getCharX(line, char) {
1207
- 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]];
1208
1400
  const lineText = this.#textDocument?.getLineText(line);
1209
1401
  const offsetLeft = this.#getGutterWidth() + this.#metrics.ch;
1210
1402
  if (lineText === void 0 || lineText.length === 0 || char <= 0) return [offsetLeft, 0];
@@ -1232,12 +1424,12 @@ var Editor = class {
1232
1424
  }
1233
1425
  }
1234
1426
  }
1235
- if (this.#lastCharX !== void 0) {
1236
- this.#lastCharX[0] = line;
1237
- this.#lastCharX[1] = char;
1238
- this.#lastCharX[2] = left;
1239
- this.#lastCharX[3] = wrapLine;
1240
- } 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 = [
1241
1433
  line,
1242
1434
  char,
1243
1435
  left,