@adecore/editor-react 0.39.0 → 0.40.1
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/CallHierarchyPanel.js +30 -55
- package/dist/CodeAuthorsCard.js +0 -1
- package/dist/CompletionPopup.js +150 -181
- package/dist/DiffView.d.ts +1 -1
- package/dist/DiffView.js +2 -29
- package/dist/PickPopup.js +54 -80
- package/dist/SplitDiffView.js +27 -57
- package/dist/SymbolPicker.js +2 -8
- package/dist/code-actions-model.js +2 -9
- package/dist/code-actions.js +10 -11
- package/dist/code-vision.js +2 -1
- package/dist/completion-model.js +2 -2
- package/dist/completion.js +13 -34
- package/dist/definition-link.d.ts +1 -0
- package/dist/definition-link.js +22 -18
- package/dist/diagnostics-model.d.ts +1 -0
- package/dist/diagnostics-model.js +3 -1
- package/dist/diagnostics.js +1 -2
- package/dist/diff-syntax.d.ts +1 -0
- package/dist/diff-syntax.js +31 -0
- package/dist/doc-comment-model.d.ts +1 -0
- package/dist/doc-comment-model.js +1 -1
- package/dist/folding.js +2 -3
- package/dist/format-on-type.js +6 -10
- package/dist/hover-content.d.ts +0 -2
- package/dist/hover-content.js +1 -9
- package/dist/inlay-hints.d.ts +0 -1
- package/dist/inlay-hints.js +11 -41
- package/dist/navigation.js +4 -14
- package/dist/peek.js +2 -12
- package/dist/popup-focus.d.ts +3 -1
- package/dist/popup-focus.js +33 -0
- package/dist/recent-choices.d.ts +0 -1
- package/dist/recent-choices.js +0 -4
- package/dist/rendered-docs.d.ts +1 -0
- package/dist/rendered-docs.js +15 -28
- package/dist/snippet-field-transforms.d.ts +5 -0
- package/dist/snippet-field-transforms.js +27 -16
- package/dist/snippet-session.js +8 -20
- package/dist/symbol-links.js +3 -10
- package/dist/symbol-picker-model.d.ts +1 -0
- package/dist/symbol-picker-model.js +7 -0
- package/dist/unique.d.ts +10 -0
- package/dist/unique.js +15 -0
- package/package.json +9 -9
- package/src/AnchoredPopup.tsx +2 -0
- package/src/BaselineStatus.tsx +1 -0
- package/src/CallHierarchyPanel.tsx +16 -6
- package/src/ChangeReview.tsx +7 -0
- package/src/CodeAuthorsCard.tsx +2 -5
- package/src/CompletionPopup.tsx +21 -15
- package/src/DiffView.tsx +53 -30
- package/src/EditorContextMenu.tsx +7 -0
- package/src/EditorView.tsx +28 -0
- package/src/FindReplace.tsx +8 -0
- package/src/HoverCard.tsx +8 -0
- package/src/HoverSections.tsx +11 -0
- package/src/LanguagePopups.tsx +1 -0
- package/src/PanelResizeHandle.tsx +8 -0
- package/src/PathText.tsx +1 -0
- package/src/PeekPanel.tsx +13 -0
- package/src/PickPopup.tsx +7 -6
- package/src/RenameCard.tsx +13 -0
- package/src/RenderedDocs.tsx +11 -0
- package/src/SplitDiffView.tsx +91 -56
- package/src/SymbolPicker.tsx +10 -18
- package/src/call-hierarchy-model.ts +10 -0
- package/src/call-hierarchy.ts +82 -0
- package/src/code-actions-model.ts +21 -9
- package/src/code-actions.ts +83 -7
- package/src/code-authors.ts +20 -0
- package/src/code-vision-model.ts +37 -0
- package/src/code-vision.ts +47 -3
- package/src/completion-call.ts +19 -0
- package/src/completion-model.ts +58 -2
- package/src/completion.ts +156 -30
- package/src/conflict-model.ts +4 -0
- package/src/context-menu-rows.ts +1 -0
- package/src/context-menu.ts +7 -0
- package/src/definition-link.ts +42 -18
- package/src/diagnostics-model.ts +19 -1
- package/src/diagnostics.ts +31 -2
- package/src/diff-expansion.ts +9 -0
- package/src/diff-overlays.tsx +8 -0
- package/src/diff-syntax.ts +54 -0
- package/src/diff-wordmarks.ts +8 -0
- package/src/doc-comment-model.ts +51 -1
- package/src/editor-language.ts +11 -0
- package/src/find-query.ts +7 -0
- package/src/folding.ts +10 -3
- package/src/format-on-type.ts +23 -10
- package/src/highlight-layers.ts +3 -0
- package/src/highlights.ts +18 -0
- package/src/history.ts +11 -0
- package/src/holder.ts +3 -0
- package/src/hover-content.ts +34 -17
- package/src/hover.ts +57 -0
- package/src/index.ts +4 -0
- package/src/inlay-hints.ts +86 -47
- package/src/key-priority.ts +2 -0
- package/src/line-edits.ts +8 -0
- package/src/models.ts +6 -0
- package/src/navigation-history.ts +5 -0
- package/src/navigation.ts +28 -13
- package/src/panel-size.ts +7 -0
- package/src/patch-decorations.ts +39 -0
- package/src/patch-layout.ts +47 -0
- package/src/peek-model.ts +21 -0
- package/src/peek.ts +49 -13
- package/src/pick.ts +35 -0
- package/src/popup-focus.ts +11 -1
- package/src/popup-placement.ts +5 -0
- package/src/popups.ts +2 -6
- package/src/project-language.ts +33 -0
- package/src/project-problems.ts +11 -0
- package/src/proposal-model.ts +40 -0
- package/src/recent-choices.ts +3 -5
- package/src/refresher.ts +3 -0
- package/src/rename-model.ts +28 -0
- package/src/rename.ts +56 -0
- package/src/rendered-docs.ts +89 -19
- package/src/review-group.ts +4 -0
- package/src/row-host.ts +12 -0
- package/src/selection-ranges.ts +6 -0
- package/src/semantic-delta.ts +7 -0
- package/src/semantic-model.ts +11 -0
- package/src/semantic-tokens.ts +37 -0
- package/src/shortcut-keys.ts +3 -0
- package/src/signature-model.ts +7 -0
- package/src/signature.ts +27 -0
- package/src/snippet-field-transforms.ts +48 -13
- package/src/snippet-indent.ts +10 -0
- package/src/snippet-range-order.ts +5 -0
- package/src/snippet-session.ts +173 -31
- package/src/snippet-transform-client.ts +14 -0
- package/src/snippet-transform-format.ts +98 -0
- package/src/snippet-transform-protocol.ts +5 -0
- package/src/snippet-transform-queue.ts +53 -0
- package/src/snippet-transform-worker.ts +14 -0
- package/src/snippet-variables.ts +9 -0
- package/src/snippet.ts +107 -0
- package/src/split-alignment.ts +10 -0
- package/src/split-scroll.ts +8 -0
- package/src/symbol-links.ts +9 -12
- package/src/symbol-picker-model.ts +25 -0
- package/src/symbol-picker.ts +9 -0
- package/src/symbols.ts +21 -0
- package/src/testing.ts +7 -0
- package/src/timers.ts +5 -0
- package/src/unique.ts +23 -0
- package/src/use-problem-counts.ts +4 -0
- package/src/view-state.ts +9 -0
- package/src/word-diff.ts +5 -0
- package/src/workspace-edit.ts +84 -0
package/src/snippet-session.ts
CHANGED
|
@@ -6,15 +6,13 @@ import { realTimers, type Timers } from './timers.ts';
|
|
|
6
6
|
import { KEY_PRIORITY } from './key-priority.ts';
|
|
7
7
|
import { MAX_TRANSFORM_CHARACTERS, type SnippetTransformJob } from './snippet-transform-protocol.ts';
|
|
8
8
|
import type { EditorContentChange, EditorPosition, EditorRange, EditorTextChange } from '@adecore/editor';
|
|
9
|
-
import { comparePositions, rangeHolds, shiftPosition } from './diagnostics-model.ts';
|
|
9
|
+
import { comparePositions, endOfInsertion, rangeHolds, sameRange, shiftPosition } from './diagnostics-model.ts';
|
|
10
10
|
import type { EditorLanguage } from './editor-language.ts';
|
|
11
|
+
import { textBeforeOf } from './line-text.ts';
|
|
11
12
|
import type { SnippetField, SnippetStop, SnippetTransform } from './snippet.ts';
|
|
12
13
|
|
|
13
14
|
export function positionInInsertion(start: EditorPosition, text: string, offset: number): EditorPosition {
|
|
14
|
-
|
|
15
|
-
return lines.length === 1
|
|
16
|
-
? { line: start.line, character: start.character + offset }
|
|
17
|
-
: { line: start.line + lines.length - 1, character: lines[lines.length - 1]!.length };
|
|
15
|
+
return endOfInsertion(start, text.slice(0, offset));
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
interface SessionRange extends EditorRange {
|
|
@@ -32,6 +30,7 @@ interface Field {
|
|
|
32
30
|
choices?: readonly string[];
|
|
33
31
|
divergent?: boolean;
|
|
34
32
|
}
|
|
33
|
+
|
|
35
34
|
interface Instance {
|
|
36
35
|
fields: Field[];
|
|
37
36
|
hull: EditorRange;
|
|
@@ -44,6 +43,7 @@ function mapRange<Range extends EditorRange>(
|
|
|
44
43
|
grow: boolean | ((change: EditorContentChange) => boolean)
|
|
45
44
|
): Range {
|
|
46
45
|
let { start, end } = range;
|
|
46
|
+
|
|
47
47
|
for (const change of changes) {
|
|
48
48
|
const grows = typeof grow === 'boolean' ? grow : grow(change);
|
|
49
49
|
const empty = comparePositions(start, end) === 0;
|
|
@@ -52,16 +52,19 @@ function mapRange<Range extends EditorRange>(
|
|
|
52
52
|
start = grows && comparePositions(start, change.range.start) === 0 ? change.range.start : shiftPosition(start, change);
|
|
53
53
|
end = !grows && !empty && insertAtEnd ? end : shiftPosition(end, change);
|
|
54
54
|
}
|
|
55
|
+
|
|
55
56
|
return { ...range, start, end };
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
function selectionsOf(field: Field): SessionRange[] {
|
|
59
60
|
const primary = field.ranges[field.primary]!;
|
|
60
61
|
const sorted = [...field.ranges].sort((left, right) => comparePositions(left.start, right.start));
|
|
62
|
+
|
|
61
63
|
// The editor merges touching selections; keep these mirrors static instead of editing their union.
|
|
62
64
|
if (sorted.some((range, index) => index > 0 && comparePositions(sorted[index - 1]!.end, range.start) >= 0)) {
|
|
63
65
|
return [primary];
|
|
64
66
|
}
|
|
67
|
+
|
|
65
68
|
return [...field.ranges.filter((range) => range !== primary), primary];
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -81,6 +84,7 @@ export interface SnippetInsertion {
|
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
type SnippetHost = Pick<EditorLanguage, 'editor' | 'onDispose'> & Partial<Pick<EditorLanguage, 'popups' | 'pick' | 'project' | 'onKey'>>;
|
|
87
|
+
|
|
84
88
|
const MAX_FRAMES = 8;
|
|
85
89
|
const MAX_RANGES = 1000;
|
|
86
90
|
|
|
@@ -88,9 +92,11 @@ function frameOf(insertions: readonly SnippetInsertion[]): Frame | null {
|
|
|
88
92
|
const primary = insertions.at(-1);
|
|
89
93
|
const positions = [...insertions].sort((left, right) => comparePositions(left.start, right.start));
|
|
90
94
|
const instanceOrder = new Map(positions.map((insertion, index) => [insertion.start, index]));
|
|
95
|
+
|
|
91
96
|
if (primary === undefined || primary.stops.length === 0) {
|
|
92
97
|
return null;
|
|
93
98
|
}
|
|
99
|
+
|
|
94
100
|
return {
|
|
95
101
|
order: primary.stops.map((stop) => stop.index),
|
|
96
102
|
current: 0,
|
|
@@ -101,6 +107,7 @@ function frameOf(insertions: readonly SnippetInsertion[]): Frame | null {
|
|
|
101
107
|
selected: [],
|
|
102
108
|
fields: stops.map((stop): Field => {
|
|
103
109
|
const field = fields?.find((field) => field.index === stop.index);
|
|
110
|
+
|
|
104
111
|
return {
|
|
105
112
|
index: stop.index,
|
|
106
113
|
primary: field?.primary ?? 0,
|
|
@@ -125,41 +132,54 @@ function rangeCount(frame: Frame): number {
|
|
|
125
132
|
|
|
126
133
|
function selectedOf(frame: Frame): SessionRange[] {
|
|
127
134
|
const stop = frame.order[frame.current]!;
|
|
135
|
+
|
|
128
136
|
const selections = frame.instances.flatMap((instance) => {
|
|
129
137
|
const field = instance.fields.find((field) => field.index === stop);
|
|
130
138
|
instance.selected = field === undefined ? [] : stop === 0 ? [field.ranges[field.primary]!] : selectionsOf(field);
|
|
139
|
+
|
|
131
140
|
return instance.selected;
|
|
132
141
|
});
|
|
142
|
+
|
|
133
143
|
const sorted = [...selections].sort((left, right) => comparePositions(left.start, right.start));
|
|
144
|
+
|
|
134
145
|
if (sorted.some((range, index) => index > 0 && comparePositions(sorted[index - 1]!.end, range.start) >= 0)) {
|
|
135
146
|
const primary = selections.at(-1)!;
|
|
147
|
+
|
|
136
148
|
for (const instance of frame.instances) {
|
|
137
149
|
instance.selected = instance.selected.includes(primary) ? [primary] : [];
|
|
138
150
|
}
|
|
151
|
+
|
|
139
152
|
return [primary];
|
|
140
153
|
}
|
|
154
|
+
|
|
141
155
|
return selections;
|
|
142
156
|
}
|
|
143
157
|
|
|
144
158
|
function activeOwners(active: readonly SessionRange[], changes: readonly EditorContentChange[]): ReadonlyMap<EditorContentChange, SessionRange> {
|
|
145
159
|
const sorted = [...active].sort((left, right) => comparePositions(left.start, right.start) || comparePositions(left.end, right.end));
|
|
146
160
|
const owners = new Map<EditorContentChange, SessionRange>();
|
|
161
|
+
|
|
147
162
|
for (const edit of changes) {
|
|
148
163
|
let low = 0;
|
|
149
164
|
let high = sorted.length;
|
|
165
|
+
|
|
150
166
|
while (low < high) {
|
|
151
167
|
const middle = (low + high) >>> 1;
|
|
168
|
+
|
|
152
169
|
if (comparePositions(sorted[middle]!.start, edit.range.start) <= 0) {
|
|
153
170
|
low = middle + 1;
|
|
154
171
|
} else {
|
|
155
172
|
high = middle;
|
|
156
173
|
}
|
|
157
174
|
}
|
|
175
|
+
|
|
158
176
|
const range = sorted[low - 1];
|
|
177
|
+
|
|
159
178
|
if (range !== undefined && rangeHolds(range, edit.range.start) && rangeHolds(range, edit.range.end)) {
|
|
160
179
|
owners.set(edit, range);
|
|
161
180
|
}
|
|
162
181
|
}
|
|
182
|
+
|
|
163
183
|
return owners;
|
|
164
184
|
}
|
|
165
185
|
|
|
@@ -175,6 +195,7 @@ export class SnippetFeature {
|
|
|
175
195
|
private readonly transforms: SnippetTransformLease | null;
|
|
176
196
|
private readonly fieldTransforms: SnippetFieldTransforms;
|
|
177
197
|
private exitGeneration = 0;
|
|
198
|
+
|
|
178
199
|
private detached: {
|
|
179
200
|
targets: readonly FieldTransformTarget[];
|
|
180
201
|
ranges: ReadonlyMap<symbol, EditorRange>;
|
|
@@ -186,15 +207,19 @@ export class SnippetFeature {
|
|
|
186
207
|
this.transforms = language.project === undefined ? null : acquireSnippetTransforms(language.project, timers);
|
|
187
208
|
const { editor } = language;
|
|
188
209
|
this.fieldTransforms = new SnippetFieldTransforms(editor, this.transforms, (targets, values) => this.applyFieldTransforms(targets, values));
|
|
210
|
+
|
|
189
211
|
const key = (event: KeyboardEvent): boolean => this.key(event);
|
|
212
|
+
|
|
190
213
|
const offs = [
|
|
191
214
|
language.onKey === undefined ? editor.onKeyDown(key) : language.onKey(key, KEY_PRIORITY.snippets),
|
|
192
215
|
editor.onTextChange((change) => this.edited(change)),
|
|
193
216
|
editor.onCaret(() => this.moved())
|
|
194
217
|
];
|
|
218
|
+
|
|
195
219
|
language.onDispose(() => {
|
|
196
220
|
this.transforms?.dispose();
|
|
197
221
|
this.end();
|
|
222
|
+
|
|
198
223
|
for (const off of offs) {
|
|
199
224
|
off();
|
|
200
225
|
}
|
|
@@ -205,12 +230,14 @@ export class SnippetFeature {
|
|
|
205
230
|
if (this.transforms === null) {
|
|
206
231
|
return null;
|
|
207
232
|
}
|
|
233
|
+
|
|
208
234
|
try {
|
|
209
235
|
return await this.transforms.request(jobs, { current });
|
|
210
236
|
} catch (error) {
|
|
211
237
|
if (error instanceof SnippetTransformError && ['timeout', 'unavailable', 'superseded', 'disposed'].includes(error.kind)) {
|
|
212
238
|
return null;
|
|
213
239
|
}
|
|
240
|
+
|
|
214
241
|
throw error;
|
|
215
242
|
}
|
|
216
243
|
}
|
|
@@ -225,6 +252,7 @@ export class SnippetFeature {
|
|
|
225
252
|
): Promise<boolean> {
|
|
226
253
|
let selections = this.language.editor.getSelections(),
|
|
227
254
|
valid = true;
|
|
255
|
+
|
|
228
256
|
const off = this.language.editor.onTextChange((change) => {
|
|
229
257
|
if (this.detached === null) {
|
|
230
258
|
valid = false;
|
|
@@ -233,20 +261,16 @@ export class SnippetFeature {
|
|
|
233
261
|
edited?.(change.changes, this.detached.map);
|
|
234
262
|
}
|
|
235
263
|
});
|
|
264
|
+
|
|
236
265
|
try {
|
|
237
266
|
await this.fieldTransforms.settle();
|
|
238
267
|
} finally {
|
|
239
268
|
off();
|
|
240
269
|
}
|
|
270
|
+
|
|
241
271
|
const now = this.language.editor.getSelections();
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
current() &&
|
|
245
|
-
now.length === selections.length &&
|
|
246
|
-
now.every(
|
|
247
|
-
(range, index) => comparePositions(range.start, selections[index]!.start) === 0 && comparePositions(range.end, selections[index]!.end) === 0
|
|
248
|
-
)
|
|
249
|
-
);
|
|
272
|
+
|
|
273
|
+
return valid && current() && now.length === selections.length && now.every((range, index) => sameRange(range, selections[index]!));
|
|
250
274
|
}
|
|
251
275
|
|
|
252
276
|
get isActive(): boolean {
|
|
@@ -271,18 +295,25 @@ export class SnippetFeature {
|
|
|
271
295
|
: insertion.fields.reduce((count, field) => count + field.ranges.length + (field.linked?.length ?? 0), 1)),
|
|
272
296
|
0
|
|
273
297
|
);
|
|
298
|
+
|
|
274
299
|
if (ranges > MAX_RANGES) {
|
|
275
300
|
this.end();
|
|
301
|
+
|
|
276
302
|
return;
|
|
277
303
|
}
|
|
304
|
+
|
|
278
305
|
const frame = frameOf(insertions);
|
|
306
|
+
|
|
279
307
|
if (frame === null) {
|
|
280
308
|
if (!options.nested) {
|
|
281
309
|
this.end();
|
|
282
310
|
}
|
|
311
|
+
|
|
283
312
|
return;
|
|
284
313
|
}
|
|
314
|
+
|
|
285
315
|
const parent = this.frames.at(-1);
|
|
316
|
+
|
|
286
317
|
const nested =
|
|
287
318
|
options.nested &&
|
|
288
319
|
parent !== undefined &&
|
|
@@ -291,13 +322,17 @@ export class SnippetFeature {
|
|
|
291
322
|
parent.selected.some((range) => rangeHolds(range, instance.hull.start) && rangeHolds(range, instance.hull.end))
|
|
292
323
|
)
|
|
293
324
|
);
|
|
325
|
+
|
|
294
326
|
if (nested && parent !== undefined) {
|
|
295
327
|
const selectedParents = new Set(this.frames.flatMap((frame) => frame.instances.flatMap((parent) => parent.selected.map((range) => range.id))));
|
|
328
|
+
|
|
296
329
|
for (const instance of frame.instances) {
|
|
297
330
|
const enclosing = parent.instances
|
|
298
331
|
.flatMap((parent) => parent.selected)
|
|
299
332
|
.find((range) => rangeHolds(range, instance.hull.start) && rangeHolds(range, instance.hull.end));
|
|
333
|
+
|
|
300
334
|
const emptyHull = comparePositions(instance.hull.start, instance.hull.end) === 0;
|
|
335
|
+
|
|
301
336
|
const ancestors = this.frames
|
|
302
337
|
.flatMap((frame) => frame.instances.flatMap((parent) => parent.fields.flatMap((field) => field.ranges)))
|
|
303
338
|
.filter(
|
|
@@ -310,17 +345,21 @@ export class SnippetFeature {
|
|
|
310
345
|
(comparePositions(range.start, instance.hull.start) < 0 && comparePositions(instance.hull.end, range.end) < 0))
|
|
311
346
|
)
|
|
312
347
|
.map((range) => range.id);
|
|
348
|
+
|
|
313
349
|
for (const field of instance.fields) {
|
|
314
350
|
field.ranges = field.ranges.map((range) => ({ ...range, order: [...(enclosing?.order ?? []), ...range.order], ancestors }));
|
|
315
351
|
}
|
|
316
352
|
}
|
|
317
353
|
}
|
|
354
|
+
|
|
318
355
|
if (!nested || this.frames.length >= MAX_FRAMES || this.frames.reduce((total, frame) => total + rangeCount(frame), rangeCount(frame)) > MAX_RANGES) {
|
|
319
356
|
this.end();
|
|
320
357
|
}
|
|
358
|
+
|
|
321
359
|
if (rangeCount(frame) > MAX_RANGES) {
|
|
322
360
|
return;
|
|
323
361
|
}
|
|
362
|
+
|
|
324
363
|
this.closeChoices();
|
|
325
364
|
this.frames.push(frame);
|
|
326
365
|
this.generation++;
|
|
@@ -330,6 +369,7 @@ export class SnippetFeature {
|
|
|
330
369
|
/* Imports can touch a hull's edge; their affinity must differ from edits inside its active field. */
|
|
331
370
|
applyEdits(changes: readonly EditorContentChange[], token: number, extras: readonly EditorContentChange[] = []): boolean {
|
|
332
371
|
const frame = this.frames.at(-1);
|
|
372
|
+
|
|
333
373
|
const fits =
|
|
334
374
|
frame !== undefined &&
|
|
335
375
|
changes.every((edit) =>
|
|
@@ -341,12 +381,16 @@ export class SnippetFeature {
|
|
|
341
381
|
instance.selected.some((range) => rangeHolds(range, edit.range.start) && rangeHolds(range, edit.range.end))
|
|
342
382
|
)
|
|
343
383
|
);
|
|
384
|
+
|
|
344
385
|
if (token !== this.generation || !fits) {
|
|
345
386
|
this.end();
|
|
387
|
+
|
|
346
388
|
return this.language.editor.applyEdits(changes);
|
|
347
389
|
}
|
|
390
|
+
|
|
348
391
|
this.applying = true;
|
|
349
392
|
this.extras = extras;
|
|
393
|
+
|
|
350
394
|
try {
|
|
351
395
|
return this.language.editor.applyEdits(changes);
|
|
352
396
|
} finally {
|
|
@@ -366,9 +410,11 @@ export class SnippetFeature {
|
|
|
366
410
|
|
|
367
411
|
private closeChoices(): void {
|
|
368
412
|
const view = this.language.popups?.getState().pick;
|
|
413
|
+
|
|
369
414
|
if (this.choicePrefix !== null && view?.groups.some((group) => group.rows.some((row) => row.id.startsWith(this.choicePrefix!)))) {
|
|
370
415
|
this.language.pick?.close();
|
|
371
416
|
}
|
|
417
|
+
|
|
372
418
|
this.choicePrefix = null;
|
|
373
419
|
}
|
|
374
420
|
|
|
@@ -377,6 +423,7 @@ export class SnippetFeature {
|
|
|
377
423
|
this.frames.pop();
|
|
378
424
|
this.generation++;
|
|
379
425
|
const parent = this.frames.at(-1);
|
|
426
|
+
|
|
380
427
|
if (parent !== undefined) {
|
|
381
428
|
selectedOf(parent);
|
|
382
429
|
parent.expectedSelections = this.language.editor.getSelections().length;
|
|
@@ -385,9 +432,11 @@ export class SnippetFeature {
|
|
|
385
432
|
|
|
386
433
|
private visit(index: number, choices = true): void {
|
|
387
434
|
const frame = this.frames.at(-1);
|
|
435
|
+
|
|
388
436
|
if (frame === undefined) {
|
|
389
437
|
return;
|
|
390
438
|
}
|
|
439
|
+
|
|
391
440
|
this.closeChoices();
|
|
392
441
|
this.generation++;
|
|
393
442
|
frame.current = index;
|
|
@@ -396,20 +445,26 @@ export class SnippetFeature {
|
|
|
396
445
|
this.fieldTransforms.prune();
|
|
397
446
|
const selections = selectedOf(frame);
|
|
398
447
|
this.placed = selections;
|
|
448
|
+
|
|
399
449
|
if (selections.length === 0) {
|
|
400
450
|
this.end();
|
|
451
|
+
|
|
401
452
|
return;
|
|
402
453
|
}
|
|
454
|
+
|
|
403
455
|
this.visiting = true;
|
|
456
|
+
|
|
404
457
|
try {
|
|
405
458
|
this.language.editor.setSelections(selections.map(({ start, end }) => ({ start, end })));
|
|
406
459
|
frame.expectedSelections = this.language.editor.getSelections().length;
|
|
407
460
|
} finally {
|
|
408
461
|
this.visiting = false;
|
|
409
462
|
}
|
|
463
|
+
|
|
410
464
|
if (choices && stop !== 0) {
|
|
411
465
|
this.announceField(frame, index);
|
|
412
466
|
}
|
|
467
|
+
|
|
413
468
|
if (stop === 0 || index === frame.order.length - 1) {
|
|
414
469
|
this.pop();
|
|
415
470
|
} else if (choices) {
|
|
@@ -420,12 +475,15 @@ export class SnippetFeature {
|
|
|
420
475
|
/* Says which field the caret moved to, with the number of choices when the field is a list. */
|
|
421
476
|
private announceField(frame: Frame, index: number): void {
|
|
422
477
|
const i18n = this.language.project?.i18n;
|
|
478
|
+
|
|
423
479
|
if (i18n === undefined) {
|
|
424
480
|
return;
|
|
425
481
|
}
|
|
482
|
+
|
|
426
483
|
const total = frame.order.filter((stop) => stop !== 0).length;
|
|
427
484
|
const options = { current: index + 1, total };
|
|
428
485
|
const choices = this.fieldOf(frame)?.choices?.length ?? 0;
|
|
486
|
+
|
|
429
487
|
this.language.editor.announce(
|
|
430
488
|
choices > 0 && index < frame.order.length - 1
|
|
431
489
|
? i18n.t('editor:language.announce.fieldChoices', { ...options, count: choices })
|
|
@@ -433,18 +491,21 @@ export class SnippetFeature {
|
|
|
433
491
|
);
|
|
434
492
|
}
|
|
435
493
|
|
|
436
|
-
private fieldOf(frame: Frame):
|
|
494
|
+
private fieldOf(frame: Frame): Field | undefined {
|
|
437
495
|
return frame.instances.at(-1)?.fields.find((field) => field.index === frame.order[frame.current]);
|
|
438
496
|
}
|
|
439
497
|
|
|
440
498
|
private openChoices(frame: Frame, activeChoice?: number): void {
|
|
441
499
|
const field = this.fieldOf(frame);
|
|
500
|
+
|
|
442
501
|
if (field?.choices === undefined || field.choices.length === 0 || this.language.pick === undefined) {
|
|
443
502
|
return;
|
|
444
503
|
}
|
|
504
|
+
|
|
445
505
|
const token = this.generation;
|
|
446
506
|
const prefix = `snippet-${token}-`;
|
|
447
507
|
this.choicePrefix = prefix;
|
|
508
|
+
|
|
448
509
|
this.language.pick.open({
|
|
449
510
|
anchor: field.ranges[field.primary]!.start,
|
|
450
511
|
title: this.language.project?.i18n.t('editor:language.snippet.choices'),
|
|
@@ -454,12 +515,16 @@ export class SnippetFeature {
|
|
|
454
515
|
if (token !== this.generation) {
|
|
455
516
|
return;
|
|
456
517
|
}
|
|
518
|
+
|
|
457
519
|
const text = field.choices![Number(id.slice(prefix.length))];
|
|
520
|
+
|
|
458
521
|
if (text === undefined) {
|
|
459
522
|
return;
|
|
460
523
|
}
|
|
524
|
+
|
|
461
525
|
const changes = frame.instances.flatMap((instance) => instance.selected.map((range) => ({ range, text })));
|
|
462
526
|
this.applyEdits(changes, token);
|
|
527
|
+
|
|
463
528
|
if (this.frames.at(-1) === frame) {
|
|
464
529
|
this.visit(frame.current, false);
|
|
465
530
|
}
|
|
@@ -474,10 +539,13 @@ export class SnippetFeature {
|
|
|
474
539
|
|
|
475
540
|
private step(backwards: boolean): void {
|
|
476
541
|
const frame = this.frames.at(-1);
|
|
542
|
+
|
|
477
543
|
if (frame === undefined) {
|
|
478
544
|
return;
|
|
479
545
|
}
|
|
546
|
+
|
|
480
547
|
this.exitField(frame);
|
|
548
|
+
|
|
481
549
|
if (backwards && frame.current === 0 && this.frames.length > 1) {
|
|
482
550
|
this.pop();
|
|
483
551
|
this.visit(this.frames.at(-1)!.current);
|
|
@@ -490,26 +558,34 @@ export class SnippetFeature {
|
|
|
490
558
|
if ((!this.isActive && !this.fieldTransforms.pending) || event.ctrlKey || event.metaKey || event.altKey || event.isComposing) {
|
|
491
559
|
return false;
|
|
492
560
|
}
|
|
561
|
+
|
|
493
562
|
if (event.key === 'Tab' && this.isActive) {
|
|
494
563
|
this.step(event.shiftKey);
|
|
564
|
+
|
|
495
565
|
return true;
|
|
496
566
|
}
|
|
567
|
+
|
|
497
568
|
if (event.key === 'Escape') {
|
|
498
569
|
const popups = this.language.popups?.getState();
|
|
570
|
+
|
|
499
571
|
if (
|
|
500
572
|
popups !== undefined &&
|
|
501
573
|
[popups.completion, popups.signature, popups.hover, popups.pick, popups.peek, popups.callHierarchy].some((popup) => popup !== null)
|
|
502
574
|
) {
|
|
503
575
|
return false;
|
|
504
576
|
}
|
|
577
|
+
|
|
505
578
|
const active = this.isActive;
|
|
506
579
|
const caret = this.language.editor.getCaret();
|
|
507
580
|
this.end();
|
|
581
|
+
|
|
508
582
|
if (active) {
|
|
509
583
|
this.language.editor.setCaret(caret);
|
|
510
584
|
}
|
|
585
|
+
|
|
511
586
|
return true;
|
|
512
587
|
}
|
|
588
|
+
|
|
513
589
|
return false;
|
|
514
590
|
}
|
|
515
591
|
|
|
@@ -519,12 +595,15 @@ export class SnippetFeature {
|
|
|
519
595
|
frame.exits.set(index, seq);
|
|
520
596
|
const generation = this.exitGeneration;
|
|
521
597
|
const targets: FieldTransformTarget[] = [];
|
|
598
|
+
|
|
522
599
|
for (const instance of frame.instances) {
|
|
523
600
|
const field = instance.fields.find((field) => field.index === index);
|
|
601
|
+
|
|
524
602
|
for (const range of field?.ranges ?? []) {
|
|
525
603
|
if (range.transform === undefined) {
|
|
526
604
|
continue;
|
|
527
605
|
}
|
|
606
|
+
|
|
528
607
|
targets.push({
|
|
529
608
|
id: range.id,
|
|
530
609
|
range,
|
|
@@ -538,6 +617,7 @@ export class SnippetFeature {
|
|
|
538
617
|
});
|
|
539
618
|
}
|
|
540
619
|
}
|
|
620
|
+
|
|
541
621
|
if (
|
|
542
622
|
targets.length === 0 ||
|
|
543
623
|
this.transforms === null ||
|
|
@@ -545,18 +625,23 @@ export class SnippetFeature {
|
|
|
545
625
|
) {
|
|
546
626
|
return;
|
|
547
627
|
}
|
|
628
|
+
|
|
548
629
|
for (const instance of frame.instances) {
|
|
549
630
|
for (const field of instance.fields) {
|
|
550
631
|
const primary = field.ranges[field.primary];
|
|
632
|
+
|
|
551
633
|
field.ranges = field.ranges.filter(
|
|
552
634
|
(range) =>
|
|
553
635
|
!range.parents?.includes(index) ||
|
|
554
636
|
!targets.some((target) => target.owner === instance && rangeHolds(target.range, range.start) && rangeHolds(target.range, range.end))
|
|
555
637
|
);
|
|
638
|
+
|
|
556
639
|
field.primary = Math.max(0, field.ranges.indexOf(primary!));
|
|
557
640
|
}
|
|
641
|
+
|
|
558
642
|
instance.fields = instance.fields.filter((field) => field.ranges.length > 0);
|
|
559
643
|
}
|
|
644
|
+
|
|
560
645
|
frame.order = frame.order.filter((stop) => frame.instances.at(-1)?.fields.some((field) => field.index === stop));
|
|
561
646
|
frame.current = frame.order.indexOf(index);
|
|
562
647
|
this.fieldTransforms.enqueue(targets, () => generation === this.exitGeneration && frame.exits.get(index) === seq);
|
|
@@ -566,6 +651,7 @@ export class SnippetFeature {
|
|
|
566
651
|
const { editor } = this.language;
|
|
567
652
|
const activeFrame = this.frames.at(-1);
|
|
568
653
|
const active = activeFrame?.instances.flatMap((instance) => instance.selected) ?? [];
|
|
654
|
+
|
|
569
655
|
if (
|
|
570
656
|
targets.some(
|
|
571
657
|
(target) =>
|
|
@@ -575,25 +661,34 @@ export class SnippetFeature {
|
|
|
575
661
|
) {
|
|
576
662
|
return;
|
|
577
663
|
}
|
|
664
|
+
|
|
578
665
|
const groups: { change: EditorContentChange; targets: { target: FieldTransformTarget; text: string }[] }[] = [];
|
|
579
666
|
let characters = 0;
|
|
667
|
+
|
|
580
668
|
for (const [index, target] of targets.entries()) {
|
|
581
|
-
const before = editor
|
|
669
|
+
const before = textBeforeOf(editor, target.range.start);
|
|
582
670
|
const prefix = /^[\t ]*/.exec(before)?.[0] ?? '';
|
|
583
671
|
const value = values[index]!;
|
|
584
672
|
let newlines = 0;
|
|
673
|
+
|
|
585
674
|
for (let at = value.indexOf('\n'); at !== -1; at = value.indexOf('\n', at + 1)) {
|
|
586
675
|
newlines++;
|
|
587
676
|
}
|
|
677
|
+
|
|
588
678
|
characters += value.length + newlines * prefix.length;
|
|
679
|
+
|
|
589
680
|
if (characters > MAX_TRANSFORM_CHARACTERS) {
|
|
590
681
|
return;
|
|
591
682
|
}
|
|
683
|
+
|
|
592
684
|
const text = value.replace(/\n/g, '\n' + prefix);
|
|
685
|
+
|
|
593
686
|
if (text === editor.textInRange(target.range)) {
|
|
594
687
|
continue;
|
|
595
688
|
}
|
|
689
|
+
|
|
596
690
|
const empty = comparePositions(target.range.start, target.range.end) === 0;
|
|
691
|
+
|
|
597
692
|
const group = empty
|
|
598
693
|
? groups.find(
|
|
599
694
|
(group) =>
|
|
@@ -601,6 +696,7 @@ export class SnippetFeature {
|
|
|
601
696
|
comparePositions(group.change.range.start, target.range.start) === 0
|
|
602
697
|
)
|
|
603
698
|
: undefined;
|
|
699
|
+
|
|
604
700
|
if (group === undefined) {
|
|
605
701
|
groups.push({ change: { range: target.range, text }, targets: [{ target, text }] });
|
|
606
702
|
} else {
|
|
@@ -608,25 +704,32 @@ export class SnippetFeature {
|
|
|
608
704
|
group.targets.push({ target, text });
|
|
609
705
|
}
|
|
610
706
|
}
|
|
707
|
+
|
|
611
708
|
if (groups.length === 0) {
|
|
612
709
|
return;
|
|
613
710
|
}
|
|
711
|
+
|
|
614
712
|
for (const group of groups) {
|
|
615
713
|
group.targets.sort((left, right) => compareSnippetOrder(left.target.order, right.target.order));
|
|
616
714
|
group.change = { range: group.change.range, text: group.targets.map(({ text }) => text).join('') };
|
|
617
715
|
}
|
|
716
|
+
|
|
618
717
|
const changes = groups.map((group) => group.change).sort((left, right) => comparePositions(right.range.start, left.range.start));
|
|
619
718
|
const peersOf = new Map(groups.map((group) => [group.change, group.targets.map(({ target }) => target)]));
|
|
620
719
|
const ranges = new Map<symbol, EditorRange>();
|
|
621
720
|
const steps = new Map<symbol, EditorRange>();
|
|
721
|
+
|
|
622
722
|
for (const group of groups) {
|
|
623
723
|
let start = group.change.range.start;
|
|
724
|
+
|
|
624
725
|
for (const edit of changes) {
|
|
625
726
|
if (comparePositions(edit.range.start, group.change.range.start) < 0) {
|
|
626
727
|
start = shiftPosition(start, edit);
|
|
627
728
|
}
|
|
628
729
|
}
|
|
730
|
+
|
|
629
731
|
let local = group.change.range.start;
|
|
732
|
+
|
|
630
733
|
for (const { target, text } of group.targets) {
|
|
631
734
|
const localEnd = positionInInsertion(local, text, text.length);
|
|
632
735
|
steps.set(target.id, { start: local, end: localEnd });
|
|
@@ -636,12 +739,16 @@ export class SnippetFeature {
|
|
|
636
739
|
start = end;
|
|
637
740
|
}
|
|
638
741
|
}
|
|
742
|
+
|
|
639
743
|
const frame = this.frames.at(-1);
|
|
640
744
|
const pick = this.language.popups?.getState().pick;
|
|
745
|
+
|
|
641
746
|
const activeChoice =
|
|
642
747
|
this.choicePrefix !== null && pick?.active.startsWith(this.choicePrefix) ? Number(pick.active.slice(this.choicePrefix.length)) : undefined;
|
|
748
|
+
|
|
643
749
|
const trackedFrames = [...new Set([...this.frames, ...targets.map((target) => target.frame as Frame)])];
|
|
644
750
|
const transformIds = new Set(targets.map((target) => target.id));
|
|
751
|
+
|
|
645
752
|
const captured = [
|
|
646
753
|
...this.placed,
|
|
647
754
|
...active,
|
|
@@ -653,6 +760,7 @@ export class SnippetFeature {
|
|
|
653
760
|
)
|
|
654
761
|
)
|
|
655
762
|
];
|
|
763
|
+
|
|
656
764
|
const ancestorsOf = new Map(
|
|
657
765
|
targets.map((target) => [
|
|
658
766
|
target,
|
|
@@ -670,36 +778,43 @@ export class SnippetFeature {
|
|
|
670
778
|
])
|
|
671
779
|
])
|
|
672
780
|
);
|
|
781
|
+
|
|
673
782
|
const map = <Range extends EditorRange>(range: Range, parent?: (target: FieldTransformTarget) => boolean): Range => {
|
|
674
|
-
const tracked =
|
|
675
|
-
'order' in range
|
|
676
|
-
? (range as Range & SessionRange)
|
|
677
|
-
: captured.find((candidate) => comparePositions(candidate.start, range.start) === 0 && comparePositions(candidate.end, range.end) === 0);
|
|
783
|
+
const tracked = 'order' in range ? (range as Range & SessionRange) : captured.find((candidate) => sameRange(candidate, range));
|
|
678
784
|
let { start, end } = range;
|
|
785
|
+
|
|
679
786
|
for (const change of changes) {
|
|
680
787
|
const peers = peersOf.get(change)!;
|
|
681
788
|
const descendants = peers.filter((peer) => parent?.(peer) ?? (tracked !== undefined && ancestorsOf.get(peer)!.has(tracked.id)));
|
|
682
789
|
const grows = descendants.length > 0;
|
|
683
790
|
const empty = comparePositions(start, end) === 0;
|
|
791
|
+
|
|
684
792
|
const boundary = (position: EditorPosition): EditorPosition => {
|
|
685
793
|
if (comparePositions(position, change.range.start) !== 0 || peers.length === 0) {
|
|
686
794
|
return shiftPosition(position, change);
|
|
687
795
|
}
|
|
796
|
+
|
|
688
797
|
let low = 0,
|
|
689
798
|
high = peers.length;
|
|
799
|
+
|
|
690
800
|
while (low < high) {
|
|
691
801
|
const middle = (low + high) >>> 1;
|
|
802
|
+
|
|
692
803
|
if (compareSnippetOrder(peers[middle]!.order, tracked?.order ?? [Infinity]) < 0) {
|
|
693
804
|
low = middle + 1;
|
|
694
805
|
} else {
|
|
695
806
|
high = middle;
|
|
696
807
|
}
|
|
697
808
|
}
|
|
809
|
+
|
|
698
810
|
return low === 0 ? steps.get(peers[0]!.id)!.start : steps.get(peers[low - 1]!.id)!.end;
|
|
699
811
|
};
|
|
812
|
+
|
|
700
813
|
// A grown range takes in only its own descendants' output; a sibling's output at the same point stays outside.
|
|
701
814
|
const atChange = (position: EditorPosition): boolean => comparePositions(position, change.range.start) === 0;
|
|
815
|
+
|
|
702
816
|
start = grows && atChange(start) ? steps.get(descendants[0]!.id)!.start : grows ? shiftPosition(start, change) : boundary(start);
|
|
817
|
+
|
|
703
818
|
end = grows
|
|
704
819
|
? atChange(end)
|
|
705
820
|
? steps.get(descendants.at(-1)!.id)!.end
|
|
@@ -708,16 +823,20 @@ export class SnippetFeature {
|
|
|
708
823
|
? end
|
|
709
824
|
: boundary(end);
|
|
710
825
|
}
|
|
826
|
+
|
|
711
827
|
return { ...range, start, end };
|
|
712
828
|
};
|
|
829
|
+
|
|
713
830
|
this.detached = { targets, ranges, map };
|
|
714
831
|
this.applying = true;
|
|
832
|
+
|
|
715
833
|
try {
|
|
716
834
|
editor.applyEdits(changes, { mapSelections: (range) => map(range) });
|
|
717
835
|
} finally {
|
|
718
836
|
this.applying = false;
|
|
719
837
|
this.detached = null;
|
|
720
838
|
}
|
|
839
|
+
|
|
721
840
|
// The generic picker closes on any text edit, including an exit elsewhere in the snippet.
|
|
722
841
|
if (frame !== undefined && frame === this.frames.at(-1) && activeChoice !== undefined && this.language.popups?.getState().pick === null) {
|
|
723
842
|
this.openChoices(frame, activeChoice);
|
|
@@ -727,24 +846,31 @@ export class SnippetFeature {
|
|
|
727
846
|
private mapFrame(frame: Frame, changes: readonly EditorContentChange[], extras: ReadonlySet<EditorContentChange>): boolean {
|
|
728
847
|
const index = frame.order[frame.current]!;
|
|
729
848
|
const owned = this.detached === null ? changes.filter((edit) => !extras.has(edit)) : [];
|
|
849
|
+
|
|
730
850
|
for (const instance of frame.instances) {
|
|
731
851
|
const active = instance.fields.find((field) => field.index === index)?.ranges ?? [];
|
|
732
852
|
const owners = activeOwners(active, owned);
|
|
853
|
+
|
|
733
854
|
instance.selected = instance.selected.map((range) =>
|
|
734
855
|
this.detached === null ? mapRange(range, changes, (edit) => !extras.has(edit)) : this.detached.map(range)
|
|
735
856
|
);
|
|
857
|
+
|
|
736
858
|
const values = instance.selected.map((range) => this.language.editor.textInRange(range));
|
|
737
859
|
const field = instance.fields.find((field) => field.index === index);
|
|
860
|
+
|
|
738
861
|
if (values.some((value) => value !== values[0])) {
|
|
739
862
|
if (field === undefined || (!this.applying && !field.divergent)) {
|
|
740
863
|
return false;
|
|
741
864
|
}
|
|
865
|
+
|
|
742
866
|
// Per-caret completion variables intentionally give parent mirrors different values.
|
|
743
867
|
field.divergent = true;
|
|
744
868
|
} else if (field !== undefined) {
|
|
745
869
|
field.divergent = false;
|
|
746
870
|
}
|
|
871
|
+
|
|
747
872
|
const rangeIds = new Set(instance.fields.flatMap((field) => field.ranges.map((range) => range.id)));
|
|
873
|
+
|
|
748
874
|
instance.hull = mapRange(instance.hull, changes, (edit) =>
|
|
749
875
|
this.detached === null
|
|
750
876
|
? !extras.has(edit)
|
|
@@ -754,8 +880,10 @@ export class SnippetFeature {
|
|
|
754
880
|
comparePositions(target.range.start, edit.range.start) === 0
|
|
755
881
|
)
|
|
756
882
|
);
|
|
883
|
+
|
|
757
884
|
for (const field of instance.fields) {
|
|
758
885
|
const primary = field.ranges[field.primary];
|
|
886
|
+
|
|
759
887
|
field.ranges = field.ranges.filter(
|
|
760
888
|
(range) =>
|
|
761
889
|
!range.parents?.includes(index) ||
|
|
@@ -767,16 +895,18 @@ export class SnippetFeature {
|
|
|
767
895
|
: comparePositions(edit.range.start, range.end) < 0 && comparePositions(range.start, edit.range.end) < 0)
|
|
768
896
|
)
|
|
769
897
|
);
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
);
|
|
898
|
+
|
|
899
|
+
field.primary = Math.max(0, field.ranges.indexOf(primary!));
|
|
900
|
+
|
|
774
901
|
field.ranges = field.ranges.map((range) => {
|
|
775
902
|
const replacement = this.detached?.ranges.get(range.id);
|
|
903
|
+
|
|
776
904
|
if (replacement !== undefined) {
|
|
777
905
|
field.divergent = true;
|
|
906
|
+
|
|
778
907
|
return { ...range, ...replacement };
|
|
779
908
|
}
|
|
909
|
+
|
|
780
910
|
if (this.detached !== null) {
|
|
781
911
|
return this.detached.map(
|
|
782
912
|
range,
|
|
@@ -789,8 +919,10 @@ export class SnippetFeature {
|
|
|
789
919
|
rangeHolds(range, target.range.end))
|
|
790
920
|
);
|
|
791
921
|
}
|
|
922
|
+
|
|
792
923
|
return mapRange(range, changes, (edit) => {
|
|
793
924
|
const owner = owners.get(edit);
|
|
925
|
+
|
|
794
926
|
return (
|
|
795
927
|
owner !== undefined &&
|
|
796
928
|
(field.index === index
|
|
@@ -800,7 +932,9 @@ export class SnippetFeature {
|
|
|
800
932
|
});
|
|
801
933
|
});
|
|
802
934
|
}
|
|
935
|
+
|
|
803
936
|
instance.fields = instance.fields.filter((field) => field.ranges.length > 0);
|
|
937
|
+
|
|
804
938
|
if (!instance.fields.some((field) => field.index === 0)) {
|
|
805
939
|
instance.fields.push({
|
|
806
940
|
index: 0,
|
|
@@ -809,36 +943,37 @@ export class SnippetFeature {
|
|
|
809
943
|
});
|
|
810
944
|
}
|
|
811
945
|
}
|
|
946
|
+
|
|
812
947
|
frame.order = frame.order.filter((index) => frame.instances.at(-1)?.fields.some((field) => field.index === index));
|
|
813
948
|
frame.current = frame.order.indexOf(index);
|
|
949
|
+
|
|
814
950
|
return frame.current !== -1;
|
|
815
951
|
}
|
|
816
952
|
|
|
817
953
|
private edited(change: EditorTextChange): void {
|
|
818
954
|
if (change.source !== 'input' && !this.applying) {
|
|
819
955
|
this.end();
|
|
956
|
+
|
|
820
957
|
return;
|
|
821
958
|
}
|
|
959
|
+
|
|
822
960
|
this.fieldTransforms.edited(change.changes, this.detached?.map);
|
|
823
961
|
this.placed = this.placed.map((range) => (this.detached === null ? mapRange(range, change.changes, true) : this.detached.map(range)));
|
|
962
|
+
|
|
824
963
|
if (!this.isActive) {
|
|
825
964
|
return;
|
|
826
965
|
}
|
|
966
|
+
|
|
827
967
|
const extras = new Set(
|
|
828
|
-
change.changes.filter((change) =>
|
|
829
|
-
this.extras.some(
|
|
830
|
-
(extra) =>
|
|
831
|
-
comparePositions(extra.range.start, change.range.start) === 0 &&
|
|
832
|
-
comparePositions(extra.range.end, change.range.end) === 0 &&
|
|
833
|
-
extra.text === change.text
|
|
834
|
-
)
|
|
835
|
-
)
|
|
968
|
+
change.changes.filter((change) => this.extras.some((extra) => sameRange(extra.range, change.range) && extra.text === change.text))
|
|
836
969
|
);
|
|
970
|
+
|
|
837
971
|
for (const frame of this.frames) {
|
|
838
972
|
if (!this.mapFrame(frame, change.changes, extras)) {
|
|
839
973
|
const caret = this.language.editor.getCaret();
|
|
840
974
|
this.end();
|
|
841
975
|
this.language.editor.setCaret(caret);
|
|
976
|
+
|
|
842
977
|
return;
|
|
843
978
|
}
|
|
844
979
|
}
|
|
@@ -846,20 +981,27 @@ export class SnippetFeature {
|
|
|
846
981
|
|
|
847
982
|
private moved(): void {
|
|
848
983
|
const frame = this.frames.at(-1);
|
|
984
|
+
|
|
849
985
|
if (frame === undefined || this.visiting || this.applying) {
|
|
850
986
|
return;
|
|
851
987
|
}
|
|
988
|
+
|
|
852
989
|
const { editor } = this.language;
|
|
990
|
+
|
|
853
991
|
if (editor.getSelections().length < frame.expectedSelections) {
|
|
854
992
|
this.end();
|
|
993
|
+
|
|
855
994
|
return;
|
|
856
995
|
}
|
|
996
|
+
|
|
857
997
|
const generation = this.generation;
|
|
998
|
+
|
|
858
999
|
// The caret event precedes text changes; wait until ranges have followed the edit.
|
|
859
1000
|
queueMicrotask(() => {
|
|
860
1001
|
if (generation !== this.generation) {
|
|
861
1002
|
return;
|
|
862
1003
|
}
|
|
1004
|
+
|
|
863
1005
|
while (this.frames.length > 0 && !rangeHolds(this.frames.at(-1)!.instances.at(-1)!.hull, editor.getCaret())) {
|
|
864
1006
|
this.pop();
|
|
865
1007
|
}
|