@adecore/editor-react 0.42.0 → 0.43.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.
Files changed (122) hide show
  1. package/dist/CallHierarchyPanel.js +177 -181
  2. package/dist/EditorContextMenu.js +118 -105
  3. package/dist/LanguagePopups.js +7 -0
  4. package/dist/PickPopup.js +190 -70
  5. package/dist/WrapCard.d.ts +8 -0
  6. package/dist/WrapCard.js +172 -0
  7. package/dist/call-hierarchy-model.d.ts +4 -2
  8. package/dist/call-hierarchy-model.js +20 -0
  9. package/dist/call-hierarchy.d.ts +3 -0
  10. package/dist/call-hierarchy.js +54 -22
  11. package/dist/clipboard-history.d.ts +31 -0
  12. package/dist/clipboard-history.js +70 -0
  13. package/dist/code-actions.d.ts +3 -1
  14. package/dist/code-actions.js +50 -10
  15. package/dist/code-vision.js +2 -3
  16. package/dist/completion.js +10 -15
  17. package/dist/context-menu-rows.d.ts +1 -1
  18. package/dist/context-menu-rows.js +4 -0
  19. package/dist/definition-link.js +15 -5
  20. package/dist/document-colors.d.ts +24 -0
  21. package/dist/document-colors.js +194 -0
  22. package/dist/document-links.d.ts +22 -0
  23. package/dist/document-links.js +133 -0
  24. package/dist/document-query.d.ts +15 -0
  25. package/dist/document-query.js +54 -0
  26. package/dist/edit-locations.d.ts +20 -0
  27. package/dist/edit-locations.js +136 -0
  28. package/dist/editor-language.d.ts +19 -1
  29. package/dist/editor-language.js +77 -2
  30. package/dist/emmet.d.ts +13 -0
  31. package/dist/emmet.js +62 -0
  32. package/dist/folding.js +2 -3
  33. package/dist/format-on-type.js +3 -5
  34. package/dist/highlights.js +2 -3
  35. package/dist/history.d.ts +7 -0
  36. package/dist/history.js +79 -1
  37. package/dist/host-types.d.ts +6 -0
  38. package/dist/index.d.ts +11 -2
  39. package/dist/index.js +8 -0
  40. package/dist/inlay-hints.js +2 -2
  41. package/dist/inline-completion.d.ts +39 -0
  42. package/dist/inline-completion.js +221 -0
  43. package/dist/key-priority.d.ts +8 -5
  44. package/dist/key-priority.js +9 -6
  45. package/dist/linked-editing.d.ts +26 -0
  46. package/dist/linked-editing.js +215 -0
  47. package/dist/locales/en.json +31 -2
  48. package/dist/locales/nl.json +31 -2
  49. package/dist/markup-context.d.ts +37 -0
  50. package/dist/markup-context.js +291 -0
  51. package/dist/markup.d.ts +36 -0
  52. package/dist/markup.js +453 -0
  53. package/dist/navigation.js +4 -6
  54. package/dist/paste-history.d.ts +7 -0
  55. package/dist/paste-history.js +104 -0
  56. package/dist/peek.js +3 -4
  57. package/dist/pick.d.ts +20 -2
  58. package/dist/pick.js +141 -18
  59. package/dist/popups.d.ts +9 -0
  60. package/dist/popups.js +2 -1
  61. package/dist/project-language.d.ts +18 -1
  62. package/dist/project-language.js +129 -18
  63. package/dist/rename.js +8 -11
  64. package/dist/rendered-docs.js +1 -2
  65. package/dist/selection-ranges.js +2 -3
  66. package/dist/shortcut-keys.d.ts +19 -0
  67. package/dist/signature.js +3 -6
  68. package/dist/snippet-session.d.ts +2 -2
  69. package/dist/snippet-session.js +3 -3
  70. package/dist/symbol-picker.js +4 -6
  71. package/dist/symbols.js +22 -3
  72. package/dist/testing.d.ts +23 -1
  73. package/dist/testing.js +114 -0
  74. package/package.json +7 -6
  75. package/src/CallHierarchyPanel.tsx +26 -11
  76. package/src/EditorContextMenu.tsx +7 -0
  77. package/src/LanguagePopups.tsx +4 -0
  78. package/src/PickPopup.tsx +40 -5
  79. package/src/WrapCard.tsx +64 -0
  80. package/src/call-hierarchy-model.ts +25 -3
  81. package/src/call-hierarchy.ts +62 -20
  82. package/src/clipboard-history.ts +102 -0
  83. package/src/code-actions.ts +48 -11
  84. package/src/code-vision.ts +3 -3
  85. package/src/completion.ts +15 -15
  86. package/src/context-menu-rows.ts +3 -2
  87. package/src/definition-link.ts +17 -5
  88. package/src/document-colors.ts +189 -0
  89. package/src/document-links.ts +129 -0
  90. package/src/document-query.ts +71 -0
  91. package/src/edit-locations.ts +162 -0
  92. package/src/editor-language.ts +80 -3
  93. package/src/emmet-module.d.ts +44 -0
  94. package/src/emmet.ts +73 -0
  95. package/src/folding.ts +3 -3
  96. package/src/format-on-type.ts +5 -5
  97. package/src/highlights.ts +3 -3
  98. package/src/history.ts +81 -1
  99. package/src/host-types.ts +10 -0
  100. package/src/index.ts +12 -3
  101. package/src/inlay-hints.ts +2 -2
  102. package/src/inline-completion.ts +221 -0
  103. package/src/key-priority.ts +9 -6
  104. package/src/linked-editing.ts +199 -0
  105. package/src/locales/en.json +31 -2
  106. package/src/locales/nl.json +31 -2
  107. package/src/markup-context.ts +262 -0
  108. package/src/markup.ts +367 -0
  109. package/src/navigation.ts +6 -6
  110. package/src/paste-history.ts +111 -0
  111. package/src/peek.ts +4 -4
  112. package/src/pick.ts +201 -19
  113. package/src/popups.ts +19 -1
  114. package/src/project-language.ts +136 -19
  115. package/src/rename.ts +11 -11
  116. package/src/rendered-docs.ts +2 -2
  117. package/src/selection-ranges.ts +3 -3
  118. package/src/signature.ts +6 -6
  119. package/src/snippet-session.ts +9 -4
  120. package/src/symbol-picker.ts +6 -6
  121. package/src/symbols.ts +30 -4
  122. package/src/testing.ts +126 -1
@@ -0,0 +1,111 @@
1
+ import { rememberCopy } from '@adecore/editor';
2
+ import type { ClipboardEntry } from './clipboard-history.ts';
3
+ import type { EditorLanguage } from './editor-language.ts';
4
+ import { KEY_PRIORITY } from './key-priority.ts';
5
+ import type { PickGroup } from './popups.ts';
6
+ import { isShortcut } from './shortcut-keys.ts';
7
+
8
+ const LABEL_LENGTH = 80;
9
+
10
+ /* The first characters of a copy on one line, with each line break shown as a return sign. */
11
+ function labelOf(text: string): string {
12
+ const flat = text.replace(/\r\n|\r|\n/g, '⏎');
13
+
14
+ return flat.length > LABEL_LENGTH ? `${flat.slice(0, LABEL_LENGTH)}…` : flat;
15
+ }
16
+
17
+ /*
18
+ * Paste from History: a list of what was copied in the editors of the project, newest first. Typing narrows it,
19
+ * Shift with the arrows chooses several entries, which paste as one, and Delete forgets entries.
20
+ */
21
+ export class PasteHistoryFeature {
22
+ private readonly language: EditorLanguage;
23
+
24
+ constructor(language: EditorLanguage) {
25
+ this.language = language;
26
+
27
+ const off = language.onKey((event) => {
28
+ if (!isShortcut(this.language.shortcuts.pasteFromHistory, event)) {
29
+ return false;
30
+ }
31
+
32
+ this.open();
33
+
34
+ return true;
35
+ }, KEY_PRIORITY.pasteHistory);
36
+
37
+ language.onDispose(off);
38
+ }
39
+
40
+ open(): void {
41
+ const { editor, project } = this.language;
42
+ const { clipboard } = project;
43
+
44
+ if (clipboard.entries.length === 0) {
45
+ editor.announce(project.i18n.t('editor:language.pasteHistory.empty'));
46
+
47
+ return;
48
+ }
49
+
50
+ const ids = new Map<ClipboardEntry, string>();
51
+ const entries = new Map<string, ClipboardEntry>();
52
+
53
+ const groups = (): readonly PickGroup[] => [
54
+ {
55
+ title: null,
56
+ rows: clipboard.entries.map((entry) => {
57
+ const id = ids.get(entry) ?? String(ids.size);
58
+
59
+ ids.set(entry, id);
60
+ entries.set(id, entry);
61
+
62
+ return { id, label: labelOf(entry.text), detail: '', search: entry.text };
63
+ })
64
+ }
65
+ ];
66
+
67
+ const rows = groups();
68
+
69
+ this.language.pick.open({
70
+ anchor: editor.getCaret(),
71
+ title: project.i18n.t('editor:language.pasteHistory.title'),
72
+ groups: rows,
73
+ active: rows[0]!.rows[0]!.id,
74
+ filter: true,
75
+ multiple: true,
76
+ accept: (id) => this.paste([entries.get(id)!]),
77
+ acceptMany: (chosen) => this.paste(clipboard.entries.filter((entry) => chosen.includes(ids.get(entry) ?? ''))),
78
+ preview: async (id) => ({ removed: [], added: entries.get(id)?.text.split('\n') ?? [], note: null, kind: 'text' }),
79
+ remove: (chosen) => {
80
+ for (const id of chosen) {
81
+ clipboard.remove(entries.get(id)!);
82
+ }
83
+
84
+ return groups();
85
+ }
86
+ });
87
+ }
88
+
89
+ /* Pastes entries in list order, joined by line breaks, as a paste of that text from the clipboard would. */
90
+ private paste(chosen: readonly ClipboardEntry[]): void {
91
+ const { editor, project } = this.language;
92
+ const { clipboard } = project;
93
+
94
+ if (chosen.length === 0) {
95
+ return;
96
+ }
97
+
98
+ const text = chosen.map((entry) => entry.text).join('\n');
99
+ const wholeLines = chosen.length === 1 && chosen[0]!.wholeLines;
100
+
101
+ if (chosen.length === 1) {
102
+ clipboard.promote(chosen[0]!);
103
+ } else {
104
+ clipboard.add(text);
105
+ }
106
+
107
+ globalThis.navigator?.clipboard?.writeText(text).catch(() => undefined);
108
+ rememberCopy(text, wholeLines);
109
+ editor.paste?.(text, { wholeLines });
110
+ }
111
+ }
package/src/peek.ts CHANGED
@@ -82,7 +82,7 @@ export class PeekFeature {
82
82
  const { editor, project, uri } = this.language;
83
83
  const method = KINDS[kind].method;
84
84
 
85
- if (!project.service.supports(method, uri)) {
85
+ if (!this.language.service.supports(method, uri)) {
86
86
  this.tell(this.say(KINDS[kind].unavailable));
87
87
 
88
88
  return;
@@ -160,14 +160,14 @@ export class PeekFeature {
160
160
  }
161
161
 
162
162
  private async fetch(kind: PeekKind, position: EditorPosition): Promise<{ locations: Location[]; targets: Map<string, Range> }> {
163
- const { project, uri } = this.language;
163
+ const { uri } = this.language;
164
164
 
165
165
  if (kind === 'references') {
166
166
  // Without the declaration, so the list holds what the usages count above a declaration counts.
167
- return { locations: (await project.service.references(uri, position, false)) ?? [], targets: new Map() };
167
+ return { locations: (await this.language.service.references(uri, position, false)) ?? [], targets: new Map() };
168
168
  }
169
169
 
170
- const places = uniquePlaces(await project.service.definition(uri, position));
170
+ const places = uniquePlaces(await this.language.service.definition(uri, position));
171
171
  const targets = new Map(places.flatMap(({ location, target }) => (target === null ? [] : [[placeKey(location), target] as const])));
172
172
 
173
173
  return { locations: places.map(({ location }) => location), targets };
package/src/pick.ts CHANGED
@@ -22,6 +22,17 @@ export interface PickSpec {
22
22
  attempt?(id: string): Promise<string | null>;
23
23
  /* What the active row would change, asked a moment after it became active. */
24
24
  preview?(id: string, signal: AbortSignal): Promise<PickPreview | null>;
25
+ /*
26
+ * Typing narrows the rows to those whose `search` (the label without one) contains the query, whatever the case, and
27
+ * the list stays open while a person types. Backspace edits the query.
28
+ */
29
+ readonly filter?: boolean;
30
+ /* Shift with the arrows or a press chooses a run of rows, and Mod with a press adds or takes away one. */
31
+ readonly multiple?: boolean;
32
+ /* Enter takes the chosen rows, or the active one when none is chosen; without it Enter calls `accept` with the active row. */
33
+ acceptMany?(ids: readonly string[]): void;
34
+ /* Delete takes the chosen rows, or the active one, out of the list, and so does Backspace when there is no query. It answers the rows that remain. */
35
+ remove?(ids: readonly string[]): readonly PickGroup[];
25
36
  }
26
37
 
27
38
  /*
@@ -33,8 +44,18 @@ export class PickFeature {
33
44
  private readonly language: EditorLanguage;
34
45
  private readonly timers: Timers;
35
46
  private spec: PickSpec | null = null;
47
+
48
+ /* The rows as the spec gave them, or as `remove` left them; `ids` are those a query lets through. */
49
+ private groups: readonly PickGroup[] = [];
50
+
36
51
  private ids: readonly string[] = [];
37
52
  private active = '';
53
+ private query = '';
54
+
55
+ /* The rows chosen as a run or one by one, in list order, and the row a run of them grows from. */
56
+ private chosen: readonly string[] = [];
57
+
58
+ private rangeAnchor: string | null = null;
38
59
  private previewTimer: unknown;
39
60
  private controller: AbortController | null = null;
40
61
 
@@ -78,6 +99,10 @@ export class PickFeature {
78
99
 
79
100
  this.spec = spec;
80
101
  this.refusals = new Map();
102
+ this.groups = spec.groups;
103
+ this.query = '';
104
+ this.chosen = [];
105
+ this.rangeAnchor = null;
81
106
  this.ids = ids;
82
107
  this.active = spec.active !== undefined && ids.includes(spec.active) ? spec.active : ids[0]!;
83
108
  this.publish(null);
@@ -96,19 +121,53 @@ export class PickFeature {
96
121
  this.language.popups.setState({ pick: null });
97
122
  }
98
123
 
99
- move(delta: number): void {
124
+ /* 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. */
125
+ move(delta: number, extend = false): void {
126
+ if (this.ids.length === 0) {
127
+ return;
128
+ }
129
+
100
130
  const index = this.ids.indexOf(this.active);
101
- this.activate(this.ids[Math.max(0, Math.min(this.ids.length - 1, index + delta))]!);
131
+ const id = this.ids[Math.max(0, Math.min(this.ids.length - 1, index + delta))]!;
132
+
133
+ if (extend && this.spec?.multiple === true) {
134
+ this.extendTo(id);
135
+
136
+ return;
137
+ }
138
+
139
+ this.settle(id);
140
+ }
141
+
142
+ /* Makes `id` the only row the list is on. */
143
+ private settle(id: string): void {
144
+ const hadChosen = this.chosen.length > 0;
145
+
146
+ this.chosen = [];
147
+ this.rangeAnchor = null;
148
+ this.activate(id, hadChosen);
102
149
  }
103
150
 
104
- /* Takes a row, by a click or Enter. */
105
- choose(id: string): void {
151
+ /* 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. */
152
+ choose(id: string, modifiers: { readonly toggle?: boolean; readonly range?: boolean } = {}): void {
106
153
  const spec = this.spec;
107
154
 
108
155
  if (spec === null || !this.ids.includes(id) || this.attempting) {
109
156
  return;
110
157
  }
111
158
 
159
+ if (spec.multiple === true && modifiers.range === true) {
160
+ this.extendTo(id);
161
+
162
+ return;
163
+ }
164
+
165
+ if (spec.multiple === true && modifiers.toggle === true) {
166
+ this.toggle(id);
167
+
168
+ return;
169
+ }
170
+
112
171
  if (spec.attempt === undefined) {
113
172
  this.close();
114
173
  spec.accept(id);
@@ -140,29 +199,124 @@ export class PickFeature {
140
199
  });
141
200
  }
142
201
 
143
- private activate(id: string): void {
144
- if (id === this.active) {
202
+ private activate(id: string, force = false): void {
203
+ const moved = id !== this.active;
204
+
205
+ this.active = id;
206
+
207
+ if (moved) {
208
+ this.controller?.abort();
209
+ this.schedulePreview();
210
+ }
211
+
212
+ if (moved || force) {
213
+ this.publish(null);
214
+ }
215
+ }
216
+
217
+ private extendTo(id: string): void {
218
+ const anchor = this.rangeAnchor ?? this.active;
219
+ const first = this.ids.indexOf(anchor);
220
+ const last = this.ids.indexOf(id);
221
+ const run = this.ids.slice(Math.min(first, last), Math.max(first, last) + 1);
222
+
223
+ this.rangeAnchor = anchor;
224
+ this.chosen = run.length > 1 ? run : [];
225
+ this.activate(id, true);
226
+ }
227
+
228
+ private toggle(id: string): void {
229
+ const held = this.chosen.length > 0 ? this.chosen : [this.active];
230
+ const next = held.includes(id) ? held.filter((other) => other !== id) : [...held, id];
231
+
232
+ this.chosen = this.ids.filter((other) => next.includes(other));
233
+ this.rangeAnchor = id;
234
+ this.activate(id, true);
235
+ }
236
+
237
+ /* Enter: the chosen rows, or the active one when none is chosen. */
238
+ private takeActive(): void {
239
+ const spec = this.spec;
240
+
241
+ if (spec?.acceptMany === undefined) {
242
+ this.choose(this.active);
243
+
145
244
  return;
146
245
  }
147
246
 
148
- this.active = id;
247
+ const ids = this.chosen.length > 0 ? this.chosen : [this.active];
248
+
249
+ if (this.active !== '' && !this.attempting) {
250
+ this.close();
251
+ spec.acceptMany(ids);
252
+ }
253
+ }
254
+
255
+ private visibleGroups(): readonly PickGroup[] {
256
+ const query = this.query.toLowerCase();
257
+
258
+ if (query === '') {
259
+ return this.groups;
260
+ }
261
+
262
+ return this.groups
263
+ .map((group) => ({ ...group, rows: group.rows.filter((row) => (row.search ?? row.label).toLowerCase().includes(query)) }))
264
+ .filter((group) => group.rows.length > 0);
265
+ }
266
+
267
+ /* The query changed: the rows it lets through, with the first of them active. */
268
+ private requery(query: string): void {
269
+ this.query = query;
270
+ this.ids = this.visibleGroups().flatMap((group) => group.rows.map((row) => row.id));
271
+ this.chosen = [];
272
+ this.rangeAnchor = null;
273
+ this.active = this.ids[0] ?? '';
274
+ this.controller?.abort();
275
+ this.publish(null);
276
+ this.schedulePreview();
277
+ }
278
+
279
+ /* Takes the chosen rows, or the active one, out of the list; the list closes when no row is left. */
280
+ private removeChosen(): void {
281
+ const spec = this.spec;
282
+
283
+ if (spec?.remove === undefined || this.active === '') {
284
+ return;
285
+ }
286
+
287
+ const removed = this.chosen.length > 0 ? this.chosen : [this.active];
288
+ const index = this.ids.indexOf(removed[0]!);
289
+ this.groups = spec.remove(removed);
290
+
291
+ if (this.groups.every((group) => group.rows.length === 0)) {
292
+ this.close();
293
+
294
+ return;
295
+ }
296
+
297
+ this.ids = this.visibleGroups().flatMap((group) => group.rows.map((row) => row.id));
298
+ this.chosen = [];
299
+ this.rangeAnchor = null;
300
+ this.active = this.ids[Math.min(index, this.ids.length - 1)] ?? '';
149
301
  this.controller?.abort();
150
302
  this.publish(null);
151
303
  this.schedulePreview();
152
304
  }
153
305
 
154
306
  private key(event: KeyboardEvent): boolean {
155
- if (this.spec === null || event.metaKey || event.ctrlKey || event.altKey) {
307
+ const spec = this.spec;
308
+
309
+ if (spec === null || event.metaKey || event.ctrlKey || event.altKey) {
156
310
  return false;
157
311
  }
158
312
 
159
313
  switch (event.key) {
160
314
  case 'ArrowDown':
161
- this.move(1);
315
+ this.move(1, event.shiftKey);
162
316
 
163
317
  return true;
164
318
  case 'ArrowUp':
165
- this.move(-1);
319
+ this.move(-1, event.shiftKey);
166
320
 
167
321
  return true;
168
322
  case 'PageDown':
@@ -174,20 +328,31 @@ export class PickFeature {
174
328
 
175
329
  return true;
176
330
  case 'Home':
177
- this.activate(this.ids[0]!);
178
-
179
- return true;
180
331
  case 'End':
181
- this.activate(this.ids[this.ids.length - 1]!);
332
+ if (this.ids.length > 0) {
333
+ this.settle(event.key === 'Home' ? this.ids[0]! : this.ids[this.ids.length - 1]!);
334
+ }
182
335
 
183
336
  return true;
184
337
  case 'Enter':
185
- this.choose(this.active);
338
+ this.takeActive();
186
339
 
187
340
  return true;
188
- case 'Tab': {
189
- const spec = this.spec;
341
+ case 'Delete':
342
+ this.removeChosen();
343
+
344
+ return spec.remove !== undefined;
345
+ case 'Backspace':
346
+ if (this.query !== '') {
347
+ this.requery(this.query.slice(0, -1));
190
348
 
349
+ return true;
350
+ }
351
+
352
+ this.removeChosen();
353
+
354
+ return spec.remove !== undefined || spec.filter === true;
355
+ case 'Tab': {
191
356
  if (spec.onTab !== undefined) {
192
357
  this.close();
193
358
  spec.onTab(event.shiftKey);
@@ -202,6 +367,12 @@ export class PickFeature {
202
367
 
203
368
  return true;
204
369
  default:
370
+ if (spec.filter === true && event.key.length === 1) {
371
+ this.requery(this.query + event.key);
372
+
373
+ return true;
374
+ }
375
+
205
376
  return false;
206
377
  }
207
378
  }
@@ -210,7 +381,7 @@ export class PickFeature {
210
381
  const spec = this.spec;
211
382
  this.timers.clear(this.previewTimer);
212
383
 
213
- if (spec?.preview === undefined) {
384
+ if (spec?.preview === undefined || this.active === '') {
214
385
  return;
215
386
  }
216
387
 
@@ -239,6 +410,17 @@ export class PickFeature {
239
410
 
240
411
  const refusal = this.refusals.get(this.active);
241
412
  const shown = refusal === undefined ? preview : { removed: [], added: [], note: null, refusal };
242
- this.language.popups.setState({ pick: { anchor: spec.anchor, groups: spec.groups, active: this.active, preview: shown, title: spec.title ?? null } });
413
+
414
+ this.language.popups.setState({
415
+ pick: {
416
+ anchor: spec.anchor,
417
+ groups: this.visibleGroups(),
418
+ active: this.active,
419
+ preview: shown,
420
+ title: spec.title ?? null,
421
+ query: spec.filter === true ? this.query : null,
422
+ chosen: this.chosen
423
+ }
424
+ });
243
425
  }
244
426
  }
package/src/popups.ts CHANGED
@@ -82,6 +82,8 @@ export interface PickRow {
82
82
  readonly detail: string;
83
83
  /* The detail is a path, which gives way from its start before the label gives way at all. */
84
84
  readonly path?: boolean;
85
+ /* What a typed query is matched against, when a list filters; the label without it. */
86
+ readonly search?: string;
85
87
  }
86
88
 
87
89
  export interface PickGroup {
@@ -97,15 +99,22 @@ export interface PickPreview {
97
99
  readonly note: string | null;
98
100
  /* Why the action cannot be taken here, which the server said; nothing would change. */
99
101
  readonly refusal?: string;
102
+ /* `diff` draws `removed` and `added` as changed lines, which is the default; `text` draws `added` as plain code, such as the content of a clipboard entry. */
103
+ readonly kind?: 'diff' | 'text';
100
104
  }
101
105
 
102
106
  /* A list under a character of the editor; Enter takes the active row, Escape closes it. */
103
107
  export interface PickView {
104
108
  readonly anchor: EditorPosition;
109
+ /* The rows a query lets through. */
105
110
  readonly groups: readonly PickGroup[];
106
111
  readonly active: string;
107
112
  readonly preview: PickPreview | null;
108
113
  readonly title: string | null;
114
+ /* What has been typed to narrow the rows; null for a list that does not filter. */
115
+ readonly query: string | null;
116
+ /* The rows chosen as a run or one by one, in list order; empty when none is, and then the active row is the choice. */
117
+ readonly chosen: readonly string[];
109
118
  }
110
119
 
111
120
  /* One file of a rename in the preview: the lines it changes. */
@@ -192,6 +201,13 @@ export interface AuthorsView {
192
201
  readonly openCommit: (() => void) | null;
193
202
  }
194
203
 
204
+ /* The field that asks for the abbreviation a selection is wrapped in. */
205
+ export interface WrapView {
206
+ readonly anchor: EditorPosition;
207
+ /* Why the last abbreviation did not expand; the field stays open for another. */
208
+ readonly error: string | null;
209
+ }
210
+
195
211
  export interface PopupState {
196
212
  readonly hover: HoverView | null;
197
213
  readonly completion: CompletionView | null;
@@ -203,6 +219,7 @@ export interface PopupState {
203
219
  readonly symbols: SymbolsView | null;
204
220
  readonly menu: MenuView | null;
205
221
  readonly authors: AuthorsView | null;
222
+ readonly wrap: WrapView | null;
206
223
  }
207
224
 
208
225
  export type PopupStore = StoreApi<PopupState>;
@@ -218,6 +235,7 @@ export function createPopupStore(): PopupStore {
218
235
  callHierarchy: null,
219
236
  symbols: null,
220
237
  menu: null,
221
- authors: null
238
+ authors: null,
239
+ wrap: null
222
240
  }));
223
241
  }