@pierre/diffs 1.3.0-beta.10 → 1.3.0-beta.11

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 (67) hide show
  1. package/dist/components/CodeView.d.ts.map +1 -1
  2. package/dist/components/CodeView.js +1 -16
  3. package/dist/components/CodeView.js.map +1 -1
  4. package/dist/components/File.d.ts +2 -1
  5. package/dist/components/File.d.ts.map +1 -1
  6. package/dist/components/File.js +6 -2
  7. package/dist/components/File.js.map +1 -1
  8. package/dist/components/FileDiff.d.ts +8 -2
  9. package/dist/components/FileDiff.d.ts.map +1 -1
  10. package/dist/components/FileDiff.js +26 -8
  11. package/dist/components/FileDiff.js.map +1 -1
  12. package/dist/components/VirtualizedFileDiff.d.ts +1 -0
  13. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  14. package/dist/components/VirtualizedFileDiff.js +4 -0
  15. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  16. package/dist/editor/editStack.d.ts +5 -0
  17. package/dist/editor/editStack.d.ts.map +1 -1
  18. package/dist/editor/editStack.js +53 -12
  19. package/dist/editor/editStack.js.map +1 -1
  20. package/dist/editor/editor.d.ts.map +1 -1
  21. package/dist/editor/editor.js +52 -32
  22. package/dist/editor/editor.js.map +1 -1
  23. package/dist/editor/editor2.js +1 -1
  24. package/dist/editor/marker.d.ts +3 -3
  25. package/dist/editor/marker.d.ts.map +1 -1
  26. package/dist/editor/marker.js +89 -89
  27. package/dist/editor/marker.js.map +1 -1
  28. package/dist/editor/pieceTable.d.ts.map +1 -1
  29. package/dist/editor/pieceTable.js +55 -20
  30. package/dist/editor/pieceTable.js.map +1 -1
  31. package/dist/editor/popover.js +4 -0
  32. package/dist/editor/popover.js.map +1 -1
  33. package/dist/editor/selection.d.ts +2 -2
  34. package/dist/editor/selection.d.ts.map +1 -1
  35. package/dist/editor/selection.js +2 -2
  36. package/dist/editor/selection.js.map +1 -1
  37. package/dist/editor/textDocument.js +1 -1
  38. package/dist/editor/textDocument.js.map +1 -1
  39. package/dist/editor/tokenizer.d.ts.map +1 -1
  40. package/dist/editor/tokenizer.js +7 -2
  41. package/dist/editor/tokenizer.js.map +1 -1
  42. package/dist/index.d.ts +2 -2
  43. package/dist/managers/InteractionManager.d.ts +18 -7
  44. package/dist/managers/InteractionManager.d.ts.map +1 -1
  45. package/dist/managers/InteractionManager.js +154 -99
  46. package/dist/managers/InteractionManager.js.map +1 -1
  47. package/dist/react/index.d.ts +2 -2
  48. package/dist/react/utils/useFileDiffInstance.js +1 -6
  49. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  50. package/dist/react/utils/useFileInstance.js +1 -4
  51. package/dist/react/utils/useFileInstance.js.map +1 -1
  52. package/dist/ssr/index.d.ts +2 -2
  53. package/dist/style.js +1 -1
  54. package/dist/types.d.ts +7 -1
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/utils/computeFileOffsets.d.ts.map +1 -1
  57. package/dist/utils/computeFileOffsets.js +34 -5
  58. package/dist/utils/computeFileOffsets.js.map +1 -1
  59. package/dist/utils/isGutterUtilityPath.d.ts +5 -0
  60. package/dist/utils/isGutterUtilityPath.d.ts.map +1 -0
  61. package/dist/utils/isGutterUtilityPath.js +12 -0
  62. package/dist/utils/isGutterUtilityPath.js.map +1 -0
  63. package/dist/worker/worker-portable.js +34 -5
  64. package/dist/worker/worker-portable.js.map +1 -1
  65. package/dist/worker/worker.js +34 -5
  66. package/dist/worker/worker.js.map +1 -1
  67. package/package.json +1 -1
@@ -1,11 +1,15 @@
1
1
  //#region src/editor/editStack.ts
2
2
  /** Largest number of undo or redo entries kept; oldest entries drop first once exceeded. */
3
3
  const DEFAULT_EDIT_STACK_MAX_ENTRIES = 100;
4
+ const COALESCING_MODE_INSERT = 1;
5
+ const COALESCING_MODE_BACKSPACE = 2;
6
+ const COALESCING_MODE_DELETE = 3;
4
7
  /** A stack of edit entries. */
5
8
  var EditStack = class {
6
9
  #undoStack = [];
7
10
  #redoStack = [];
8
11
  #maxEntries;
12
+ #canCoalesce = false;
9
13
  constructor(options) {
10
14
  this.#maxEntries = Math.max(1, options?.maxEntries ?? DEFAULT_EDIT_STACK_MAX_ENTRIES);
11
15
  }
@@ -19,6 +23,7 @@ var EditStack = class {
19
23
  clear() {
20
24
  this.#undoStack.length = 0;
21
25
  this.#redoStack.length = 0;
26
+ this.#canCoalesce = false;
22
27
  }
23
28
  /** Clears the redo stack. */
24
29
  clearRedo() {
@@ -28,6 +33,7 @@ var EditStack = class {
28
33
  push(entry) {
29
34
  this.#undoStack.push(entry);
30
35
  this.clearRedo();
36
+ this.#canCoalesce = true;
31
37
  if (this.#undoStack.length > this.#maxEntries) this.#undoStack.shift();
32
38
  }
33
39
  /** Sets the selections after the last undo entry. */
@@ -47,6 +53,10 @@ var EditStack = class {
47
53
  peekUndo() {
48
54
  return this.#undoStack[this.#undoStack.length - 1];
49
55
  }
56
+ /** Returns the last undo entry only while its coalescing group is open. */
57
+ peekUndoForCoalescing() {
58
+ return this.#canCoalesce ? this.peekUndo() : void 0;
59
+ }
50
60
  /** Replaces the last undo entry with the given entry. */
51
61
  replaceLastUndo(entry) {
52
62
  if (this.#undoStack.length === 0) {
@@ -55,12 +65,14 @@ var EditStack = class {
55
65
  }
56
66
  this.#undoStack[this.#undoStack.length - 1] = entry;
57
67
  this.clearRedo();
68
+ this.#canCoalesce = true;
58
69
  }
59
70
  /** Moves the latest undo entry to the redo stack and returns it, or `undefined` if empty. */
60
71
  popUndoToRedo() {
61
72
  const entry = this.#undoStack.pop();
62
73
  if (entry !== void 0) {
63
74
  this.#redoStack.push(entry);
75
+ this.#canCoalesce = false;
64
76
  return entry;
65
77
  }
66
78
  }
@@ -69,6 +81,7 @@ var EditStack = class {
69
81
  const entry = this.#redoStack.pop();
70
82
  if (entry !== void 0) {
71
83
  this.#undoStack.push(entry);
84
+ this.#canCoalesce = false;
72
85
  return entry;
73
86
  }
74
87
  }
@@ -76,6 +89,30 @@ var EditStack = class {
76
89
  function createEditStackEntry(textDocument, resolvedEdits, versionBefore, versionAfter, selectionsBefore, selectionsAfter, lineAnnotationsBefore, lineAnnotationsAfter) {
77
90
  const forwardEdits = [...resolvedEdits].sort((a, b) => a.start - b.start);
78
91
  const inverseEdits = [];
92
+ let coalescingMode;
93
+ if (selectionsBefore?.length === forwardEdits.length) {
94
+ const caretOffsets = [];
95
+ for (const selection of selectionsBefore) {
96
+ if (selection.start.line !== selection.end.line || selection.start.character !== selection.end.character) break;
97
+ caretOffsets.push(textDocument.offsetAt(selection.start));
98
+ }
99
+ if (caretOffsets.length === forwardEdits.length) {
100
+ caretOffsets.sort((a, b) => a - b);
101
+ let isBackspace = true;
102
+ let isDelete = true;
103
+ for (let i = 0; i < forwardEdits.length; i++) {
104
+ const edit = forwardEdits[i];
105
+ if (edit.text.length > 0 || edit.start === edit.end) {
106
+ isBackspace = false;
107
+ isDelete = false;
108
+ break;
109
+ }
110
+ isBackspace &&= caretOffsets[i] === edit.end;
111
+ isDelete &&= caretOffsets[i] === edit.start;
112
+ }
113
+ coalescingMode = isBackspace ? COALESCING_MODE_BACKSPACE : isDelete ? COALESCING_MODE_DELETE : void 0;
114
+ }
115
+ }
79
116
  for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {
80
117
  const edit = forwardEdits[i];
81
118
  const replacedText = textDocument.getTextSlice(edit.start, edit.end);
@@ -95,7 +132,8 @@ function createEditStackEntry(textDocument, resolvedEdits, versionBefore, versio
95
132
  selectionsBefore: selectionsBefore?.map((selection) => ({ ...selection })),
96
133
  selectionsAfter: selectionsAfter?.map((selection) => ({ ...selection })),
97
134
  lineAnnotationsBefore: lineAnnotationsBefore?.slice(),
98
- lineAnnotationsAfter: lineAnnotationsAfter?.slice()
135
+ lineAnnotationsAfter: lineAnnotationsAfter?.slice(),
136
+ coalescingMode
99
137
  };
100
138
  }
101
139
  /** Determines if the change matches following modes:
@@ -116,21 +154,19 @@ function shouldCoalesceEditStackEntry(previousEntry, nextEntry) {
116
154
  const nextIsInsert = nextForward.start === nextForward.end && nextForward.text.length > 0 && !nextForward.text.includes("\n") && nextInverse.text.length === 0;
117
155
  if (previousWasInsert && nextIsInsert) {
118
156
  if (nextForward.start !== previousInverse.end) return false;
119
- mode ??= "insert";
120
- if (mode !== "insert") return false;
157
+ mode ??= COALESCING_MODE_INSERT;
158
+ if (mode !== COALESCING_MODE_INSERT || previousEntry.coalescingMode !== void 0 && previousEntry.coalescingMode !== COALESCING_MODE_INSERT || nextEntry.coalescingMode !== void 0 && nextEntry.coalescingMode !== COALESCING_MODE_INSERT) return false;
121
159
  continue;
122
160
  }
123
161
  const previousWasDelete = previousForward.text.length === 0 && previousForward.end > previousForward.start && previousInverse.text.length > 0;
124
162
  const nextIsDelete = nextForward.text.length === 0 && nextForward.end > nextForward.start && nextInverse.text.length > 0;
125
163
  if (previousWasDelete && nextIsDelete) {
126
- if (mappedNextStart === previousForward.end) {
127
- mode ??= "delete";
128
- if (mode !== "delete") return false;
129
- continue;
130
- }
131
- if (mappedNextStart + (nextForward.end - nextForward.start) !== previousForward.start) return false;
132
- mode ??= "backspace";
133
- if (mode !== "backspace") return false;
164
+ let nextMode;
165
+ if (mappedNextStart === previousForward.end) nextMode = COALESCING_MODE_DELETE;
166
+ else if (mappedNextStart + (nextForward.end - nextForward.start) !== previousForward.start) return false;
167
+ else nextMode = COALESCING_MODE_BACKSPACE;
168
+ mode ??= nextMode;
169
+ if (mode !== nextMode || previousEntry.coalescingMode !== void 0 && previousEntry.coalescingMode !== nextMode || nextEntry.coalescingMode !== void 0 && nextEntry.coalescingMode !== nextMode) return false;
134
170
  continue;
135
171
  }
136
172
  return false;
@@ -141,6 +177,7 @@ function shouldCoalesceEditStackEntry(previousEntry, nextEntry) {
141
177
  function coalesceEditStackEntries(previousEntry, nextEntry) {
142
178
  const forwardEdits = [];
143
179
  const replacedTexts = [];
180
+ let coalescingMode;
144
181
  for (let i = 0; i < previousEntry.forwardEdits.length; i++) {
145
182
  const previousForward = previousEntry.forwardEdits[i];
146
183
  const previousInverse = previousEntry.inverseEdits[i];
@@ -148,6 +185,7 @@ function coalesceEditStackEntries(previousEntry, nextEntry) {
148
185
  const nextInverse = nextEntry.inverseEdits[i];
149
186
  const mappedNextStart = mapOffsetAfterForwardBatchToBefore(nextForward.start, previousEntry.forwardEdits);
150
187
  if (previousForward.text.length > 0) {
188
+ coalescingMode ??= COALESCING_MODE_INSERT;
151
189
  forwardEdits.push({
152
190
  start: previousForward.start,
153
191
  end: previousForward.end,
@@ -157,6 +195,7 @@ function coalesceEditStackEntries(previousEntry, nextEntry) {
157
195
  continue;
158
196
  }
159
197
  if (mappedNextStart === previousForward.end) {
198
+ coalescingMode ??= COALESCING_MODE_DELETE;
160
199
  forwardEdits.push({
161
200
  start: previousForward.start,
162
201
  end: mappedNextStart + (nextForward.end - nextForward.start),
@@ -165,6 +204,7 @@ function coalesceEditStackEntries(previousEntry, nextEntry) {
165
204
  replacedTexts.push(previousInverse.text + nextInverse.text);
166
205
  continue;
167
206
  }
207
+ coalescingMode ??= COALESCING_MODE_BACKSPACE;
168
208
  forwardEdits.push({
169
209
  start: Math.min(previousForward.start, mappedNextStart),
170
210
  end: previousForward.end,
@@ -180,7 +220,8 @@ function coalesceEditStackEntries(previousEntry, nextEntry) {
180
220
  selectionsBefore: previousEntry.selectionsBefore?.slice(),
181
221
  selectionsAfter: nextEntry.selectionsAfter?.slice(),
182
222
  lineAnnotationsBefore: previousEntry.lineAnnotationsBefore?.slice(),
183
- lineAnnotationsAfter: nextEntry.lineAnnotationsAfter?.slice()
223
+ lineAnnotationsAfter: nextEntry.lineAnnotationsAfter?.slice(),
224
+ coalescingMode
184
225
  };
185
226
  }
186
227
  function buildInverseEditsFromReplacedTexts(forwardEdits, replacedTexts) {
@@ -1 +1 @@
1
- {"version":3,"file":"editStack.js","names":["#maxEntries","#undoStack","#redoStack"],"sources":["../../src/editor/editStack.ts"],"sourcesContent":["import type { DiffLineAnnotation, EditorSelection } from '../types';\nimport type { ResolvedTextEdit, TextDocument } from './textDocument';\n\n/** Largest number of undo or redo entries kept; oldest entries drop first once exceeded. */\nconst DEFAULT_EDIT_STACK_MAX_ENTRIES = 100;\n\n/** An entry in the edit stack. */\nexport interface EditStackEntry<LAnnotation> {\n /** Forward offset edits from the entry's base text to its final text. */\n forwardEdits: ResolvedTextEdit[];\n /** Inverse offset edits from the entry's final text back to its base text. */\n inverseEdits: ResolvedTextEdit[];\n /** Document version before the entry is applied. */\n versionBefore: number;\n /** Document version after the entry is applied. */\n versionAfter: number;\n /** Selection before the transaction. */\n selectionsBefore?: EditorSelection[];\n /** Selection after the transaction. */\n selectionsAfter?: EditorSelection[];\n /** Line annotations before the transaction. */\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[];\n /** Line annotations after the transaction. */\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[];\n /**\n * When `true`, this entry is its own undo step and never merges with the\n * entry before or after it. Set for paste and cut, which otherwise look like\n * typing and would merge into the previous keystroke.\n */\n undoBoundary?: boolean;\n}\n\n/** Options for the edit stack. */\nexport interface EditStackOptions {\n /** The maximum number of entries to keep in the undo stack. */\n maxEntries?: number;\n}\n\n/** A stack of edit entries. */\nexport class EditStack<LAnnotation> {\n #undoStack: EditStackEntry<LAnnotation>[] = [];\n #redoStack: EditStackEntry<LAnnotation>[] = [];\n #maxEntries: number;\n\n constructor(options?: EditStackOptions) {\n this.#maxEntries = Math.max(\n 1,\n options?.maxEntries ?? DEFAULT_EDIT_STACK_MAX_ENTRIES\n );\n }\n\n get canUndo(): boolean {\n return this.#undoStack.length > 0;\n }\n\n get canRedo(): boolean {\n return this.#redoStack.length > 0;\n }\n\n /** Clears both the undo and redo stacks. */\n clear(): void {\n this.#undoStack.length = 0;\n this.#redoStack.length = 0;\n }\n\n /** Clears the redo stack. */\n clearRedo(): void {\n this.#redoStack.length = 0;\n }\n\n /** Pushes a new entry onto the undo stack. */\n push(entry: EditStackEntry<LAnnotation>): void {\n this.#undoStack.push(entry);\n this.clearRedo();\n if (this.#undoStack.length > this.#maxEntries) {\n this.#undoStack.shift();\n }\n }\n\n /** Sets the selections after the last undo entry. */\n setLastUndoSelectionsAfter(selections: EditorSelection[]): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.selectionsAfter = selections.map((selection) => ({\n ...selection,\n }));\n }\n }\n\n /** Sets the line annotations after the last undo entry. */\n setLastUndoLineAnnotations(\n lineAnnotationsBefore: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter: DiffLineAnnotation<LAnnotation>[]\n ): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.lineAnnotationsBefore = lineAnnotationsBefore.slice();\n lastEntry.lineAnnotationsAfter = lineAnnotationsAfter.slice();\n }\n }\n\n /** Returns the last undo entry, or `undefined` if empty. */\n peekUndo(): EditStackEntry<LAnnotation> | undefined {\n return this.#undoStack[this.#undoStack.length - 1];\n }\n\n /** Replaces the last undo entry with the given entry. */\n replaceLastUndo(entry: EditStackEntry<LAnnotation>): void {\n if (this.#undoStack.length === 0) {\n this.push(entry);\n return;\n }\n this.#undoStack[this.#undoStack.length - 1] = entry;\n this.clearRedo();\n }\n\n /** Moves the latest undo entry to the redo stack and returns it, or `undefined` if empty. */\n popUndoToRedo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#undoStack.pop();\n if (entry !== undefined) {\n this.#redoStack.push(entry);\n return entry;\n }\n }\n\n /** Moves the latest redo entry back to the undo stack and returns it, or `undefined` if empty. */\n popRedoToUndo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#redoStack.pop();\n if (entry !== undefined) {\n this.#undoStack.push(entry);\n return entry;\n }\n }\n}\n\nexport function createEditStackEntry<LAnnotation>(\n textDocument: TextDocument<LAnnotation>,\n resolvedEdits: ResolvedTextEdit[],\n versionBefore: number,\n versionAfter: number,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[]\n): EditStackEntry<LAnnotation> {\n const forwardEdits = [...resolvedEdits].sort((a, b) => a.start - b.start);\n const inverseEdits: ResolvedTextEdit[] = [];\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const replacedText = textDocument.getTextSlice(edit.start, edit.end);\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedText,\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return {\n forwardEdits: forwardEdits.map((edit) => ({ ...edit })),\n inverseEdits: inverseEdits,\n versionBefore,\n versionAfter,\n selectionsBefore: selectionsBefore?.map((selection) => ({\n ...selection,\n })),\n selectionsAfter: selectionsAfter?.map((selection) => ({ ...selection })),\n lineAnnotationsBefore: lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: lineAnnotationsAfter?.slice(),\n };\n}\n\n/** Determines if the change matches following modes:\n * - 'insert': simple typing\n * - 'backspace': backward delete\n * - 'delete': forward delete\n */\nexport function shouldCoalesceEditStackEntry<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation> | undefined,\n nextEntry: EditStackEntry<LAnnotation>\n): boolean {\n if (\n previousEntry === undefined ||\n previousEntry.undoBoundary === true ||\n nextEntry.undoBoundary === true ||\n previousEntry.forwardEdits.length === 0 ||\n previousEntry.forwardEdits.length !== previousEntry.inverseEdits.length ||\n previousEntry.forwardEdits.length !== nextEntry.forwardEdits.length ||\n nextEntry.forwardEdits.length !== nextEntry.inverseEdits.length\n ) {\n return false;\n }\n let mode: 'insert' | 'backspace' | 'delete' | undefined;\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n const previousWasInsert =\n previousForward.start <= previousForward.end &&\n previousForward.text.length > 0 &&\n !previousForward.text.includes('\\n') &&\n !previousInverse.text.includes('\\n');\n const nextIsInsert =\n nextForward.start === nextForward.end &&\n nextForward.text.length > 0 &&\n !nextForward.text.includes('\\n') &&\n nextInverse.text.length === 0;\n if (previousWasInsert && nextIsInsert) {\n // Merge only when the next insert starts exactly where the previous\n // inserted text ends, in after-edit offsets. `previousInverse.end` marks\n // that point, since undo replaces exactly the inserted range. A\n // base-offset check can't tell this apart from a caret that jumped to the\n // left edge: both edges of a pure insert map back to the same base offset,\n // so typing \"a\" then \"b\" at the left edge would merge as \"ab\" while the\n // buffer holds \"ba\", corrupting redo.\n if (nextForward.start !== previousInverse.end) {\n return false;\n }\n mode ??= 'insert';\n if (mode !== 'insert') {\n return false;\n }\n continue;\n }\n const previousWasDelete =\n previousForward.text.length === 0 &&\n previousForward.end > previousForward.start &&\n previousInverse.text.length > 0;\n const nextIsDelete =\n nextForward.text.length === 0 &&\n nextForward.end > nextForward.start &&\n nextInverse.text.length > 0;\n if (previousWasDelete && nextIsDelete) {\n if (mappedNextStart === previousForward.end) {\n mode ??= 'delete';\n if (mode !== 'delete') {\n return false;\n }\n continue;\n }\n if (\n mappedNextStart + (nextForward.end - nextForward.start) !==\n previousForward.start\n ) {\n return false;\n }\n mode ??= 'backspace';\n if (mode !== 'backspace') {\n return false;\n }\n continue;\n }\n return false;\n }\n return mode !== undefined;\n}\n\n/** Coalesce edit stack entries for simple typing and single-character deletes. */\nexport function coalesceEditStackEntries<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation>,\n nextEntry: EditStackEntry<LAnnotation>\n): EditStackEntry<LAnnotation> {\n const forwardEdits: ResolvedTextEdit[] = [];\n const replacedTexts: string[] = [];\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n\n if (previousForward.text.length > 0) {\n forwardEdits.push({\n start: previousForward.start,\n end: previousForward.end,\n text: previousForward.text + nextForward.text,\n });\n replacedTexts.push(previousInverse.text);\n continue;\n }\n\n if (mappedNextStart === previousForward.end) {\n forwardEdits.push({\n start: previousForward.start,\n end: mappedNextStart + (nextForward.end - nextForward.start),\n text: '',\n });\n replacedTexts.push(previousInverse.text + nextInverse.text);\n continue;\n }\n\n forwardEdits.push({\n start: Math.min(previousForward.start, mappedNextStart),\n end: previousForward.end,\n text: '',\n });\n replacedTexts.push(nextInverse.text + previousInverse.text);\n }\n\n return {\n forwardEdits,\n inverseEdits: buildInverseEditsFromReplacedTexts(\n forwardEdits,\n replacedTexts\n ),\n versionBefore: previousEntry.versionBefore,\n versionAfter: nextEntry.versionAfter,\n selectionsBefore: previousEntry.selectionsBefore?.slice(),\n selectionsAfter: nextEntry.selectionsAfter?.slice(),\n lineAnnotationsBefore: previousEntry.lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: nextEntry.lineAnnotationsAfter?.slice(),\n };\n}\n\nfunction buildInverseEditsFromReplacedTexts(\n forwardEdits: readonly ResolvedTextEdit[],\n replacedTexts: readonly string[]\n): ResolvedTextEdit[] {\n const inverseEdits: ResolvedTextEdit[] = [];\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedTexts[i],\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return inverseEdits;\n}\n\nfunction mapOffsetAfterForwardBatchToBefore(\n offsetAfter: number,\n forwardEdits: readonly ResolvedTextEdit[]\n): number {\n let offset = offsetAfter;\n for (const edit of forwardEdits) {\n const oldLength = edit.end - edit.start;\n const newLength = edit.text.length;\n const delta = newLength - oldLength;\n if (offset < edit.start) {\n continue;\n }\n if (offset >= edit.start + newLength) {\n offset -= delta;\n continue;\n }\n offset = edit.start + Math.min(offset - edit.start, oldLength);\n }\n return offset;\n}\n"],"mappings":";;AAIA,MAAM,iCAAiC;;AAmCvC,IAAa,YAAb,MAAoC;CAClC,aAA4C,CAAC;CAC7C,aAA4C,CAAC;CAC7C;CAEA,YAAY,SAA4B;EACtC,KAAKA,cAAc,KAAK,IACtB,GACA,SAAS,cAAc,8BACzB;CACF;CAEA,IAAI,UAAmB;EACrB,OAAO,KAAKC,WAAW,SAAS;CAClC;CAEA,IAAI,UAAmB;EACrB,OAAO,KAAKC,WAAW,SAAS;CAClC;;CAGA,QAAc;EACZ,KAAKD,WAAW,SAAS;EACzB,KAAKC,WAAW,SAAS;CAC3B;;CAGA,YAAkB;EAChB,KAAKA,WAAW,SAAS;CAC3B;;CAGA,KAAK,OAA0C;EAC7C,KAAKD,WAAW,KAAK,KAAK;EAC1B,KAAK,UAAU;EACf,IAAI,KAAKA,WAAW,SAAS,KAAKD,aAChC,KAAKC,WAAW,MAAM;CAE1B;;CAGA,2BAA2B,YAAqC;EAC9D,MAAM,YAAY,KAAKA,WAAW,KAAKA,WAAW,SAAS;EAC3D,IAAI,cAAc,KAAA,GAChB,UAAU,kBAAkB,WAAW,KAAK,eAAe,EACzD,GAAG,UACL,EAAE;CAEN;;CAGA,2BACE,uBACA,sBACM;EACN,MAAM,YAAY,KAAKA,WAAW,KAAKA,WAAW,SAAS;EAC3D,IAAI,cAAc,KAAA,GAAW;GAC3B,UAAU,wBAAwB,sBAAsB,MAAM;GAC9D,UAAU,uBAAuB,qBAAqB,MAAM;EAC9D;CACF;;CAGA,WAAoD;EAClD,OAAO,KAAKA,WAAW,KAAKA,WAAW,SAAS;CAClD;;CAGA,gBAAgB,OAA0C;EACxD,IAAI,KAAKA,WAAW,WAAW,GAAG;GAChC,KAAK,KAAK,KAAK;GACf;EACF;EACA,KAAKA,WAAW,KAAKA,WAAW,SAAS,KAAK;EAC9C,KAAK,UAAU;CACjB;;CAGA,gBAAoD;EAClD,MAAM,QAAQ,KAAKA,WAAW,IAAI;EAClC,IAAI,UAAU,KAAA,GAAW;GACvB,KAAKC,WAAW,KAAK,KAAK;GAC1B,OAAO;EACT;CACF;;CAGA,gBAAoD;EAClD,MAAM,QAAQ,KAAKA,WAAW,IAAI;EAClC,IAAI,UAAU,KAAA,GAAW;GACvB,KAAKD,WAAW,KAAK,KAAK;GAC1B,OAAO;EACT;CACF;AACF;AAEA,SAAgB,qBACd,cACA,eACA,eACA,cACA,kBACA,iBACA,uBACA,sBAC6B;CAC7B,MAAM,eAAe,CAAC,GAAG,aAAa,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CACxE,MAAM,eAAmC,CAAC;CAC1C,KAAK,IAAI,IAAI,GAAG,cAAc,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC7D,MAAM,OAAO,aAAa;EAC1B,MAAM,eAAe,aAAa,aAAa,KAAK,OAAO,KAAK,GAAG;EACnE,MAAM,iBAAiB,KAAK,QAAQ;EACpC,aAAa,KAAK;GAChB,OAAO;GACP,KAAK,iBAAiB,KAAK,KAAK;GAChC,MAAM;EACR,CAAC;EACD,eAAe,KAAK,KAAK,UAAU,KAAK,MAAM,KAAK;CACrD;CACA,OAAO;EACL,cAAc,aAAa,KAAK,UAAU,EAAE,GAAG,KAAK,EAAE;EACxC;EACd;EACA;EACA,kBAAkB,kBAAkB,KAAK,eAAe,EACtD,GAAG,UACL,EAAE;EACF,iBAAiB,iBAAiB,KAAK,eAAe,EAAE,GAAG,UAAU,EAAE;EACvE,uBAAuB,uBAAuB,MAAM;EACpD,sBAAsB,sBAAsB,MAAM;CACpD;AACF;;;;;;AAOA,SAAgB,6BACd,eACA,WACS;CACT,IACE,kBAAkB,KAAA,KAClB,cAAc,iBAAiB,QAC/B,UAAU,iBAAiB,QAC3B,cAAc,aAAa,WAAW,KACtC,cAAc,aAAa,WAAW,cAAc,aAAa,UACjE,cAAc,aAAa,WAAW,UAAU,aAAa,UAC7D,UAAU,aAAa,WAAW,UAAU,aAAa,QAEzD,OAAO;CAET,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,aAAa,QAAQ,KAAK;EAC1D,MAAM,kBAAkB,cAAc,aAAa;EACnD,MAAM,kBAAkB,cAAc,aAAa;EACnD,MAAM,cAAc,UAAU,aAAa;EAC3C,MAAM,cAAc,UAAU,aAAa;EAC3C,MAAM,kBAAkB,mCACtB,YAAY,OACZ,cAAc,YAChB;EACA,MAAM,oBACJ,gBAAgB,SAAS,gBAAgB,OACzC,gBAAgB,KAAK,SAAS,KAC9B,CAAC,gBAAgB,KAAK,SAAS,IAAI,KACnC,CAAC,gBAAgB,KAAK,SAAS,IAAI;EACrC,MAAM,eACJ,YAAY,UAAU,YAAY,OAClC,YAAY,KAAK,SAAS,KAC1B,CAAC,YAAY,KAAK,SAAS,IAAI,KAC/B,YAAY,KAAK,WAAW;EAC9B,IAAI,qBAAqB,cAAc;GAQrC,IAAI,YAAY,UAAU,gBAAgB,KACxC,OAAO;GAET,SAAS;GACT,IAAI,SAAS,UACX,OAAO;GAET;EACF;EACA,MAAM,oBACJ,gBAAgB,KAAK,WAAW,KAChC,gBAAgB,MAAM,gBAAgB,SACtC,gBAAgB,KAAK,SAAS;EAChC,MAAM,eACJ,YAAY,KAAK,WAAW,KAC5B,YAAY,MAAM,YAAY,SAC9B,YAAY,KAAK,SAAS;EAC5B,IAAI,qBAAqB,cAAc;GACrC,IAAI,oBAAoB,gBAAgB,KAAK;IAC3C,SAAS;IACT,IAAI,SAAS,UACX,OAAO;IAET;GACF;GACA,IACE,mBAAmB,YAAY,MAAM,YAAY,WACjD,gBAAgB,OAEhB,OAAO;GAET,SAAS;GACT,IAAI,SAAS,aACX,OAAO;GAET;EACF;EACA,OAAO;CACT;CACA,OAAO,SAAS,KAAA;AAClB;;AAGA,SAAgB,yBACd,eACA,WAC6B;CAC7B,MAAM,eAAmC,CAAC;CAC1C,MAAM,gBAA0B,CAAC;CACjC,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,aAAa,QAAQ,KAAK;EAC1D,MAAM,kBAAkB,cAAc,aAAa;EACnD,MAAM,kBAAkB,cAAc,aAAa;EACnD,MAAM,cAAc,UAAU,aAAa;EAC3C,MAAM,cAAc,UAAU,aAAa;EAC3C,MAAM,kBAAkB,mCACtB,YAAY,OACZ,cAAc,YAChB;EAEA,IAAI,gBAAgB,KAAK,SAAS,GAAG;GACnC,aAAa,KAAK;IAChB,OAAO,gBAAgB;IACvB,KAAK,gBAAgB;IACrB,MAAM,gBAAgB,OAAO,YAAY;GAC3C,CAAC;GACD,cAAc,KAAK,gBAAgB,IAAI;GACvC;EACF;EAEA,IAAI,oBAAoB,gBAAgB,KAAK;GAC3C,aAAa,KAAK;IAChB,OAAO,gBAAgB;IACvB,KAAK,mBAAmB,YAAY,MAAM,YAAY;IACtD,MAAM;GACR,CAAC;GACD,cAAc,KAAK,gBAAgB,OAAO,YAAY,IAAI;GAC1D;EACF;EAEA,aAAa,KAAK;GAChB,OAAO,KAAK,IAAI,gBAAgB,OAAO,eAAe;GACtD,KAAK,gBAAgB;GACrB,MAAM;EACR,CAAC;EACD,cAAc,KAAK,YAAY,OAAO,gBAAgB,IAAI;CAC5D;CAEA,OAAO;EACL;EACA,cAAc,mCACZ,cACA,aACF;EACA,eAAe,cAAc;EAC7B,cAAc,UAAU;EACxB,kBAAkB,cAAc,kBAAkB,MAAM;EACxD,iBAAiB,UAAU,iBAAiB,MAAM;EAClD,uBAAuB,cAAc,uBAAuB,MAAM;EAClE,sBAAsB,UAAU,sBAAsB,MAAM;CAC9D;AACF;AAEA,SAAS,mCACP,cACA,eACoB;CACpB,MAAM,eAAmC,CAAC;CAC1C,KAAK,IAAI,IAAI,GAAG,cAAc,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC7D,MAAM,OAAO,aAAa;EAC1B,MAAM,iBAAiB,KAAK,QAAQ;EACpC,aAAa,KAAK;GAChB,OAAO;GACP,KAAK,iBAAiB,KAAK,KAAK;GAChC,MAAM,cAAc;EACtB,CAAC;EACD,eAAe,KAAK,KAAK,UAAU,KAAK,MAAM,KAAK;CACrD;CACA,OAAO;AACT;AAEA,SAAS,mCACP,aACA,cACQ;CACR,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,YAAY,KAAK,MAAM,KAAK;EAClC,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,QAAQ,YAAY;EAC1B,IAAI,SAAS,KAAK,OAChB;EAEF,IAAI,UAAU,KAAK,QAAQ,WAAW;GACpC,UAAU;GACV;EACF;EACA,SAAS,KAAK,QAAQ,KAAK,IAAI,SAAS,KAAK,OAAO,SAAS;CAC/D;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"editStack.js","names":["#maxEntries","#undoStack","#redoStack","#canCoalesce"],"sources":["../../src/editor/editStack.ts"],"sourcesContent":["import type { DiffLineAnnotation, EditorSelection } from '../types';\nimport type { ResolvedTextEdit, TextDocument } from './textDocument';\n\n/** Largest number of undo or redo entries kept; oldest entries drop first once exceeded. */\nconst DEFAULT_EDIT_STACK_MAX_ENTRIES = 100;\n\nconst COALESCING_MODE_INSERT = 1;\nconst COALESCING_MODE_BACKSPACE = 2;\nconst COALESCING_MODE_DELETE = 3;\ntype EditStackCoalescingMode = 1 | 2 | 3;\n\n/** An entry in the edit stack. */\nexport interface EditStackEntry<LAnnotation> {\n /** Forward offset edits from the entry's base text to its final text. */\n forwardEdits: ResolvedTextEdit[];\n /** Inverse offset edits from the entry's final text back to its base text. */\n inverseEdits: ResolvedTextEdit[];\n /** Document version before the entry is applied. */\n versionBefore: number;\n /** Document version after the entry is applied. */\n versionAfter: number;\n /** Selection before the transaction. */\n selectionsBefore?: EditorSelection[];\n /** Selection after the transaction. */\n selectionsAfter?: EditorSelection[];\n /** Line annotations before the transaction. */\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[];\n /** Line annotations after the transaction. */\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[];\n /** Input mode inferred from the edit or retained by a coalesced run. */\n coalescingMode?: EditStackCoalescingMode;\n /**\n * When `true`, this entry is its own undo step and never merges with the\n * entry before or after it. Set for paste and cut, which otherwise look like\n * typing and would merge into the previous keystroke.\n */\n undoBoundary?: boolean;\n}\n\n/** Options for the edit stack. */\nexport interface EditStackOptions {\n /** The maximum number of entries to keep in the undo stack. */\n maxEntries?: number;\n}\n\n/** A stack of edit entries. */\nexport class EditStack<LAnnotation> {\n #undoStack: EditStackEntry<LAnnotation>[] = [];\n #redoStack: EditStackEntry<LAnnotation>[] = [];\n #maxEntries: number;\n #canCoalesce = false;\n\n constructor(options?: EditStackOptions) {\n this.#maxEntries = Math.max(\n 1,\n options?.maxEntries ?? DEFAULT_EDIT_STACK_MAX_ENTRIES\n );\n }\n\n get canUndo(): boolean {\n return this.#undoStack.length > 0;\n }\n\n get canRedo(): boolean {\n return this.#redoStack.length > 0;\n }\n\n /** Clears both the undo and redo stacks. */\n clear(): void {\n this.#undoStack.length = 0;\n this.#redoStack.length = 0;\n this.#canCoalesce = false;\n }\n\n /** Clears the redo stack. */\n clearRedo(): void {\n this.#redoStack.length = 0;\n }\n\n /** Pushes a new entry onto the undo stack. */\n push(entry: EditStackEntry<LAnnotation>): void {\n this.#undoStack.push(entry);\n this.clearRedo();\n this.#canCoalesce = true;\n if (this.#undoStack.length > this.#maxEntries) {\n this.#undoStack.shift();\n }\n }\n\n /** Sets the selections after the last undo entry. */\n setLastUndoSelectionsAfter(selections: EditorSelection[]): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.selectionsAfter = selections.map((selection) => ({\n ...selection,\n }));\n }\n }\n\n /** Sets the line annotations after the last undo entry. */\n setLastUndoLineAnnotations(\n lineAnnotationsBefore: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter: DiffLineAnnotation<LAnnotation>[]\n ): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.lineAnnotationsBefore = lineAnnotationsBefore.slice();\n lastEntry.lineAnnotationsAfter = lineAnnotationsAfter.slice();\n }\n }\n\n /** Returns the last undo entry, or `undefined` if empty. */\n peekUndo(): EditStackEntry<LAnnotation> | undefined {\n return this.#undoStack[this.#undoStack.length - 1];\n }\n\n /** Returns the last undo entry only while its coalescing group is open. */\n peekUndoForCoalescing(): EditStackEntry<LAnnotation> | undefined {\n return this.#canCoalesce ? this.peekUndo() : undefined;\n }\n\n /** Replaces the last undo entry with the given entry. */\n replaceLastUndo(entry: EditStackEntry<LAnnotation>): void {\n if (this.#undoStack.length === 0) {\n this.push(entry);\n return;\n }\n this.#undoStack[this.#undoStack.length - 1] = entry;\n this.clearRedo();\n this.#canCoalesce = true;\n }\n\n /** Moves the latest undo entry to the redo stack and returns it, or `undefined` if empty. */\n popUndoToRedo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#undoStack.pop();\n if (entry !== undefined) {\n this.#redoStack.push(entry);\n this.#canCoalesce = false;\n return entry;\n }\n }\n\n /** Moves the latest redo entry back to the undo stack and returns it, or `undefined` if empty. */\n popRedoToUndo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#redoStack.pop();\n if (entry !== undefined) {\n this.#undoStack.push(entry);\n this.#canCoalesce = false;\n return entry;\n }\n }\n}\n\nexport function createEditStackEntry<LAnnotation>(\n textDocument: TextDocument<LAnnotation>,\n resolvedEdits: ResolvedTextEdit[],\n versionBefore: number,\n versionAfter: number,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[]\n): EditStackEntry<LAnnotation> {\n const forwardEdits = [...resolvedEdits].sort((a, b) => a.start - b.start);\n const inverseEdits: ResolvedTextEdit[] = [];\n let coalescingMode: EditStackCoalescingMode | undefined;\n if (selectionsBefore?.length === forwardEdits.length) {\n const caretOffsets: number[] = [];\n for (const selection of selectionsBefore) {\n if (\n selection.start.line !== selection.end.line ||\n selection.start.character !== selection.end.character\n ) {\n break;\n }\n caretOffsets.push(textDocument.offsetAt(selection.start));\n }\n if (caretOffsets.length === forwardEdits.length) {\n caretOffsets.sort((a, b) => a - b);\n let isBackspace = true;\n let isDelete = true;\n for (let i = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n if (edit.text.length > 0 || edit.start === edit.end) {\n isBackspace = false;\n isDelete = false;\n break;\n }\n isBackspace &&= caretOffsets[i] === edit.end;\n isDelete &&= caretOffsets[i] === edit.start;\n }\n coalescingMode = isBackspace\n ? COALESCING_MODE_BACKSPACE\n : isDelete\n ? COALESCING_MODE_DELETE\n : undefined;\n }\n }\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const replacedText = textDocument.getTextSlice(edit.start, edit.end);\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedText,\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return {\n forwardEdits: forwardEdits.map((edit) => ({ ...edit })),\n inverseEdits: inverseEdits,\n versionBefore,\n versionAfter,\n selectionsBefore: selectionsBefore?.map((selection) => ({\n ...selection,\n })),\n selectionsAfter: selectionsAfter?.map((selection) => ({ ...selection })),\n lineAnnotationsBefore: lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: lineAnnotationsAfter?.slice(),\n coalescingMode,\n };\n}\n\n/** Determines if the change matches following modes:\n * - 'insert': simple typing\n * - 'backspace': backward delete\n * - 'delete': forward delete\n */\nexport function shouldCoalesceEditStackEntry<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation> | undefined,\n nextEntry: EditStackEntry<LAnnotation>\n): boolean {\n if (\n previousEntry === undefined ||\n previousEntry.undoBoundary === true ||\n nextEntry.undoBoundary === true ||\n previousEntry.forwardEdits.length === 0 ||\n previousEntry.forwardEdits.length !== previousEntry.inverseEdits.length ||\n previousEntry.forwardEdits.length !== nextEntry.forwardEdits.length ||\n nextEntry.forwardEdits.length !== nextEntry.inverseEdits.length\n ) {\n return false;\n }\n let mode: EditStackCoalescingMode | undefined;\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n const previousWasInsert =\n previousForward.start <= previousForward.end &&\n previousForward.text.length > 0 &&\n !previousForward.text.includes('\\n') &&\n !previousInverse.text.includes('\\n');\n const nextIsInsert =\n nextForward.start === nextForward.end &&\n nextForward.text.length > 0 &&\n !nextForward.text.includes('\\n') &&\n nextInverse.text.length === 0;\n if (previousWasInsert && nextIsInsert) {\n // Merge only when the next insert starts exactly where the previous\n // inserted text ends, in after-edit offsets. `previousInverse.end` marks\n // that point, since undo replaces exactly the inserted range. A\n // base-offset check can't tell this apart from a caret that jumped to the\n // left edge: both edges of a pure insert map back to the same base offset,\n // so typing \"a\" then \"b\" at the left edge would merge as \"ab\" while the\n // buffer holds \"ba\", corrupting redo.\n if (nextForward.start !== previousInverse.end) {\n return false;\n }\n mode ??= COALESCING_MODE_INSERT;\n if (\n mode !== COALESCING_MODE_INSERT ||\n (previousEntry.coalescingMode !== undefined &&\n previousEntry.coalescingMode !== COALESCING_MODE_INSERT) ||\n (nextEntry.coalescingMode !== undefined &&\n nextEntry.coalescingMode !== COALESCING_MODE_INSERT)\n ) {\n return false;\n }\n continue;\n }\n const previousWasDelete =\n previousForward.text.length === 0 &&\n previousForward.end > previousForward.start &&\n previousInverse.text.length > 0;\n const nextIsDelete =\n nextForward.text.length === 0 &&\n nextForward.end > nextForward.start &&\n nextInverse.text.length > 0;\n if (previousWasDelete && nextIsDelete) {\n let nextMode: EditStackCoalescingMode;\n if (mappedNextStart === previousForward.end) {\n nextMode = COALESCING_MODE_DELETE;\n } else if (\n mappedNextStart + (nextForward.end - nextForward.start) !==\n previousForward.start\n ) {\n return false;\n } else {\n nextMode = COALESCING_MODE_BACKSPACE;\n }\n mode ??= nextMode;\n if (\n mode !== nextMode ||\n (previousEntry.coalescingMode !== undefined &&\n previousEntry.coalescingMode !== nextMode) ||\n (nextEntry.coalescingMode !== undefined &&\n nextEntry.coalescingMode !== nextMode)\n ) {\n return false;\n }\n continue;\n }\n return false;\n }\n return mode !== undefined;\n}\n\n/** Coalesce edit stack entries for simple typing and single-character deletes. */\nexport function coalesceEditStackEntries<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation>,\n nextEntry: EditStackEntry<LAnnotation>\n): EditStackEntry<LAnnotation> {\n const forwardEdits: ResolvedTextEdit[] = [];\n const replacedTexts: string[] = [];\n let coalescingMode: EditStackCoalescingMode | undefined;\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n\n if (previousForward.text.length > 0) {\n coalescingMode ??= COALESCING_MODE_INSERT;\n forwardEdits.push({\n start: previousForward.start,\n end: previousForward.end,\n text: previousForward.text + nextForward.text,\n });\n replacedTexts.push(previousInverse.text);\n continue;\n }\n\n if (mappedNextStart === previousForward.end) {\n coalescingMode ??= COALESCING_MODE_DELETE;\n forwardEdits.push({\n start: previousForward.start,\n end: mappedNextStart + (nextForward.end - nextForward.start),\n text: '',\n });\n replacedTexts.push(previousInverse.text + nextInverse.text);\n continue;\n }\n\n coalescingMode ??= COALESCING_MODE_BACKSPACE;\n forwardEdits.push({\n start: Math.min(previousForward.start, mappedNextStart),\n end: previousForward.end,\n text: '',\n });\n replacedTexts.push(nextInverse.text + previousInverse.text);\n }\n\n return {\n forwardEdits,\n inverseEdits: buildInverseEditsFromReplacedTexts(\n forwardEdits,\n replacedTexts\n ),\n versionBefore: previousEntry.versionBefore,\n versionAfter: nextEntry.versionAfter,\n selectionsBefore: previousEntry.selectionsBefore?.slice(),\n selectionsAfter: nextEntry.selectionsAfter?.slice(),\n lineAnnotationsBefore: previousEntry.lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: nextEntry.lineAnnotationsAfter?.slice(),\n coalescingMode,\n };\n}\n\nfunction buildInverseEditsFromReplacedTexts(\n forwardEdits: readonly ResolvedTextEdit[],\n replacedTexts: readonly string[]\n): ResolvedTextEdit[] {\n const inverseEdits: ResolvedTextEdit[] = [];\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedTexts[i],\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return inverseEdits;\n}\n\nfunction mapOffsetAfterForwardBatchToBefore(\n offsetAfter: number,\n forwardEdits: readonly ResolvedTextEdit[]\n): number {\n let offset = offsetAfter;\n for (const edit of forwardEdits) {\n const oldLength = edit.end - edit.start;\n const newLength = edit.text.length;\n const delta = newLength - oldLength;\n if (offset < edit.start) {\n continue;\n }\n if (offset >= edit.start + newLength) {\n offset -= delta;\n continue;\n }\n offset = edit.start + Math.min(offset - edit.start, oldLength);\n }\n return offset;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","names":[],"sources":["../../src/editor/editor.ts"],"mappings":";;;;;;;;UA+KiB,aAAA;;EAEf,iBAAA;EAF4B;;;;EAO5B,YAAA;EA4B+B;;;;EAvB/B,mBAAA,GAAsB,mBAAA;EA+BZ;EA7BV,gBAAA;EA8BgB;EA5BhB,aAAA;EAiCuC;;;;EA5BvC,YAAA,GAAe,YAAA;EAnBf;EAqBA,qBAAA,GAAwB,iBAAA;EAXxB;;;;EAgBA,sBAAA;EAPe;;;;;EAaf,SAAA;IACE,QAAA,GAAW,IAAA,cAAkB,OAAA;EAAA;EAG/B;EAAA,qBAAA,IACE,OAAA,EAAS,sBAAA,CAAuB,WAAA,MAC7B,WAAA;EAD6B;EAGlC,QAAA,IACE,MAAA,EAAQ,MAAA,CAAO,WAAA,GACf,YAAA,EAAc,sBAAA,CAAuB,WAAA;EAJlC;EAOL,QAAA,IACE,IAAA,EAAM,YAAA,EACN,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAN7B;EASV,OAAA;EATE;EAWF,MAAA;EAEA,OAAA;AAAA;AAAA,cAaW,MAAA,yBAA+B,WAAA,CAAY,WAAA;EAAA;EAoItD,WAAA,CAAY,OAAA,GAAS,aAAA,CAAc,WAAA;EAInC,UAAA,CAAW,OAAA,EAAS,aAAA,CAAc,WAAA;EAiClC,IAAA,CAAK,YAAA,EAAc,sBAAA,CAAuB,WAAA;EA7LxC;;;;;AAOK;AAaT;;;;EAsNE,UAAA,CAAW,KAAA,EAAO,QAAA,IAAY,aAAA;EAlFT;EAAA,IAqJjB,OAAA;EAjJgB;EAAA,IAsJhB,OAAA;EArHe;EA0HnB,IAAA;EASW;EAJX,IAAA;EAIA,OAAA,IAAW,YAAA;EAcX,OAAA;EAIA,QAAA,IAAY,WAAA;EAcZ,QAAA;IAAW,UAAA;IAAY;EAAA,GAAQ,WAAA;EAsB/B,aAAA,CACE,UAAA,GAAa,KAAA;IAAU,SAAA;EAAA;EAmCzB,UAAA,CAAW,OAAA,EAAS,MAAA;EA6BpB,KAAA,CAAM,OAAA,GAAU,YAAA;EAchB,IAAA;EAIA,OAAA,CAAQ,OAAA;EArb6C;EA+frD,aAAA,CAAc,IAAA,EAAM,YAAA,GAAe,YAAA;EA/fjB;EA2hBlB,+BAAA;EA3hBsD;EAsiBtD,gBAAA,EAAkB,WAAA,CAAY,WAAA;AAAA"}
1
+ {"version":3,"file":"editor.d.ts","names":[],"sources":["../../src/editor/editor.ts"],"mappings":";;;;;;;;UAiLiB,aAAA;;EAEf,iBAAA;EAF4B;;;;EAO5B,YAAA;EA4B+B;;;;EAvB/B,mBAAA,GAAsB,mBAAA;EA+BZ;EA7BV,gBAAA;EA8BgB;EA5BhB,aAAA;EAiCuC;;;;EA5BvC,YAAA,GAAe,YAAA;EAnBf;EAqBA,qBAAA,GAAwB,iBAAA;EAXxB;;;;EAgBA,sBAAA;EAPe;;;;;EAaf,SAAA;IACE,QAAA,GAAW,IAAA,cAAkB,OAAA;EAAA;EAG/B;EAAA,qBAAA,IACE,OAAA,EAAS,sBAAA,CAAuB,WAAA,MAC7B,WAAA;EAD6B;EAGlC,QAAA,IACE,MAAA,EAAQ,MAAA,CAAO,WAAA,GACf,YAAA,EAAc,sBAAA,CAAuB,WAAA;EAJlC;EAOL,QAAA,IACE,IAAA,EAAM,YAAA,EACN,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAN7B;EASV,OAAA;EATE;EAWF,MAAA;EAEA,OAAA;AAAA;AAAA,cAaW,MAAA,yBAA+B,WAAA,CAAY,WAAA;EAAA;EAqItD,WAAA,CAAY,OAAA,GAAS,aAAA,CAAc,WAAA;EAInC,UAAA,CAAW,OAAA,EAAS,aAAA,CAAc,WAAA;EAiClC,IAAA,CAAK,YAAA,EAAc,sBAAA,CAAuB,WAAA;EA9LxC;;;;;AAOK;AAaT;;;;EAwME,UAAA,CAAW,KAAA,EAAO,QAAA,IAAY,aAAA;EAnET;EAAA,IAsIjB,OAAA;EAlIgB;EAAA,IAuIhB,OAAA;EAtGe;EA2GnB,IAAA;EASW;EAJX,IAAA;EAIA,OAAA,IAAW,YAAA;EAcX,OAAA;EAIA,QAAA,IAAY,WAAA;EAcZ,QAAA;IAAW,UAAA;IAAY;EAAA,GAAQ,WAAA;EAsB/B,aAAA,CACE,UAAA,GAAa,KAAA;IAAU,SAAA;EAAA;EAmCzB,UAAA,CAAW,OAAA,EAAS,MAAA;EA6BpB,KAAA,CAAM,OAAA,GAAU,YAAA;EAchB,IAAA;EAIA,OAAA,CAAQ,OAAA;EAva6C;EAifrD,aAAA,CAAc,IAAA,EAAM,YAAA,GAAe,YAAA;EAjfjB;EA6gBlB,+BAAA;EA7gBsD;EAwhBtD,gBAAA,EAAkB,WAAA,CAAY,WAAA;AAAA"}
@@ -1,4 +1,5 @@
1
1
  import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
2
+ import { isGutterUtilityPath } from "../utils/isGutterUtilityPath.js";
2
3
  import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
3
4
  import { isMoveCursorShortcut, isPrimaryModifier, isSafari } from "./platform.js";
4
5
  import { resolveEditorCommandFromKeyboardEvent, resolveFindAgainShortcut } from "./command.js";
@@ -8,7 +9,7 @@ import { resolveBlockCommentEdits, resolveCommentConfig, resolveLineCommentEdits
8
9
  import { addEventListener, clampDomOffset, extend, getLineNumberAttr, h, round } from "./utils.js";
9
10
  import { applyDocumentChangeToLineAnnotations, renderLineAnnotations } from "./lineAnnotations.js";
10
11
  import { PopoverManager } from "./popover.js";
11
- import { applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNexMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionClipboardTexts, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapSelectionsAfterEdits, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines } from "./selection.js";
12
+ import { applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNextMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionClipboardTexts, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapSelectionsAfterEdits, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines } from "./selection.js";
12
13
  import { MarkerRenderer, markerSeverityDatasetKey } from "./marker.js";
13
14
  import { findBracketMatchRanges } from "./matchBrackets.js";
14
15
  import { createSpriteElement } from "./sprite.js";
@@ -146,14 +147,10 @@ var Editor = class {
146
147
  const file = fileInstance.__getCurrentFile?.();
147
148
  if (file !== void 0) requirePersistedCacheKey(file);
148
149
  }
149
- const { useTokenTransformer, enableGutterUtility, enableLineSelection, lineHoverHighlight = "disabled", ...rest } = fileInstance.options;
150
- if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || lineHoverHighlight !== "disabled") {
150
+ if (fileInstance.options.useTokenTransformer !== true) {
151
151
  fileInstance.setOptions({
152
- ...rest,
153
- useTokenTransformer: true,
154
- enableGutterUtility: false,
155
- enableLineSelection: false,
156
- lineHoverHighlight: "disabled"
152
+ ...fileInstance.options,
153
+ useTokenTransformer: true
157
154
  });
158
155
  fileInstance.rerender();
159
156
  }
@@ -485,7 +482,7 @@ var Editor = class {
485
482
  this.#renderRange = renderRange;
486
483
  this.#viewportWindowLines = renderRange?.totalLines;
487
484
  this.#tokenizer?.prebuildStateStack(renderRange);
488
- this.#markerRenderer?.removePopup();
485
+ this.#markerRenderer?.removePopover();
489
486
  if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
490
487
  if (this.#initSelections !== void 0 && this.#primaryCaretElement !== void 0) {
491
488
  this.#initSelections = void 0;
@@ -628,10 +625,10 @@ var Editor = class {
628
625
  }
629
626
  #getPopoverManager() {
630
627
  return this.#popoverManager ??= new PopoverManager({
631
- hasActivePopover: () => this.#selectionAction !== void 0 || this.#markerRenderer?.isPopupVisible() === true,
628
+ hasActivePopover: () => this.#selectionAction !== void 0 || this.#markerRenderer?.isPopoverVisible() === true,
632
629
  updateActivePopover: () => {
633
630
  this.#updateSelectionActionPopover();
634
- this.#markerRenderer?.updatePopupPosition();
631
+ this.#markerRenderer?.updatePopoverPosition();
635
632
  }
636
633
  });
637
634
  }
@@ -642,10 +639,11 @@ var Editor = class {
642
639
  this.#lastAccessedCharX = void 0;
643
640
  }
644
641
  #resetState() {
645
- this.#setSelectedLinesSafe(null);
642
+ this.#setEditorActiveLineSafe(null);
646
643
  this.#gutterWidthCache = void 0;
647
644
  this.#contentWidthCache = void 0;
648
645
  this.#shouldIgnoreSelectionChange = false;
646
+ this.#suppressNativeSelectionSync = false;
649
647
  this.#overlayElements?.forEach((el) => el.remove());
650
648
  this.#overlayElements = void 0;
651
649
  this.#selections = void 0;
@@ -728,6 +726,12 @@ var Editor = class {
728
726
  this.#selectEventDisposes?.forEach((dispose) => dispose());
729
727
  this.#selectEventDisposes = disposes;
730
728
  }
729
+ #isLineSelectionEnabled() {
730
+ return this.#fileInstance?.options.enableLineSelection === true;
731
+ }
732
+ #preserveEditorSelectionsForGutterGesture() {
733
+ this.#suppressNativeSelectionSync = this.#selections !== void 0;
734
+ }
731
735
  #listenContentElement(contentEl, gutterEl) {
732
736
  const { onFocus, onBlur } = this.#options;
733
737
  const targetIsContentElement = (e) => {
@@ -754,7 +758,7 @@ var Editor = class {
754
758
  return;
755
759
  }
756
760
  this.#setDeletedTextSelectionActive(false);
757
- this.#markerRenderer?.removePopup();
761
+ this.#markerRenderer?.removePopover();
758
762
  if (isSafari() && this.#lineAnnotations !== void 0 && this.#lineAnnotations.length > 0) {
759
763
  const annotationDisposes = [...contentEl.querySelectorAll("[data-line-annotation]")].map((el) => [addEventListener(el, "mouseenter", () => {
760
764
  this.#shouldIgnoreSelectionChange = true;
@@ -887,9 +891,20 @@ var Editor = class {
887
891
  ];
888
892
  const deletionsCode = this.#fileContainer?.shadowRoot?.querySelector("[data-deletions]");
889
893
  if (deletionsCode != null) this.#editorEventDisposes.push(addEventListener(deletionsCode, "pointerdown", (e) => {
890
- const target = e.composedPath()[0];
894
+ const path = e.composedPath();
895
+ if (isGutterUtilityPath(path)) {
896
+ this.#preserveEditorSelectionsForGutterGesture();
897
+ return;
898
+ }
899
+ const target = path[0];
891
900
  const gutterRow = target instanceof HTMLElement ? target.closest("[data-column-number]") : null;
892
- if (gutterRow instanceof HTMLElement && this.#beginDeletionGutterSelection(gutterRow, deletionsCode, e.pointerType === "mouse")) return;
901
+ if (gutterRow instanceof HTMLElement) {
902
+ if (this.#isLineSelectionEnabled()) {
903
+ this.#preserveEditorSelectionsForGutterGesture();
904
+ return;
905
+ }
906
+ if (this.#beginDeletionGutterSelection(gutterRow, deletionsCode, e.pointerType === "mouse")) return;
907
+ }
893
908
  this.#setDeletedTextSelectionActive(true);
894
909
  if (this.#selections !== void 0) this.#updateSelections([]);
895
910
  }, { passive: true }));
@@ -908,7 +923,16 @@ var Editor = class {
908
923
  return lineNumber - 1;
909
924
  };
910
925
  this.#editorEventDisposes.push(addEventListener(gutterEl, "pointerdown", (e) => {
911
- const gutterRow = resolveGutterTarget(e.composedPath()[0]);
926
+ const path = e.composedPath();
927
+ if (isGutterUtilityPath(path)) {
928
+ this.#preserveEditorSelectionsForGutterGesture();
929
+ return;
930
+ }
931
+ const gutterRow = resolveGutterTarget(path[0]);
932
+ if (gutterRow !== void 0 && this.#isLineSelectionEnabled()) {
933
+ this.#preserveEditorSelectionsForGutterGesture();
934
+ return;
935
+ }
912
936
  if (gutterRow?.dataset.lineType === "change-deletion") {
913
937
  const code = gutterRow.closest("[data-code]");
914
938
  if (code != null) this.#beginDeletionGutterSelection(gutterRow, code, e.pointerType === "mouse");
@@ -918,7 +942,7 @@ var Editor = class {
918
942
  const textDocument = this.#textDocument;
919
943
  const lineIndex = resolveEditableLine(gutterRow);
920
944
  if (lineIndex === void 0 || textDocument === void 0) return;
921
- this.#markerRenderer?.removePopup();
945
+ this.#markerRenderer?.removePopover();
922
946
  const selection = this.#spanLineSelection(lineIndex, lineIndex, textDocument);
923
947
  this.#isGutterMouseDown = true;
924
948
  this.#selectionStart = selection;
@@ -994,7 +1018,7 @@ var Editor = class {
994
1018
  this.#updateSelections(expanded);
995
1019
  this.focus();
996
1020
  } else {
997
- const nextMatch = findNexMatch(textDocument, selections);
1021
+ const nextMatch = findNextMatch(textDocument, selections);
998
1022
  if (nextMatch !== void 0) {
999
1023
  this.#updateSelections(nextMatch);
1000
1024
  const primaryMatch = nextMatch.at(-1);
@@ -1420,7 +1444,7 @@ var Editor = class {
1420
1444
  this.#updateSelections(this.#selections ?? []);
1421
1445
  if (this.#selections !== void 0) this.focus();
1422
1446
  }
1423
- this.#markerRenderer?.removePopup();
1447
+ this.#markerRenderer?.removePopover();
1424
1448
  this.#computeContentOffset(this.#contentElement);
1425
1449
  };
1426
1450
  #remeasureMetricsOnFontLoad() {
@@ -1434,7 +1458,7 @@ var Editor = class {
1434
1458
  this.#contentWidthCache = void 0;
1435
1459
  this.#resetCache();
1436
1460
  if (this.#selections !== void 0 || this.#matches !== void 0 || this.#markerRenderer !== void 0) this.#updateSelections(this.#selections ?? []);
1437
- this.#markerRenderer?.removePopup();
1461
+ this.#markerRenderer?.removePopover();
1438
1462
  });
1439
1463
  }
1440
1464
  #rerender(change, newLineAnnotations, renderRange = this.#renderRange, shouldUpdateBuffer) {
@@ -1742,7 +1766,7 @@ var Editor = class {
1742
1766
  }
1743
1767
  this.#setDeletedTextSelectionActive(true);
1744
1768
  this.#updateSelections([]);
1745
- [...deletionsCode.querySelectorAll("[data-gutter] > [data-column-number]")][focusIndex]?.setAttribute("data-selected-line", "single");
1769
+ this.#setEditorActiveLineSafe(getLineNumberAttr(focusContent) ?? null, true, "deletions");
1746
1770
  const lo = Math.min(anchorIndex, focusIndex);
1747
1771
  const hi = Math.max(anchorIndex, focusIndex);
1748
1772
  this.#deletedSelectionText = lo < 0 ? "" : rows.slice(lo, hi + 1).filter((row) => row.getAttribute("data-line-type") === "change-deletion").map((row) => row.textContent ?? "").join("\n");
@@ -1780,19 +1804,18 @@ var Editor = class {
1780
1804
  if (pre == null) return;
1781
1805
  if (active) {
1782
1806
  pre.setAttribute("data-deleted-text-selection", "");
1783
- for (const highlighted of pre.querySelectorAll("[data-selected-line]")) highlighted.removeAttribute("data-selected-line");
1807
+ this.#setEditorActiveLineSafe(null);
1784
1808
  this.#deletedSelectionText = "";
1785
1809
  } else {
1786
1810
  pre.removeAttribute("data-deleted-text-selection");
1787
1811
  this.#deletedSelectionText = "";
1788
1812
  }
1789
1813
  }
1790
- #setSelectedLinesSafe(range, lineNumberOnly = false) {
1814
+ #setEditorActiveLineSafe(lineNumber, lineNumberOnly = false, side = "additions") {
1791
1815
  try {
1792
- this.#fileInstance?.setSelectedLines(range, {
1793
- notify: false,
1794
- activeLineSide: "additions",
1795
- lineNumberOnly
1816
+ this.#fileInstance?.setEditorActiveLine(lineNumber, {
1817
+ lineNumberOnly,
1818
+ side
1796
1819
  });
1797
1820
  } catch {}
1798
1821
  }
@@ -1806,7 +1829,7 @@ var Editor = class {
1806
1829
  #updateSelections(selections) {
1807
1830
  this.__postponeBgTokenizeToNextFrame();
1808
1831
  this.#primaryCaretElement = void 0;
1809
- this.#setSelectedLinesSafe(null);
1832
+ this.#setEditorActiveLineSafe(null);
1810
1833
  if (selections.length === 0 && this.#matches === void 0 && this.#markerRenderer === void 0) {
1811
1834
  this.#selections = void 0;
1812
1835
  this.#overlayElements?.forEach((el) => el.remove());
@@ -1826,10 +1849,7 @@ var Editor = class {
1826
1849
  this.#selections = normalizedSelections;
1827
1850
  const hasNonEmptySelection = normalizedSelections.some((selection) => !isCollapsedSelection(selection));
1828
1851
  const caretLine = getCaretPosition(primarySelection).line + 1;
1829
- this.#setSelectedLinesSafe({
1830
- start: caretLine,
1831
- end: caretLine
1832
- }, hasNonEmptySelection);
1852
+ this.#setEditorActiveLineSafe(caretLine, hasNonEmptySelection);
1833
1853
  for (const selection of normalizedSelections) {
1834
1854
  if (!isCollapsedSelection(selection)) this.#renderSelection(renderCtx, "selection", selection);
1835
1855
  this.#renderCaret(renderCtx, selection, selection === primarySelection);