@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.
- package/dist/components/CodeView.d.ts.map +1 -1
- package/dist/components/CodeView.js +1 -16
- package/dist/components/CodeView.js.map +1 -1
- package/dist/components/File.d.ts +2 -1
- package/dist/components/File.d.ts.map +1 -1
- package/dist/components/File.js +6 -2
- package/dist/components/File.js.map +1 -1
- package/dist/components/FileDiff.d.ts +8 -2
- package/dist/components/FileDiff.d.ts.map +1 -1
- package/dist/components/FileDiff.js +26 -8
- package/dist/components/FileDiff.js.map +1 -1
- package/dist/components/VirtualizedFileDiff.d.ts +1 -0
- package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
- package/dist/components/VirtualizedFileDiff.js +4 -0
- package/dist/components/VirtualizedFileDiff.js.map +1 -1
- package/dist/editor/editStack.d.ts +5 -0
- package/dist/editor/editStack.d.ts.map +1 -1
- package/dist/editor/editStack.js +53 -12
- package/dist/editor/editStack.js.map +1 -1
- package/dist/editor/editor.d.ts.map +1 -1
- package/dist/editor/editor.js +52 -32
- package/dist/editor/editor.js.map +1 -1
- package/dist/editor/editor2.js +1 -1
- package/dist/editor/marker.d.ts +3 -3
- package/dist/editor/marker.d.ts.map +1 -1
- package/dist/editor/marker.js +89 -89
- package/dist/editor/marker.js.map +1 -1
- package/dist/editor/pieceTable.d.ts.map +1 -1
- package/dist/editor/pieceTable.js +55 -20
- package/dist/editor/pieceTable.js.map +1 -1
- package/dist/editor/popover.js +4 -0
- package/dist/editor/popover.js.map +1 -1
- package/dist/editor/selection.d.ts +2 -2
- package/dist/editor/selection.d.ts.map +1 -1
- package/dist/editor/selection.js +2 -2
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/textDocument.js +1 -1
- package/dist/editor/textDocument.js.map +1 -1
- package/dist/editor/tokenizer.d.ts.map +1 -1
- package/dist/editor/tokenizer.js +7 -2
- package/dist/editor/tokenizer.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/managers/InteractionManager.d.ts +18 -7
- package/dist/managers/InteractionManager.d.ts.map +1 -1
- package/dist/managers/InteractionManager.js +154 -99
- package/dist/managers/InteractionManager.js.map +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/utils/useFileDiffInstance.js +1 -6
- package/dist/react/utils/useFileDiffInstance.js.map +1 -1
- package/dist/react/utils/useFileInstance.js +1 -4
- package/dist/react/utils/useFileInstance.js.map +1 -1
- package/dist/ssr/index.d.ts +2 -2
- package/dist/style.js +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/computeFileOffsets.d.ts.map +1 -1
- package/dist/utils/computeFileOffsets.js +34 -5
- package/dist/utils/computeFileOffsets.js.map +1 -1
- package/dist/utils/isGutterUtilityPath.d.ts +5 -0
- package/dist/utils/isGutterUtilityPath.d.ts.map +1 -0
- package/dist/utils/isGutterUtilityPath.js +12 -0
- package/dist/utils/isGutterUtilityPath.js.map +1 -0
- package/dist/worker/worker-portable.js +34 -5
- package/dist/worker/worker-portable.js.map +1 -1
- package/dist/worker/worker.js +34 -5
- package/dist/worker/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/editor/editStack.js
CHANGED
|
@@ -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 ??=
|
|
120
|
-
if (mode !==
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
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":"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"}
|
|
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":";;;;;;;;
|
|
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"}
|
package/dist/editor/editor.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
150
|
-
if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || lineHoverHighlight !== "disabled") {
|
|
150
|
+
if (fileInstance.options.useTokenTransformer !== true) {
|
|
151
151
|
fileInstance.setOptions({
|
|
152
|
-
...
|
|
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?.
|
|
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?.
|
|
628
|
+
hasActivePopover: () => this.#selectionAction !== void 0 || this.#markerRenderer?.isPopoverVisible() === true,
|
|
632
629
|
updateActivePopover: () => {
|
|
633
630
|
this.#updateSelectionActionPopover();
|
|
634
|
-
this.#markerRenderer?.
|
|
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.#
|
|
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?.
|
|
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
|
|
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
|
|
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
|
|
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?.
|
|
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 =
|
|
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?.
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
1814
|
+
#setEditorActiveLineSafe(lineNumber, lineNumberOnly = false, side = "additions") {
|
|
1791
1815
|
try {
|
|
1792
|
-
this.#fileInstance?.
|
|
1793
|
-
|
|
1794
|
-
|
|
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.#
|
|
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.#
|
|
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);
|