@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.ts
CHANGED
|
@@ -45,13 +45,16 @@ type Node =
|
|
|
45
45
|
| { kind: 'variable'; name: string; body?: Node[]; transform?: SnippetTransform };
|
|
46
46
|
|
|
47
47
|
export type SnippetVariableResolver = (name: string) => string | undefined;
|
|
48
|
+
|
|
48
49
|
export interface SnippetTemplate {
|
|
49
50
|
readonly source: string;
|
|
50
51
|
render(resolve?: SnippetVariableResolver, variableTransforms?: readonly string[]): ParsedSnippet;
|
|
51
52
|
}
|
|
53
|
+
|
|
52
54
|
type FieldNode = Extract<Node, { kind: 'field' }>;
|
|
53
55
|
|
|
54
56
|
export const MAX_SNIPPET_TEXT = 100_000;
|
|
57
|
+
|
|
55
58
|
const MAX_SNIPPET_NODES = 10_000;
|
|
56
59
|
const MAX_SNIPPET_WORK = 50_000;
|
|
57
60
|
const MAX_SNIPPET_RANGES = 1_000;
|
|
@@ -79,21 +82,26 @@ class SnippetParser {
|
|
|
79
82
|
if (++this.depth > MAX_SNIPPET_DEPTH) {
|
|
80
83
|
throw new SnippetLimitError();
|
|
81
84
|
}
|
|
85
|
+
|
|
82
86
|
const nodes: Node[] = [];
|
|
83
87
|
let text = '';
|
|
88
|
+
|
|
84
89
|
const flush = (): void => {
|
|
85
90
|
if (text !== '') {
|
|
86
91
|
this.push(nodes, { kind: 'text', text });
|
|
87
92
|
text = '';
|
|
88
93
|
}
|
|
89
94
|
};
|
|
95
|
+
|
|
90
96
|
while (this.at < this.source.length && !(nested && this.source[this.at] === '}')) {
|
|
91
97
|
const character = this.source[this.at]!;
|
|
98
|
+
|
|
92
99
|
if (character === '\\' && /[\\$}]/.test(this.source[this.at + 1] ?? '')) {
|
|
93
100
|
text += this.source[this.at + 1];
|
|
94
101
|
this.at += 2;
|
|
95
102
|
} else if (character === '$') {
|
|
96
103
|
const node = this.dollar();
|
|
104
|
+
|
|
97
105
|
if (node === null) {
|
|
98
106
|
text += '$';
|
|
99
107
|
} else {
|
|
@@ -105,8 +113,10 @@ class SnippetParser {
|
|
|
105
113
|
this.at++;
|
|
106
114
|
}
|
|
107
115
|
}
|
|
116
|
+
|
|
108
117
|
flush();
|
|
109
118
|
this.depth--;
|
|
119
|
+
|
|
110
120
|
return nodes;
|
|
111
121
|
}
|
|
112
122
|
|
|
@@ -114,93 +124,125 @@ class SnippetParser {
|
|
|
114
124
|
if (++this.nodes > MAX_SNIPPET_NODES) {
|
|
115
125
|
throw new SnippetLimitError();
|
|
116
126
|
}
|
|
127
|
+
|
|
117
128
|
nodes.push(node);
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
private dollar(): Node | null {
|
|
121
132
|
const start = this.at++;
|
|
122
133
|
const brace = this.source[this.at] === '{';
|
|
134
|
+
|
|
123
135
|
if (brace) {
|
|
124
136
|
this.at++;
|
|
125
137
|
}
|
|
138
|
+
|
|
126
139
|
this.name.lastIndex = this.at;
|
|
127
140
|
const name = this.name.exec(this.source)?.[0];
|
|
141
|
+
|
|
128
142
|
if (name === undefined) {
|
|
129
143
|
this.at = start + 1;
|
|
144
|
+
|
|
130
145
|
return null;
|
|
131
146
|
}
|
|
147
|
+
|
|
132
148
|
const index = /^\d+$/.test(name) ? Number(name) : null;
|
|
149
|
+
|
|
133
150
|
if (index !== null && !Number.isSafeInteger(index)) {
|
|
134
151
|
this.at = start + 1;
|
|
152
|
+
|
|
135
153
|
return null;
|
|
136
154
|
}
|
|
155
|
+
|
|
137
156
|
this.at += name.length;
|
|
157
|
+
|
|
138
158
|
const field = (): FieldNode => ({ kind: 'field', index: index! });
|
|
159
|
+
|
|
139
160
|
if (!brace) {
|
|
140
161
|
return index === null ? { kind: 'variable', name } : field();
|
|
141
162
|
}
|
|
163
|
+
|
|
142
164
|
const next = this.source[this.at];
|
|
165
|
+
|
|
143
166
|
if (next === '}') {
|
|
144
167
|
this.at++;
|
|
168
|
+
|
|
145
169
|
return index === null ? { kind: 'variable', name } : field();
|
|
146
170
|
}
|
|
171
|
+
|
|
147
172
|
if (next === ':') {
|
|
148
173
|
this.at++;
|
|
149
174
|
const body = this.sequence(true);
|
|
175
|
+
|
|
150
176
|
if (this.source[this.at] === '}') {
|
|
151
177
|
this.at++;
|
|
152
178
|
}
|
|
179
|
+
|
|
153
180
|
return index === null ? { kind: 'variable', name, body } : { ...field(), body };
|
|
154
181
|
}
|
|
182
|
+
|
|
155
183
|
if (next === '|' && index !== null) {
|
|
156
184
|
this.at++;
|
|
185
|
+
|
|
157
186
|
return { ...field(), choices: this.choices() };
|
|
158
187
|
}
|
|
188
|
+
|
|
159
189
|
if (next === '/') {
|
|
160
190
|
try {
|
|
161
191
|
const { end, ...transform } = scanSnippetTransform(this.source, this.at);
|
|
162
192
|
this.at = end;
|
|
193
|
+
|
|
163
194
|
return index === null ? { kind: 'variable', name, transform } : { ...field(), transform };
|
|
164
195
|
} catch (error) {
|
|
165
196
|
if (error instanceof RangeError) {
|
|
166
197
|
throw new SnippetLimitError();
|
|
167
198
|
}
|
|
199
|
+
|
|
168
200
|
if (error instanceof SyntaxError) {
|
|
169
201
|
throw new MalformedSnippetError();
|
|
170
202
|
}
|
|
203
|
+
|
|
171
204
|
throw error;
|
|
172
205
|
}
|
|
173
206
|
}
|
|
207
|
+
|
|
174
208
|
this.at = start + 1;
|
|
209
|
+
|
|
175
210
|
return null;
|
|
176
211
|
}
|
|
177
212
|
|
|
178
213
|
private choices(): string[] {
|
|
179
214
|
const values = [''];
|
|
215
|
+
|
|
180
216
|
while (this.at < this.source.length && !(this.source[this.at] === '|' && this.source[this.at + 1] === '}')) {
|
|
181
217
|
const character = this.source[this.at++]!;
|
|
218
|
+
|
|
182
219
|
if (character === '\\' && /[\\,|]/.test(this.source[this.at] ?? '')) {
|
|
183
220
|
values[values.length - 1] += this.source[this.at++]!;
|
|
184
221
|
} else if (character === ',') {
|
|
185
222
|
if (values.length >= MAX_SNIPPET_NODES) {
|
|
186
223
|
throw new SnippetLimitError();
|
|
187
224
|
}
|
|
225
|
+
|
|
188
226
|
values.push('');
|
|
189
227
|
} else {
|
|
190
228
|
values[values.length - 1] += character;
|
|
191
229
|
}
|
|
192
230
|
}
|
|
231
|
+
|
|
193
232
|
this.at = Math.min(this.source.length, this.at + 2);
|
|
233
|
+
|
|
194
234
|
return values;
|
|
195
235
|
}
|
|
196
236
|
}
|
|
197
237
|
|
|
198
238
|
function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly string[]): ParsedSnippet {
|
|
199
239
|
const definitions = new Map<number, FieldNode>();
|
|
240
|
+
|
|
200
241
|
const collect = (nodes: readonly Node[]): void => {
|
|
201
242
|
for (const node of nodes) {
|
|
202
243
|
if (node.kind === 'field') {
|
|
203
244
|
const known = definitions.get(node.index);
|
|
245
|
+
|
|
204
246
|
if (
|
|
205
247
|
known === undefined ||
|
|
206
248
|
(known.body === undefined &&
|
|
@@ -210,16 +252,20 @@ function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly str
|
|
|
210
252
|
definitions.set(node.index, node);
|
|
211
253
|
}
|
|
212
254
|
}
|
|
255
|
+
|
|
213
256
|
if (node.kind !== 'text' && node.body !== undefined) {
|
|
214
257
|
collect(node.body);
|
|
215
258
|
}
|
|
216
259
|
}
|
|
217
260
|
};
|
|
261
|
+
|
|
218
262
|
collect(nodes);
|
|
263
|
+
|
|
219
264
|
const fields = new Map<
|
|
220
265
|
number,
|
|
221
266
|
{ index: number; ranges: SnippetRange[]; linked: SnippetRange[]; primary: number; parents: Set<number>; choices?: string[] }
|
|
222
267
|
>();
|
|
268
|
+
|
|
223
269
|
const stops: SnippetStop[] = [];
|
|
224
270
|
const transforms: (SnippetRange & { transform: SnippetTransform })[] = [];
|
|
225
271
|
let text = '';
|
|
@@ -228,55 +274,72 @@ function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly str
|
|
|
228
274
|
let occurrence = 0;
|
|
229
275
|
let tailEvent = 0;
|
|
230
276
|
let tailOffset = -1;
|
|
277
|
+
|
|
231
278
|
const recordTail = (range: SnippetRange): void => {
|
|
232
279
|
if (range.start === range.end || range.tail === true) {
|
|
233
280
|
tailEvent++;
|
|
234
281
|
tailOffset = range.end;
|
|
235
282
|
}
|
|
236
283
|
};
|
|
284
|
+
|
|
237
285
|
const append = (value: string): void => {
|
|
238
286
|
if (text.length + value.length > MAX_SNIPPET_TEXT) {
|
|
239
287
|
throw new SnippetLimitError();
|
|
240
288
|
}
|
|
289
|
+
|
|
241
290
|
text += value;
|
|
242
291
|
};
|
|
292
|
+
|
|
243
293
|
const render = (nodes: readonly Node[], linked: boolean, parents: readonly number[], visiting: ReadonlySet<number>): void => {
|
|
244
294
|
if (parents.length > MAX_SNIPPET_DEPTH) {
|
|
245
295
|
throw new SnippetLimitError();
|
|
246
296
|
}
|
|
297
|
+
|
|
247
298
|
for (const node of nodes) {
|
|
248
299
|
if (++work > MAX_SNIPPET_WORK) {
|
|
249
300
|
throw new SnippetLimitError();
|
|
250
301
|
}
|
|
302
|
+
|
|
251
303
|
const order = occurrence++;
|
|
304
|
+
|
|
252
305
|
if (node.kind === 'text') {
|
|
253
306
|
const start = text.length;
|
|
254
307
|
const value = node.transform === undefined || variableTransforms === undefined ? node.text : variableTransforms[transforms.length];
|
|
308
|
+
|
|
255
309
|
if (typeof value !== 'string') {
|
|
256
310
|
throw new RangeError('The variable transform count does not match the snippet.');
|
|
257
311
|
}
|
|
312
|
+
|
|
258
313
|
append(value);
|
|
314
|
+
|
|
259
315
|
if (node.transform !== undefined) {
|
|
260
316
|
if (++ranges > MAX_SNIPPET_RANGES) {
|
|
261
317
|
throw new SnippetLimitError();
|
|
262
318
|
}
|
|
319
|
+
|
|
263
320
|
const range = { start, end: text.length, transform: node.transform, ...(parents.length === 0 ? {} : { parents: [...parents] }) };
|
|
264
321
|
withSnippetOrder(range, order);
|
|
265
322
|
transforms.push(range);
|
|
266
323
|
recordTail(range);
|
|
267
324
|
}
|
|
325
|
+
|
|
268
326
|
continue;
|
|
269
327
|
}
|
|
328
|
+
|
|
270
329
|
if (node.kind === 'variable') {
|
|
271
330
|
render(node.body ?? [], linked, parents, visiting);
|
|
331
|
+
|
|
272
332
|
continue;
|
|
273
333
|
}
|
|
334
|
+
|
|
274
335
|
if (++ranges > MAX_SNIPPET_RANGES) {
|
|
275
336
|
throw new SnippetLimitError();
|
|
276
337
|
}
|
|
338
|
+
|
|
277
339
|
const definition = definitions.get(node.index)!;
|
|
278
340
|
const from = text.length;
|
|
279
341
|
const beforeTail = tailEvent;
|
|
342
|
+
|
|
280
343
|
if (!visiting.has(node.index)) {
|
|
281
344
|
if (definition.choices !== undefined) {
|
|
282
345
|
append(definition.choices[0] ?? '');
|
|
@@ -284,6 +347,7 @@ function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly str
|
|
|
284
347
|
render(definition.body ?? [], linked || node !== definition, [...parents, node.index], new Set([...visiting, node.index]));
|
|
285
348
|
}
|
|
286
349
|
}
|
|
350
|
+
|
|
287
351
|
const range: SnippetRange = {
|
|
288
352
|
start: from,
|
|
289
353
|
end: text.length,
|
|
@@ -291,8 +355,10 @@ function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly str
|
|
|
291
355
|
...(node.transform === undefined ? {} : { transform: node.transform }),
|
|
292
356
|
...(tailEvent !== beforeTail && tailOffset === text.length ? { tail: true as const } : {})
|
|
293
357
|
};
|
|
358
|
+
|
|
294
359
|
withSnippetOrder(range, order);
|
|
295
360
|
recordTail(range);
|
|
361
|
+
|
|
296
362
|
const field = fields.get(node.index) ?? {
|
|
297
363
|
index: node.index,
|
|
298
364
|
ranges: [],
|
|
@@ -301,14 +367,18 @@ function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly str
|
|
|
301
367
|
parents: new Set<number>(),
|
|
302
368
|
choices: definition.choices
|
|
303
369
|
};
|
|
370
|
+
|
|
304
371
|
parents.filter((parent) => parent !== node.index).forEach((parent) => field.parents.add(parent));
|
|
372
|
+
|
|
305
373
|
if (linked) {
|
|
306
374
|
field.linked.push(range);
|
|
307
375
|
} else {
|
|
308
376
|
if (node === definition) {
|
|
309
377
|
field.primary = field.ranges.length;
|
|
310
378
|
}
|
|
379
|
+
|
|
311
380
|
field.ranges.push(range);
|
|
381
|
+
|
|
312
382
|
stops.push(
|
|
313
383
|
withSnippetOrder(
|
|
314
384
|
{
|
|
@@ -322,14 +392,19 @@ function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly str
|
|
|
322
392
|
)
|
|
323
393
|
);
|
|
324
394
|
}
|
|
395
|
+
|
|
325
396
|
fields.set(node.index, field);
|
|
326
397
|
}
|
|
327
398
|
};
|
|
399
|
+
|
|
328
400
|
render(nodes, false, [], new Set());
|
|
401
|
+
|
|
329
402
|
if (variableTransforms !== undefined && variableTransforms.length !== transforms.length) {
|
|
330
403
|
throw new RangeError('The variable transform count does not match the snippet.');
|
|
331
404
|
}
|
|
405
|
+
|
|
332
406
|
stops.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
407
|
+
|
|
333
408
|
return {
|
|
334
409
|
text,
|
|
335
410
|
stops,
|
|
@@ -350,6 +425,7 @@ function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly str
|
|
|
350
425
|
function variableIndices(nodes: readonly Node[]): ReadonlyMap<string, number> {
|
|
351
426
|
let highest = 0;
|
|
352
427
|
const names = new Set<string>();
|
|
428
|
+
|
|
353
429
|
const scan = (nodes: readonly Node[]): void => {
|
|
354
430
|
for (const node of nodes) {
|
|
355
431
|
if (node.kind === 'field') {
|
|
@@ -357,17 +433,21 @@ function variableIndices(nodes: readonly Node[]): ReadonlyMap<string, number> {
|
|
|
357
433
|
} else if (node.kind === 'variable' && !SNIPPET_VARIABLES.has(node.name)) {
|
|
358
434
|
names.add(node.name);
|
|
359
435
|
}
|
|
436
|
+
|
|
360
437
|
if (node.kind !== 'text' && node.body !== undefined) {
|
|
361
438
|
scan(node.body);
|
|
362
439
|
}
|
|
363
440
|
}
|
|
364
441
|
};
|
|
442
|
+
|
|
365
443
|
scan(nodes);
|
|
444
|
+
|
|
366
445
|
return new Map(
|
|
367
446
|
[...names].map((name) => {
|
|
368
447
|
if (!Number.isSafeInteger(++highest)) {
|
|
369
448
|
throw new SnippetLimitError();
|
|
370
449
|
}
|
|
450
|
+
|
|
371
451
|
return [name, highest];
|
|
372
452
|
})
|
|
373
453
|
);
|
|
@@ -376,16 +456,20 @@ function variableIndices(nodes: readonly Node[]): ReadonlyMap<string, number> {
|
|
|
376
456
|
function resolveVariables(nodes: readonly Node[], indices: ReadonlyMap<string, number>, resolve?: SnippetVariableResolver): Node[] {
|
|
377
457
|
const unknown = new Map<string, { index: number; body: Node[]; defaulted: boolean; definition: Node }>();
|
|
378
458
|
const values = new Map<string, string | undefined>();
|
|
459
|
+
|
|
379
460
|
const valueOf = (name: string): string | undefined => {
|
|
380
461
|
if (!values.has(name)) {
|
|
381
462
|
values.set(name, resolve?.(name));
|
|
382
463
|
}
|
|
464
|
+
|
|
383
465
|
return values.get(name);
|
|
384
466
|
};
|
|
467
|
+
|
|
385
468
|
const collect = (nodes: readonly Node[]): void => {
|
|
386
469
|
for (const node of nodes) {
|
|
387
470
|
if (node.kind === 'variable' && valueOf(node.name) === undefined && !SNIPPET_VARIABLES.has(node.name)) {
|
|
388
471
|
const previous = unknown.get(node.name);
|
|
472
|
+
|
|
389
473
|
if (previous === undefined) {
|
|
390
474
|
unknown.set(node.name, {
|
|
391
475
|
index: indices.get(node.name)!,
|
|
@@ -401,34 +485,44 @@ function resolveVariables(nodes: readonly Node[], indices: ReadonlyMap<string, n
|
|
|
401
485
|
previous.definition = node;
|
|
402
486
|
}
|
|
403
487
|
}
|
|
488
|
+
|
|
404
489
|
if (node.kind !== 'text' && node.body !== undefined) {
|
|
405
490
|
collect(node.body);
|
|
406
491
|
}
|
|
407
492
|
}
|
|
408
493
|
};
|
|
494
|
+
|
|
409
495
|
collect(nodes);
|
|
410
496
|
// Resolved empty variables retain their defaults without adding a navigable field.
|
|
411
497
|
let work = 0;
|
|
498
|
+
|
|
412
499
|
const normalize = (nodes: readonly Node[], visiting: ReadonlySet<string>, depth = 0): Node[] => {
|
|
413
500
|
if (depth > MAX_SNIPPET_DEPTH) {
|
|
414
501
|
throw new SnippetLimitError();
|
|
415
502
|
}
|
|
503
|
+
|
|
416
504
|
return nodes.flatMap((node): Node[] => {
|
|
417
505
|
if (++work > MAX_SNIPPET_WORK) {
|
|
418
506
|
throw new SnippetLimitError();
|
|
419
507
|
}
|
|
508
|
+
|
|
420
509
|
if (node.kind === 'variable' && !unknown.has(node.name)) {
|
|
421
510
|
const value = valueOf(node.name);
|
|
511
|
+
|
|
422
512
|
if (node.transform !== undefined) {
|
|
423
513
|
return [{ kind: 'text', text: value ?? '', transform: node.transform }];
|
|
424
514
|
}
|
|
515
|
+
|
|
425
516
|
return value !== undefined && value !== '' ? [{ kind: 'text', text: value }] : normalize(node.body ?? [], visiting, depth + 1);
|
|
426
517
|
}
|
|
518
|
+
|
|
427
519
|
if (node.kind === 'field') {
|
|
428
520
|
return [{ ...node, ...(node.body === undefined ? {} : { body: normalize(node.body, visiting, depth + 1) }) }];
|
|
429
521
|
}
|
|
522
|
+
|
|
430
523
|
if (node.kind === 'variable') {
|
|
431
524
|
const field = unknown.get(node.name)!;
|
|
525
|
+
|
|
432
526
|
return [
|
|
433
527
|
{
|
|
434
528
|
kind: 'field',
|
|
@@ -440,15 +534,18 @@ function resolveVariables(nodes: readonly Node[], indices: ReadonlyMap<string, n
|
|
|
440
534
|
}
|
|
441
535
|
];
|
|
442
536
|
}
|
|
537
|
+
|
|
443
538
|
return [node];
|
|
444
539
|
});
|
|
445
540
|
};
|
|
541
|
+
|
|
446
542
|
return normalize(nodes, new Set());
|
|
447
543
|
}
|
|
448
544
|
|
|
449
545
|
export function compileSnippet(source: string): SnippetTemplate {
|
|
450
546
|
let nodes: Node[] | null = null;
|
|
451
547
|
let indices: ReadonlyMap<string, number> = new Map();
|
|
548
|
+
|
|
452
549
|
if (source.length <= MAX_SNIPPET_TEXT) {
|
|
453
550
|
try {
|
|
454
551
|
nodes = new SnippetParser(source).parse();
|
|
@@ -457,24 +554,29 @@ export function compileSnippet(source: string): SnippetTemplate {
|
|
|
457
554
|
if (error instanceof MalformedSnippetError) {
|
|
458
555
|
return { source, render: () => ({ text: source, stops: [] }) };
|
|
459
556
|
}
|
|
557
|
+
|
|
460
558
|
if (!(error instanceof SnippetLimitError)) {
|
|
461
559
|
throw error;
|
|
462
560
|
}
|
|
561
|
+
|
|
463
562
|
nodes = null;
|
|
464
563
|
}
|
|
465
564
|
}
|
|
565
|
+
|
|
466
566
|
return {
|
|
467
567
|
source,
|
|
468
568
|
render: (resolve, variableTransforms) => {
|
|
469
569
|
if (nodes === null) {
|
|
470
570
|
return { text: source, stops: [], limited: true };
|
|
471
571
|
}
|
|
572
|
+
|
|
472
573
|
try {
|
|
473
574
|
return renderSnippet(resolveVariables(nodes, indices, resolve), variableTransforms);
|
|
474
575
|
} catch (error) {
|
|
475
576
|
if (!(error instanceof SnippetLimitError)) {
|
|
476
577
|
throw error;
|
|
477
578
|
}
|
|
579
|
+
|
|
478
580
|
// Language-server snippets may expand exponentially; retain the original text without a session.
|
|
479
581
|
return { text: source, stops: [], limited: true };
|
|
480
582
|
}
|
|
@@ -489,13 +591,16 @@ export function parseSnippet(snippet: string, resolve?: SnippetVariableResolver)
|
|
|
489
591
|
/* One defining occurrence per index, then $0 or the end of the inserted text. */
|
|
490
592
|
export function tabOrder(parsed: ParsedSnippet): SnippetStop[] {
|
|
491
593
|
const first = new Map<number, SnippetStop>();
|
|
594
|
+
|
|
492
595
|
for (const stop of parsed.stops) {
|
|
493
596
|
if (!first.has(stop.index)) {
|
|
494
597
|
first.set(stop.index, stop);
|
|
495
598
|
}
|
|
496
599
|
}
|
|
600
|
+
|
|
497
601
|
for (const field of parsed.fields ?? []) {
|
|
498
602
|
const range = field.ranges[field.primary];
|
|
603
|
+
|
|
499
604
|
if (range !== undefined) {
|
|
500
605
|
first.set(field.index, {
|
|
501
606
|
index: field.index,
|
|
@@ -505,6 +610,8 @@ export function tabOrder(parsed: ParsedSnippet): SnippetStop[] {
|
|
|
505
610
|
});
|
|
506
611
|
}
|
|
507
612
|
}
|
|
613
|
+
|
|
508
614
|
const visited = [...first.values()].filter((stop) => stop.index > 0).sort((left, right) => left.index - right.index);
|
|
615
|
+
|
|
509
616
|
return [...visited, first.get(0) ?? { index: 0, start: parsed.text.length, end: parsed.text.length }];
|
|
510
617
|
}
|
package/src/split-alignment.ts
CHANGED
|
@@ -10,36 +10,46 @@ export function splitAlignment(
|
|
|
10
10
|
): { old: HostedRow[]; new: HostedRow[] } {
|
|
11
11
|
const rows: { old: HostedRow[]; new: HostedRow[] } = { old: [], new: [] };
|
|
12
12
|
const previous = { old: -1, new: -1 };
|
|
13
|
+
|
|
13
14
|
const add = (side: 'old' | 'new', index: number, height: number, gap?: number): void => {
|
|
14
15
|
if (height <= 0) {
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
18
|
+
|
|
17
19
|
const line = Math.max(0, previous[side]);
|
|
18
20
|
const placement = previous[side] < 0 ? 'above' : 'below';
|
|
19
21
|
const last = rows[side].at(-1);
|
|
22
|
+
|
|
20
23
|
if (gap === undefined && last?.filler && last.line === line && last.placement === placement) {
|
|
21
24
|
last.height += height;
|
|
22
25
|
} else {
|
|
23
26
|
rows[side].push({ id: gap === undefined ? `spacer-${index}` : `gap-${gap}`, line, placement, height, fixed: true, filler: gap === undefined });
|
|
24
27
|
}
|
|
25
28
|
};
|
|
29
|
+
|
|
26
30
|
for (const [index, entry] of aligned.entries()) {
|
|
27
31
|
if (entry.kind === 'gap') {
|
|
28
32
|
add('old', index, gapHeight, entry.row.id);
|
|
29
33
|
add('new', index, gapHeight, entry.row.id);
|
|
34
|
+
|
|
30
35
|
continue;
|
|
31
36
|
}
|
|
37
|
+
|
|
32
38
|
const old = entry.old === undefined ? 0 : oldHeight(entry.old);
|
|
33
39
|
const fresh = entry.new === undefined ? 0 : newHeight(entry.new);
|
|
40
|
+
|
|
34
41
|
if (entry.old !== undefined) {
|
|
35
42
|
previous.old = entry.old;
|
|
36
43
|
}
|
|
44
|
+
|
|
37
45
|
if (entry.new !== undefined) {
|
|
38
46
|
previous.new = entry.new;
|
|
39
47
|
}
|
|
48
|
+
|
|
40
49
|
const target = Math.max(old, fresh);
|
|
41
50
|
add('old', index, target - old);
|
|
42
51
|
add('new', index, target - fresh);
|
|
43
52
|
}
|
|
53
|
+
|
|
44
54
|
return rows;
|
|
45
55
|
}
|
package/src/split-scroll.ts
CHANGED
|
@@ -23,14 +23,18 @@ export class SplitScroll {
|
|
|
23
23
|
if (this.syncing) {
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
+
|
|
26
27
|
const top = this.editors[side].getScrollTop();
|
|
28
|
+
|
|
27
29
|
if (side !== this.leader) {
|
|
28
30
|
// Native scroll echoes arrive after setScrollTop returns, including a clamped follower position.
|
|
29
31
|
if (Math.abs(top - this.expected[side]) < 1) {
|
|
30
32
|
return;
|
|
31
33
|
}
|
|
34
|
+
|
|
32
35
|
this.leader = side;
|
|
33
36
|
}
|
|
37
|
+
|
|
34
38
|
this.sync();
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -39,11 +43,15 @@ export class SplitScroll {
|
|
|
39
43
|
const other = side === 'old' ? 'new' : 'old';
|
|
40
44
|
const top = this.editors[side].getScrollTop();
|
|
41
45
|
this.expected[side] = top;
|
|
46
|
+
|
|
42
47
|
if (Math.abs(this.editors[other].getScrollTop() - top) < 1) {
|
|
43
48
|
this.expected[other] = this.editors[other].getScrollTop();
|
|
49
|
+
|
|
44
50
|
return;
|
|
45
51
|
}
|
|
52
|
+
|
|
46
53
|
this.syncing = true;
|
|
54
|
+
|
|
47
55
|
try {
|
|
48
56
|
this.editors[other].setScrollTop(top);
|
|
49
57
|
} finally {
|
package/src/symbol-links.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Location, SymbolInformation, WorkspaceSymbol, WorkspaceSymbolResult } from '@adecore/lsp';
|
|
2
|
+
import { uniqueBy } from './unique.ts';
|
|
2
3
|
|
|
3
4
|
/* The classes a name in a signature has: a pointer and an underline on hover, and the accent only then, so the signature stays calm. */
|
|
4
5
|
export const SYMBOL_LINK_CLASS = 'cursor-pointer hover:text-accent hover:underline';
|
|
@@ -46,27 +47,23 @@ export function placesOfName(result: WorkspaceSymbolResult, name: string, fileTe
|
|
|
46
47
|
const found: Found[] = ((result ?? []) as (SymbolInformation | WorkspaceSymbol)[]).filter((symbol) => symbol.name === name);
|
|
47
48
|
const types = found.filter((symbol) => TYPE_KINDS.has(symbol.kind));
|
|
48
49
|
let candidates = types.length > 0 ? types : found;
|
|
50
|
+
|
|
49
51
|
if (candidates.length > 1) {
|
|
50
52
|
const named = candidates.filter(
|
|
51
53
|
(symbol) => symbol.containerName !== undefined && symbol.containerName !== '' && fileText.includes(symbol.containerName)
|
|
52
54
|
);
|
|
55
|
+
|
|
53
56
|
if (named.length > 0) {
|
|
54
57
|
candidates = named;
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
|
-
|
|
58
|
-
return
|
|
59
|
-
.map((symbol): Location =>
|
|
60
|
+
|
|
61
|
+
return uniqueBy(
|
|
62
|
+
candidates.map((symbol): Location =>
|
|
60
63
|
'range' in symbol.location
|
|
61
64
|
? symbol.location
|
|
62
65
|
: { uri: symbol.location.uri, range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (seen.has(key)) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
seen.add(key);
|
|
70
|
-
return true;
|
|
71
|
-
});
|
|
66
|
+
),
|
|
67
|
+
(location) => `${location.uri}\0${location.range.start.line}`
|
|
68
|
+
);
|
|
72
69
|
}
|