@adecore/editor-react 0.42.0 → 0.44.0
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 +177 -181
- package/dist/EditorContextMenu.js +118 -105
- package/dist/LanguagePopups.js +7 -0
- package/dist/PickPopup.js +245 -104
- package/dist/WrapCard.d.ts +8 -0
- package/dist/WrapCard.js +172 -0
- package/dist/call-hierarchy-model.d.ts +4 -2
- package/dist/call-hierarchy-model.js +20 -0
- package/dist/call-hierarchy.d.ts +3 -0
- package/dist/call-hierarchy.js +54 -22
- package/dist/clipboard-history.d.ts +31 -0
- package/dist/clipboard-history.js +70 -0
- package/dist/code-actions.d.ts +3 -1
- package/dist/code-actions.js +50 -10
- package/dist/code-vision.js +2 -3
- package/dist/completion.d.ts +28 -0
- package/dist/completion.js +83 -27
- package/dist/context-menu-rows.d.ts +1 -1
- package/dist/context-menu-rows.js +4 -0
- package/dist/definition-link.js +15 -5
- package/dist/document-colors.d.ts +24 -0
- package/dist/document-colors.js +194 -0
- package/dist/document-links.d.ts +22 -0
- package/dist/document-links.js +133 -0
- package/dist/document-query.d.ts +15 -0
- package/dist/document-query.js +54 -0
- package/dist/edit-locations.d.ts +20 -0
- package/dist/edit-locations.js +136 -0
- package/dist/editor-language.d.ts +23 -1
- package/dist/editor-language.js +83 -2
- package/dist/emmet.d.ts +13 -0
- package/dist/emmet.js +62 -0
- package/dist/folding.js +2 -3
- package/dist/format-on-type.js +3 -5
- package/dist/highlights.js +2 -3
- package/dist/history.d.ts +7 -0
- package/dist/history.js +79 -1
- package/dist/host-types.d.ts +10 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +10 -0
- package/dist/inlay-hints.js +2 -2
- package/dist/inline-completion.d.ts +39 -0
- package/dist/inline-completion.js +221 -0
- package/dist/key-priority.d.ts +9 -5
- package/dist/key-priority.js +11 -6
- package/dist/linked-editing.d.ts +26 -0
- package/dist/linked-editing.js +215 -0
- package/dist/live-templates.d.ts +16 -0
- package/dist/live-templates.js +289 -0
- package/dist/locales/en.json +61 -2
- package/dist/locales/nl.json +61 -2
- package/dist/markup-context.d.ts +55 -0
- package/dist/markup-context.js +408 -0
- package/dist/markup.d.ts +39 -0
- package/dist/markup.js +465 -0
- package/dist/navigation.js +4 -6
- package/dist/paste-history.d.ts +7 -0
- package/dist/paste-history.js +104 -0
- package/dist/peek.js +3 -4
- package/dist/pick.d.ts +21 -2
- package/dist/pick.js +152 -19
- package/dist/popups.d.ts +10 -0
- package/dist/popups.js +2 -1
- package/dist/postfix.d.ts +21 -0
- package/dist/postfix.js +212 -0
- package/dist/project-language.d.ts +20 -1
- package/dist/project-language.js +132 -18
- package/dist/rename.js +8 -11
- package/dist/rendered-docs.js +1 -2
- package/dist/selection-ranges.js +2 -3
- package/dist/shortcut-keys.d.ts +24 -0
- package/dist/signature.js +3 -6
- package/dist/snippet-insert.d.ts +10 -0
- package/dist/snippet-insert.js +217 -0
- package/dist/snippet-session.d.ts +2 -2
- package/dist/snippet-session.js +3 -3
- package/dist/snippet.d.ts +7 -1
- package/dist/snippet.js +65 -8
- package/dist/surround-constructs.d.ts +10 -0
- package/dist/surround-constructs.js +100 -0
- package/dist/surround.d.ts +18 -0
- package/dist/surround.js +399 -0
- package/dist/symbol-picker.js +4 -6
- package/dist/symbols.js +22 -3
- package/dist/template-builtins.d.ts +3 -0
- package/dist/template-builtins.js +46 -0
- package/dist/template-context.d.ts +12 -0
- package/dist/template-context.js +56 -0
- package/dist/templates.d.ts +59 -0
- package/dist/templates.js +103 -0
- package/dist/testing.d.ts +23 -1
- package/dist/testing.js +114 -0
- package/package.json +7 -6
- package/src/CallHierarchyPanel.tsx +26 -11
- package/src/EditorContextMenu.tsx +7 -0
- package/src/LanguagePopups.tsx +4 -0
- package/src/PickPopup.tsx +47 -5
- package/src/WrapCard.tsx +64 -0
- package/src/call-hierarchy-model.ts +25 -3
- package/src/call-hierarchy.ts +62 -20
- package/src/clipboard-history.ts +102 -0
- package/src/code-actions.ts +48 -11
- package/src/code-vision.ts +3 -3
- package/src/completion.ts +123 -25
- package/src/context-menu-rows.ts +3 -2
- package/src/definition-link.ts +17 -5
- package/src/document-colors.ts +189 -0
- package/src/document-links.ts +129 -0
- package/src/document-query.ts +71 -0
- package/src/edit-locations.ts +162 -0
- package/src/editor-language.ts +86 -3
- package/src/emmet-module.d.ts +44 -0
- package/src/emmet.ts +73 -0
- package/src/folding.ts +3 -3
- package/src/format-on-type.ts +5 -5
- package/src/highlights.ts +3 -3
- package/src/history.ts +81 -1
- package/src/host-types.ts +17 -0
- package/src/index.ts +28 -3
- package/src/inlay-hints.ts +2 -2
- package/src/inline-completion.ts +221 -0
- package/src/key-priority.ts +11 -6
- package/src/linked-editing.ts +199 -0
- package/src/live-templates.ts +315 -0
- package/src/locales/en.json +61 -2
- package/src/locales/nl.json +61 -2
- package/src/markup-context.ts +387 -0
- package/src/markup.ts +377 -0
- package/src/navigation.ts +6 -6
- package/src/paste-history.ts +111 -0
- package/src/peek.ts +4 -4
- package/src/pick.ts +218 -20
- package/src/popups.ts +21 -1
- package/src/postfix.ts +224 -0
- package/src/project-language.ts +139 -19
- package/src/rename.ts +11 -11
- package/src/rendered-docs.ts +2 -2
- package/src/selection-ranges.ts +3 -3
- package/src/signature.ts +6 -6
- package/src/snippet-insert.ts +258 -0
- package/src/snippet-session.ts +9 -4
- package/src/snippet.ts +82 -7
- package/src/surround-constructs.ts +85 -0
- package/src/surround.ts +430 -0
- package/src/symbol-picker.ts +6 -6
- package/src/symbols.ts +30 -4
- package/src/template-builtins.ts +94 -0
- package/src/template-context.ts +65 -0
- package/src/templates.ts +197 -0
- package/src/testing.ts +126 -1
package/dist/pick.js
CHANGED
|
@@ -10,8 +10,14 @@ export class PickFeature {
|
|
|
10
10
|
language;
|
|
11
11
|
timers;
|
|
12
12
|
spec = null;
|
|
13
|
+
/* The rows as the spec gave them, or as `remove` left them; `ids` are those a query lets through. */
|
|
14
|
+
groups = [];
|
|
13
15
|
ids = [];
|
|
14
16
|
active = '';
|
|
17
|
+
query = '';
|
|
18
|
+
/* The rows chosen as a run or one by one, in list order, and the row a run of them grows from. */
|
|
19
|
+
chosen = [];
|
|
20
|
+
rangeAnchor = null;
|
|
15
21
|
previewTimer;
|
|
16
22
|
controller = null;
|
|
17
23
|
/* The rows that said why they cannot be taken, by id. */
|
|
@@ -42,6 +48,10 @@ export class PickFeature {
|
|
|
42
48
|
}
|
|
43
49
|
this.spec = spec;
|
|
44
50
|
this.refusals = new Map();
|
|
51
|
+
this.groups = spec.groups;
|
|
52
|
+
this.query = '';
|
|
53
|
+
this.chosen = [];
|
|
54
|
+
this.rangeAnchor = null;
|
|
45
55
|
this.ids = ids;
|
|
46
56
|
this.active = spec.active !== undefined && ids.includes(spec.active) ? spec.active : ids[0];
|
|
47
57
|
this.publish(null);
|
|
@@ -59,16 +69,40 @@ export class PickFeature {
|
|
|
59
69
|
pick: null
|
|
60
70
|
});
|
|
61
71
|
}
|
|
62
|
-
move
|
|
72
|
+
/* Moves the active row; with `extend`, in a list of several choices, the rows between where the move began and the new row are chosen. */
|
|
73
|
+
move(delta, extend = false) {
|
|
74
|
+
if (this.ids.length === 0) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
63
77
|
const index = this.ids.indexOf(this.active);
|
|
64
|
-
this.
|
|
78
|
+
const id = this.ids[Math.max(0, Math.min(this.ids.length - 1, index + delta))];
|
|
79
|
+
if (extend && this.spec?.multiple === true) {
|
|
80
|
+
this.extendTo(id);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.settle(id);
|
|
84
|
+
}
|
|
85
|
+
/* Makes `id` the only row the list is on. */
|
|
86
|
+
settle(id) {
|
|
87
|
+
const hadChosen = this.chosen.length > 0;
|
|
88
|
+
this.chosen = [];
|
|
89
|
+
this.rangeAnchor = null;
|
|
90
|
+
this.activate(id, hadChosen);
|
|
65
91
|
}
|
|
66
|
-
/* Takes a row, by a click or Enter. */
|
|
67
|
-
choose(id) {
|
|
92
|
+
/* Takes a row, by a click or Enter. In a list of several choices a press with Shift chooses the run up to the row, and one with Mod adds or takes away the row. */
|
|
93
|
+
choose(id, modifiers = {}) {
|
|
68
94
|
const spec = this.spec;
|
|
69
95
|
if (spec === null || !this.ids.includes(id) || this.attempting) {
|
|
70
96
|
return;
|
|
71
97
|
}
|
|
98
|
+
if (spec.multiple === true && modifiers.range === true) {
|
|
99
|
+
this.extendTo(id);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (spec.multiple === true && modifiers.toggle === true) {
|
|
103
|
+
this.toggle(id);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
72
106
|
if (spec.attempt === undefined) {
|
|
73
107
|
this.close();
|
|
74
108
|
spec.accept(id);
|
|
@@ -89,25 +123,99 @@ export class PickFeature {
|
|
|
89
123
|
this.publish(null);
|
|
90
124
|
});
|
|
91
125
|
}
|
|
92
|
-
activate(id) {
|
|
93
|
-
|
|
126
|
+
activate(id, force = false) {
|
|
127
|
+
const moved = id !== this.active;
|
|
128
|
+
this.active = id;
|
|
129
|
+
if (moved) {
|
|
130
|
+
this.controller?.abort();
|
|
131
|
+
this.schedulePreview();
|
|
132
|
+
}
|
|
133
|
+
if (moved || force) {
|
|
134
|
+
this.publish(null);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
extendTo(id) {
|
|
138
|
+
const anchor = this.rangeAnchor ?? this.active;
|
|
139
|
+
const first = this.ids.indexOf(anchor);
|
|
140
|
+
const last = this.ids.indexOf(id);
|
|
141
|
+
const run = this.ids.slice(Math.min(first, last), Math.max(first, last) + 1);
|
|
142
|
+
this.rangeAnchor = anchor;
|
|
143
|
+
this.chosen = run.length > 1 ? run : [];
|
|
144
|
+
this.activate(id, true);
|
|
145
|
+
}
|
|
146
|
+
toggle(id) {
|
|
147
|
+
const held = this.chosen.length > 0 ? this.chosen : [this.active];
|
|
148
|
+
const next = held.includes(id) ? held.filter(other => other !== id) : [...held, id];
|
|
149
|
+
this.chosen = this.ids.filter(other => next.includes(other));
|
|
150
|
+
this.rangeAnchor = id;
|
|
151
|
+
this.activate(id, true);
|
|
152
|
+
}
|
|
153
|
+
/* Enter: the chosen rows, or the active one when none is chosen. */
|
|
154
|
+
takeActive() {
|
|
155
|
+
const spec = this.spec;
|
|
156
|
+
if (spec?.acceptMany === undefined) {
|
|
157
|
+
this.choose(this.active);
|
|
94
158
|
return;
|
|
95
159
|
}
|
|
96
|
-
this.
|
|
160
|
+
const ids = this.chosen.length > 0 ? this.chosen : [this.active];
|
|
161
|
+
if (this.active !== '' && !this.attempting) {
|
|
162
|
+
this.close();
|
|
163
|
+
spec.acceptMany(ids);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
visibleGroups() {
|
|
167
|
+
const query = this.query.toLowerCase();
|
|
168
|
+
if (query === '') {
|
|
169
|
+
return this.groups;
|
|
170
|
+
}
|
|
171
|
+
return this.groups.map(group => ({
|
|
172
|
+
...group,
|
|
173
|
+
rows: group.rows.filter(row => (row.search ?? row.label).toLowerCase().includes(query))
|
|
174
|
+
})).filter(group => group.rows.length > 0);
|
|
175
|
+
}
|
|
176
|
+
/* The query changed: the rows it lets through, with the first of them active. */
|
|
177
|
+
requery(query) {
|
|
178
|
+
this.query = query;
|
|
179
|
+
this.ids = this.visibleGroups().flatMap(group => group.rows.map(row => row.id));
|
|
180
|
+
this.chosen = [];
|
|
181
|
+
this.rangeAnchor = null;
|
|
182
|
+
this.active = this.ids[0] ?? '';
|
|
183
|
+
this.controller?.abort();
|
|
184
|
+
this.publish(null);
|
|
185
|
+
this.schedulePreview();
|
|
186
|
+
}
|
|
187
|
+
/* Takes the chosen rows, or the active one, out of the list; the list closes when no row is left. */
|
|
188
|
+
removeChosen() {
|
|
189
|
+
const spec = this.spec;
|
|
190
|
+
if (spec?.remove === undefined || this.active === '') {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const removed = this.chosen.length > 0 ? this.chosen : [this.active];
|
|
194
|
+
const index = this.ids.indexOf(removed[0]);
|
|
195
|
+
this.groups = spec.remove(removed);
|
|
196
|
+
if (this.groups.every(group => group.rows.length === 0)) {
|
|
197
|
+
this.close();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.ids = this.visibleGroups().flatMap(group => group.rows.map(row => row.id));
|
|
201
|
+
this.chosen = [];
|
|
202
|
+
this.rangeAnchor = null;
|
|
203
|
+
this.active = this.ids[Math.min(index, this.ids.length - 1)] ?? '';
|
|
97
204
|
this.controller?.abort();
|
|
98
205
|
this.publish(null);
|
|
99
206
|
this.schedulePreview();
|
|
100
207
|
}
|
|
101
208
|
key(event) {
|
|
102
|
-
|
|
209
|
+
const spec = this.spec;
|
|
210
|
+
if (spec === null || event.metaKey || event.ctrlKey || event.altKey) {
|
|
103
211
|
return false;
|
|
104
212
|
}
|
|
105
213
|
switch (event.key) {
|
|
106
214
|
case 'ArrowDown':
|
|
107
|
-
this.move(1);
|
|
215
|
+
this.move(1, event.shiftKey);
|
|
108
216
|
return true;
|
|
109
217
|
case 'ArrowUp':
|
|
110
|
-
this.move(-1);
|
|
218
|
+
this.move(-1, event.shiftKey);
|
|
111
219
|
return true;
|
|
112
220
|
case 'PageDown':
|
|
113
221
|
this.move(8);
|
|
@@ -116,17 +224,26 @@ export class PickFeature {
|
|
|
116
224
|
this.move(-8);
|
|
117
225
|
return true;
|
|
118
226
|
case 'Home':
|
|
119
|
-
this.activate(this.ids[0]);
|
|
120
|
-
return true;
|
|
121
227
|
case 'End':
|
|
122
|
-
|
|
228
|
+
if (this.ids.length > 0) {
|
|
229
|
+
this.settle(event.key === 'Home' ? this.ids[0] : this.ids[this.ids.length - 1]);
|
|
230
|
+
}
|
|
123
231
|
return true;
|
|
124
232
|
case 'Enter':
|
|
125
|
-
this.
|
|
233
|
+
this.takeActive();
|
|
126
234
|
return true;
|
|
235
|
+
case 'Delete':
|
|
236
|
+
this.removeChosen();
|
|
237
|
+
return spec.remove !== undefined;
|
|
238
|
+
case 'Backspace':
|
|
239
|
+
if (this.query !== '') {
|
|
240
|
+
this.requery(this.query.slice(0, -1));
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
this.removeChosen();
|
|
244
|
+
return spec.remove !== undefined || spec.filter === true;
|
|
127
245
|
case 'Tab':
|
|
128
246
|
{
|
|
129
|
-
const spec = this.spec;
|
|
130
247
|
if (spec.onTab !== undefined) {
|
|
131
248
|
this.close();
|
|
132
249
|
spec.onTab(event.shiftKey);
|
|
@@ -139,13 +256,27 @@ export class PickFeature {
|
|
|
139
256
|
this.close();
|
|
140
257
|
return true;
|
|
141
258
|
default:
|
|
142
|
-
|
|
259
|
+
if (spec.filter === true && event.key.length === 1) {
|
|
260
|
+
this.requery(this.query + event.key);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
return spec.filter !== true && event.key.length === 1 && this.takeMnemonic(event.key);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/* Takes the visible row whose mnemonic is the typed character, whatever the case. */
|
|
267
|
+
takeMnemonic(character) {
|
|
268
|
+
const wanted = character.toLowerCase();
|
|
269
|
+
const row = this.visibleGroups().flatMap(group => group.rows).find(candidate => candidate.mnemonic?.toLowerCase() === wanted);
|
|
270
|
+
if (row === undefined) {
|
|
271
|
+
return false;
|
|
143
272
|
}
|
|
273
|
+
this.choose(row.id);
|
|
274
|
+
return true;
|
|
144
275
|
}
|
|
145
276
|
schedulePreview() {
|
|
146
277
|
const spec = this.spec;
|
|
147
278
|
this.timers.clear(this.previewTimer);
|
|
148
|
-
if (spec?.preview === undefined) {
|
|
279
|
+
if (spec?.preview === undefined || this.active === '') {
|
|
149
280
|
return;
|
|
150
281
|
}
|
|
151
282
|
const id = this.active;
|
|
@@ -174,10 +305,12 @@ export class PickFeature {
|
|
|
174
305
|
this.language.popups.setState({
|
|
175
306
|
pick: {
|
|
176
307
|
anchor: spec.anchor,
|
|
177
|
-
groups:
|
|
308
|
+
groups: this.visibleGroups(),
|
|
178
309
|
active: this.active,
|
|
179
310
|
preview: shown,
|
|
180
|
-
title: spec.title ?? null
|
|
311
|
+
title: spec.title ?? null,
|
|
312
|
+
query: spec.filter === true ? this.query : null,
|
|
313
|
+
chosen: this.chosen
|
|
181
314
|
}
|
|
182
315
|
});
|
|
183
316
|
}
|
package/dist/popups.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export interface PickRow {
|
|
|
54
54
|
readonly label: string;
|
|
55
55
|
readonly detail: string;
|
|
56
56
|
readonly path?: boolean;
|
|
57
|
+
readonly search?: string;
|
|
58
|
+
readonly mnemonic?: string;
|
|
57
59
|
}
|
|
58
60
|
export interface PickGroup {
|
|
59
61
|
readonly title: string | null;
|
|
@@ -64,6 +66,7 @@ export interface PickPreview {
|
|
|
64
66
|
readonly added: readonly string[];
|
|
65
67
|
readonly note: string | null;
|
|
66
68
|
readonly refusal?: string;
|
|
69
|
+
readonly kind?: 'diff' | 'text';
|
|
67
70
|
}
|
|
68
71
|
export interface PickView {
|
|
69
72
|
readonly anchor: EditorPosition;
|
|
@@ -71,6 +74,8 @@ export interface PickView {
|
|
|
71
74
|
readonly active: string;
|
|
72
75
|
readonly preview: PickPreview | null;
|
|
73
76
|
readonly title: string | null;
|
|
77
|
+
readonly query: string | null;
|
|
78
|
+
readonly chosen: readonly string[];
|
|
74
79
|
}
|
|
75
80
|
export interface RenameFileView {
|
|
76
81
|
readonly uri: string;
|
|
@@ -134,6 +139,10 @@ export interface AuthorsView {
|
|
|
134
139
|
readonly authorship: CodeAuthorship;
|
|
135
140
|
readonly openCommit: (() => void) | null;
|
|
136
141
|
}
|
|
142
|
+
export interface WrapView {
|
|
143
|
+
readonly anchor: EditorPosition;
|
|
144
|
+
readonly error: string | null;
|
|
145
|
+
}
|
|
137
146
|
export interface PopupState {
|
|
138
147
|
readonly hover: HoverView | null;
|
|
139
148
|
readonly completion: CompletionView | null;
|
|
@@ -145,6 +154,7 @@ export interface PopupState {
|
|
|
145
154
|
readonly symbols: SymbolsView | null;
|
|
146
155
|
readonly menu: MenuView | null;
|
|
147
156
|
readonly authors: AuthorsView | null;
|
|
157
|
+
readonly wrap: WrapView | null;
|
|
148
158
|
}
|
|
149
159
|
export type PopupStore = StoreApi<PopupState>;
|
|
150
160
|
export declare function createPopupStore(): PopupStore;
|
package/dist/popups.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { EditorPosition } from '@adecore/editor';
|
|
2
|
+
import type { LocalCompletionItem } from './completion.ts';
|
|
3
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
4
|
+
interface PostfixKey {
|
|
5
|
+
readonly start: EditorPosition;
|
|
6
|
+
expand(): boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class PostfixFeature {
|
|
9
|
+
private readonly language;
|
|
10
|
+
constructor(language: EditorLanguage);
|
|
11
|
+
key(): PostfixKey | null;
|
|
12
|
+
rows(position: EditorPosition): readonly LocalCompletionItem[];
|
|
13
|
+
private row;
|
|
14
|
+
private candidates;
|
|
15
|
+
private expandKey;
|
|
16
|
+
private expand;
|
|
17
|
+
private run;
|
|
18
|
+
private rendered;
|
|
19
|
+
private expressionOf;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
package/dist/postfix.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { isIdentifierCharacter } from './completion-model.js';
|
|
2
|
+
import { comparePositions } from './diagnostics-model.js';
|
|
3
|
+
import { lineTextOf, textBeforeOf } from './line-text.js';
|
|
4
|
+
import { bodyWithIndentUnit, dedentLines, indentUnitOf, insertSnippet } from './snippet-insert.js';
|
|
5
|
+
import { compileSnippet } from './snippet.js';
|
|
6
|
+
import { SNIPPET_KIND, sortAfterServer, templateContextAt } from './template-context.js';
|
|
7
|
+
const MAX_ROW_LENGTH = 80;
|
|
8
|
+
/* The identifier characters after the last dot of `before`, and where that dot is; null when no dot leads them. */
|
|
9
|
+
function typedKey(before) {
|
|
10
|
+
let index = before.length;
|
|
11
|
+
while (index > 0 && isIdentifierCharacter(before[index - 1])) {
|
|
12
|
+
index--;
|
|
13
|
+
}
|
|
14
|
+
return index > 0 && before[index - 1] === '.' ? {
|
|
15
|
+
key: before.slice(index),
|
|
16
|
+
dot: index - 1
|
|
17
|
+
} : null;
|
|
18
|
+
}
|
|
19
|
+
/* One line of a chooser row: whitespace collapsed and cut short. */
|
|
20
|
+
function rowLabel(text) {
|
|
21
|
+
const line = text.replace(/\s+/g, ' ').trim();
|
|
22
|
+
return line.length > MAX_ROW_LENGTH ? `${line.slice(0, MAX_ROW_LENGTH - 1)}…` : line;
|
|
23
|
+
}
|
|
24
|
+
/*
|
|
25
|
+
* Postfix completion: a dot and a key after an expression, and Tab or an accepted row wraps the expression in a
|
|
26
|
+
* template. The registry holds the templates and `TemplatesFeature` decides whether Tab is ours or a live template's.
|
|
27
|
+
*/
|
|
28
|
+
export class PostfixFeature {
|
|
29
|
+
language;
|
|
30
|
+
constructor(language) {
|
|
31
|
+
this.language = language;
|
|
32
|
+
}
|
|
33
|
+
/* The key before the caret that a template of the registry applies to; null where Tab has no postfix template to expand. */
|
|
34
|
+
key() {
|
|
35
|
+
const {
|
|
36
|
+
editor,
|
|
37
|
+
project
|
|
38
|
+
} = this.language;
|
|
39
|
+
if (!project.templates.settings.postfix) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const caret = editor.getCaret();
|
|
43
|
+
const typed = typedKey(textBeforeOf(editor, caret));
|
|
44
|
+
if (typed === null || typed.key === '') {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const candidates = this.candidates({
|
|
48
|
+
line: caret.line,
|
|
49
|
+
character: typed.dot
|
|
50
|
+
}, template => template.key === typed.key);
|
|
51
|
+
if (candidates === null) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
start: candidates.dot,
|
|
56
|
+
expand: () => this.expandKey(candidates, caret)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/* The rows of the suggestion list after a dot, for the templates whose key the typed characters can lead to. */
|
|
60
|
+
rows(position) {
|
|
61
|
+
const {
|
|
62
|
+
editor,
|
|
63
|
+
project
|
|
64
|
+
} = this.language;
|
|
65
|
+
const {
|
|
66
|
+
settings
|
|
67
|
+
} = project.templates;
|
|
68
|
+
const selections = editor.getSelections();
|
|
69
|
+
if (!settings.postfix || !settings.postfixInCompletion || selections.length !== 1 || comparePositions(selections[0].start, selections[0].end) !== 0) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const typed = typedKey(textBeforeOf(editor, position));
|
|
73
|
+
if (typed === null) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
const candidates = this.candidates({
|
|
77
|
+
line: position.line,
|
|
78
|
+
character: typed.dot
|
|
79
|
+
}, template => template.key.startsWith(typed.key));
|
|
80
|
+
if (candidates === null) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const range = {
|
|
84
|
+
start: {
|
|
85
|
+
line: position.line,
|
|
86
|
+
character: typed.dot + 1
|
|
87
|
+
},
|
|
88
|
+
end: position
|
|
89
|
+
};
|
|
90
|
+
return candidates.templates.map(template => this.row(template, candidates, range, sortAfterServer(template.key)));
|
|
91
|
+
}
|
|
92
|
+
row(template, candidates, range, sortText) {
|
|
93
|
+
const item = {
|
|
94
|
+
label: template.key,
|
|
95
|
+
kind: SNIPPET_KIND,
|
|
96
|
+
detail: template.example,
|
|
97
|
+
documentation: template.description,
|
|
98
|
+
filterText: template.key,
|
|
99
|
+
sortText,
|
|
100
|
+
textEdit: {
|
|
101
|
+
range,
|
|
102
|
+
newText: template.key
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
item,
|
|
107
|
+
accept: () => this.expand(template, candidates, this.language.editor.getCaret())
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/* The templates `matches` lets through that apply to the expressions before `dot`; null when none does. */
|
|
111
|
+
candidates(dot, matches) {
|
|
112
|
+
const {
|
|
113
|
+
editor,
|
|
114
|
+
project
|
|
115
|
+
} = this.language;
|
|
116
|
+
const operands = editor.postfixOperands?.(dot) ?? [];
|
|
117
|
+
if (operands.length === 0) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const context = templateContextAt(this.language, dot);
|
|
121
|
+
if (context.place !== 'code') {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const widest = operands.at(-1);
|
|
125
|
+
const templates = project.templates.postfix.filter(template => matches(template) && template.languages.some(family => context.families.includes(family)) && (template.expression !== 'statement' || widest.statement));
|
|
126
|
+
return templates.length === 0 ? null : {
|
|
127
|
+
dot,
|
|
128
|
+
operands,
|
|
129
|
+
templates
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/* Expands the one template of a key, or lists them when several share it. */
|
|
133
|
+
expandKey(candidates, caret) {
|
|
134
|
+
if (candidates.templates.length === 1) {
|
|
135
|
+
this.expand(candidates.templates[0], candidates, caret);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
const start = {
|
|
139
|
+
line: candidates.dot.line,
|
|
140
|
+
character: candidates.dot.character + 1
|
|
141
|
+
};
|
|
142
|
+
this.language.completion.showItems(candidates.templates.map(template => this.row(template, candidates, {
|
|
143
|
+
start,
|
|
144
|
+
end: caret
|
|
145
|
+
}, template.id)), start);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
/* Wraps the widest expression for a statement, the only one there is, or the one a person picks from the expressions. */
|
|
149
|
+
expand(template, candidates, caret) {
|
|
150
|
+
const {
|
|
151
|
+
operands
|
|
152
|
+
} = candidates;
|
|
153
|
+
if (template.expression === 'statement' || operands.length === 1) {
|
|
154
|
+
void this.run(template, template.expression === 'statement' ? operands.at(-1) : operands[0], caret);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const {
|
|
158
|
+
editor,
|
|
159
|
+
completion,
|
|
160
|
+
pick,
|
|
161
|
+
project
|
|
162
|
+
} = this.language;
|
|
163
|
+
const rows = operands.map((operand, index) => ({
|
|
164
|
+
id: String(index),
|
|
165
|
+
label: rowLabel(editor.textInRange(operand.range)),
|
|
166
|
+
detail: ''
|
|
167
|
+
}));
|
|
168
|
+
completion.close();
|
|
169
|
+
pick.open({
|
|
170
|
+
anchor: caret,
|
|
171
|
+
title: project.i18n.t('editor:language.postfix.expressions'),
|
|
172
|
+
groups: [{
|
|
173
|
+
title: null,
|
|
174
|
+
rows
|
|
175
|
+
}],
|
|
176
|
+
accept: id => void this.run(template, operands[Number(id)], caret),
|
|
177
|
+
preview: id => Promise.resolve({
|
|
178
|
+
removed: [],
|
|
179
|
+
added: this.rendered(template, operands[Number(id)]).split('\n'),
|
|
180
|
+
note: null,
|
|
181
|
+
kind: 'text'
|
|
182
|
+
})
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
run(template, operand, caret) {
|
|
186
|
+
const {
|
|
187
|
+
editor
|
|
188
|
+
} = this.language;
|
|
189
|
+
const expression = this.expressionOf(operand);
|
|
190
|
+
return insertSnippet(this.language, bodyWithIndentUnit(template.body, indentUnitOf(editor)), [{
|
|
191
|
+
range: {
|
|
192
|
+
start: operand.range.start,
|
|
193
|
+
end: caret
|
|
194
|
+
}
|
|
195
|
+
}], name => name === 'EXPR' ? expression : undefined);
|
|
196
|
+
}
|
|
197
|
+
/* What the template would put in place of the expression, tab stops at their placeholders. */
|
|
198
|
+
rendered(template, operand) {
|
|
199
|
+
const expression = this.expressionOf(operand);
|
|
200
|
+
return compileSnippet(bodyWithIndentUnit(template.body, indentUnitOf(this.language.editor))).render(name => name === 'EXPR' ? expression : undefined, undefined, {
|
|
201
|
+
indentVariables: true
|
|
202
|
+
}).text;
|
|
203
|
+
}
|
|
204
|
+
/* The text of an expression, with the indentation of its first line taken off the lines after it. */
|
|
205
|
+
expressionOf(operand) {
|
|
206
|
+
const {
|
|
207
|
+
editor
|
|
208
|
+
} = this.language;
|
|
209
|
+
const indent = /^[\t ]*/.exec(lineTextOf(editor, operand.range.start.line))[0];
|
|
210
|
+
return dedentLines(editor.textInRange(operand.range).trimEnd(), indent);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type i18n } from 'i18next';
|
|
2
2
|
import { type ApplyWorkspaceEditResult, type LanguageService, type WorkspaceEdit } from '@adecore/lsp';
|
|
3
3
|
import type { Editor, EditorPosition } from '@adecore/editor';
|
|
4
|
+
import { ClipboardHistory } from './clipboard-history.ts';
|
|
5
|
+
import { TemplateRegistry } from './templates.ts';
|
|
6
|
+
import { EditLocations } from './edit-locations.ts';
|
|
4
7
|
import { NavigationHistory, type Place } from './navigation-history.ts';
|
|
5
8
|
import { ProjectProblems } from './project-problems.ts';
|
|
6
9
|
import type { LanguageHost } from './host-types.ts';
|
|
@@ -10,6 +13,9 @@ export interface LanguageDocumentHandle {
|
|
|
10
13
|
readonly ready: Promise<void>;
|
|
11
14
|
release(): void;
|
|
12
15
|
}
|
|
16
|
+
export interface AcquireOptions {
|
|
17
|
+
readonly serve?: boolean;
|
|
18
|
+
}
|
|
13
19
|
export type FileOperationTarget = string | {
|
|
14
20
|
readonly path: string;
|
|
15
21
|
readonly directory?: boolean;
|
|
@@ -21,6 +27,9 @@ export declare class ProjectLanguage {
|
|
|
21
27
|
readonly folder: string;
|
|
22
28
|
readonly problems: ProjectProblems;
|
|
23
29
|
readonly history: NavigationHistory;
|
|
30
|
+
readonly edits: EditLocations;
|
|
31
|
+
readonly clipboard: ClipboardHistory;
|
|
32
|
+
readonly templates: TemplateRegistry;
|
|
24
33
|
private readonly holders;
|
|
25
34
|
private readonly files;
|
|
26
35
|
panelBodyHeight: number;
|
|
@@ -28,6 +37,8 @@ export declare class ProjectLanguage {
|
|
|
28
37
|
private pendingCaret;
|
|
29
38
|
private lastPlace;
|
|
30
39
|
private disposed;
|
|
40
|
+
private readonly subscriptions;
|
|
41
|
+
private readonly workspaceResults;
|
|
31
42
|
constructor(service: LanguageService, host?: LanguageHost);
|
|
32
43
|
onPanelBodyChange(listener: (height: number) => void): () => void;
|
|
33
44
|
resizePanelBody(height: number): void;
|
|
@@ -38,11 +49,19 @@ export declare class ProjectLanguage {
|
|
|
38
49
|
willDeleteFiles(targets: readonly FileOperationTarget[]): Promise<WorkspaceEdit | null>;
|
|
39
50
|
didDeleteFiles(targets: readonly FileOperationTarget[]): Promise<void>;
|
|
40
51
|
readText(uri: string): Promise<string | null>;
|
|
52
|
+
pullWorkspaceProblems(uri: string, options?: {
|
|
53
|
+
readonly signal?: AbortSignal;
|
|
54
|
+
}): Promise<boolean>;
|
|
55
|
+
private showDocument;
|
|
41
56
|
openPlace(place: Place): void;
|
|
42
57
|
noteCaret(place: Place): void;
|
|
43
58
|
jumpTo(place: Place): void;
|
|
44
59
|
takeCaret(uri: string, line: number): EditorPosition | null;
|
|
45
|
-
|
|
60
|
+
openEditors(): readonly {
|
|
61
|
+
uri: string;
|
|
62
|
+
editor: Editor;
|
|
63
|
+
}[];
|
|
64
|
+
acquire(uri: string, languageId: string, editor: Editor, options?: AcquireOptions): LanguageDocumentHandle;
|
|
46
65
|
private release;
|
|
47
66
|
dispose(): void;
|
|
48
67
|
}
|