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

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