@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.
Files changed (150) 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 +245 -104
  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.d.ts +28 -0
  17. package/dist/completion.js +83 -27
  18. package/dist/context-menu-rows.d.ts +1 -1
  19. package/dist/context-menu-rows.js +4 -0
  20. package/dist/definition-link.js +15 -5
  21. package/dist/document-colors.d.ts +24 -0
  22. package/dist/document-colors.js +194 -0
  23. package/dist/document-links.d.ts +22 -0
  24. package/dist/document-links.js +133 -0
  25. package/dist/document-query.d.ts +15 -0
  26. package/dist/document-query.js +54 -0
  27. package/dist/edit-locations.d.ts +20 -0
  28. package/dist/edit-locations.js +136 -0
  29. package/dist/editor-language.d.ts +23 -1
  30. package/dist/editor-language.js +83 -2
  31. package/dist/emmet.d.ts +13 -0
  32. package/dist/emmet.js +62 -0
  33. package/dist/folding.js +2 -3
  34. package/dist/format-on-type.js +3 -5
  35. package/dist/highlights.js +2 -3
  36. package/dist/history.d.ts +7 -0
  37. package/dist/history.js +79 -1
  38. package/dist/host-types.d.ts +10 -0
  39. package/dist/index.d.ts +13 -2
  40. package/dist/index.js +10 -0
  41. package/dist/inlay-hints.js +2 -2
  42. package/dist/inline-completion.d.ts +39 -0
  43. package/dist/inline-completion.js +221 -0
  44. package/dist/key-priority.d.ts +9 -5
  45. package/dist/key-priority.js +11 -6
  46. package/dist/linked-editing.d.ts +26 -0
  47. package/dist/linked-editing.js +215 -0
  48. package/dist/live-templates.d.ts +16 -0
  49. package/dist/live-templates.js +289 -0
  50. package/dist/locales/en.json +61 -2
  51. package/dist/locales/nl.json +61 -2
  52. package/dist/markup-context.d.ts +55 -0
  53. package/dist/markup-context.js +408 -0
  54. package/dist/markup.d.ts +39 -0
  55. package/dist/markup.js +465 -0
  56. package/dist/navigation.js +4 -6
  57. package/dist/paste-history.d.ts +7 -0
  58. package/dist/paste-history.js +104 -0
  59. package/dist/peek.js +3 -4
  60. package/dist/pick.d.ts +21 -2
  61. package/dist/pick.js +152 -19
  62. package/dist/popups.d.ts +10 -0
  63. package/dist/popups.js +2 -1
  64. package/dist/postfix.d.ts +21 -0
  65. package/dist/postfix.js +212 -0
  66. package/dist/project-language.d.ts +20 -1
  67. package/dist/project-language.js +132 -18
  68. package/dist/rename.js +8 -11
  69. package/dist/rendered-docs.js +1 -2
  70. package/dist/selection-ranges.js +2 -3
  71. package/dist/shortcut-keys.d.ts +24 -0
  72. package/dist/signature.js +3 -6
  73. package/dist/snippet-insert.d.ts +10 -0
  74. package/dist/snippet-insert.js +217 -0
  75. package/dist/snippet-session.d.ts +2 -2
  76. package/dist/snippet-session.js +3 -3
  77. package/dist/snippet.d.ts +7 -1
  78. package/dist/snippet.js +65 -8
  79. package/dist/surround-constructs.d.ts +10 -0
  80. package/dist/surround-constructs.js +100 -0
  81. package/dist/surround.d.ts +18 -0
  82. package/dist/surround.js +399 -0
  83. package/dist/symbol-picker.js +4 -6
  84. package/dist/symbols.js +22 -3
  85. package/dist/template-builtins.d.ts +3 -0
  86. package/dist/template-builtins.js +46 -0
  87. package/dist/template-context.d.ts +12 -0
  88. package/dist/template-context.js +56 -0
  89. package/dist/templates.d.ts +59 -0
  90. package/dist/templates.js +103 -0
  91. package/dist/testing.d.ts +23 -1
  92. package/dist/testing.js +114 -0
  93. package/package.json +7 -6
  94. package/src/CallHierarchyPanel.tsx +26 -11
  95. package/src/EditorContextMenu.tsx +7 -0
  96. package/src/LanguagePopups.tsx +4 -0
  97. package/src/PickPopup.tsx +47 -5
  98. package/src/WrapCard.tsx +64 -0
  99. package/src/call-hierarchy-model.ts +25 -3
  100. package/src/call-hierarchy.ts +62 -20
  101. package/src/clipboard-history.ts +102 -0
  102. package/src/code-actions.ts +48 -11
  103. package/src/code-vision.ts +3 -3
  104. package/src/completion.ts +123 -25
  105. package/src/context-menu-rows.ts +3 -2
  106. package/src/definition-link.ts +17 -5
  107. package/src/document-colors.ts +189 -0
  108. package/src/document-links.ts +129 -0
  109. package/src/document-query.ts +71 -0
  110. package/src/edit-locations.ts +162 -0
  111. package/src/editor-language.ts +86 -3
  112. package/src/emmet-module.d.ts +44 -0
  113. package/src/emmet.ts +73 -0
  114. package/src/folding.ts +3 -3
  115. package/src/format-on-type.ts +5 -5
  116. package/src/highlights.ts +3 -3
  117. package/src/history.ts +81 -1
  118. package/src/host-types.ts +17 -0
  119. package/src/index.ts +28 -3
  120. package/src/inlay-hints.ts +2 -2
  121. package/src/inline-completion.ts +221 -0
  122. package/src/key-priority.ts +11 -6
  123. package/src/linked-editing.ts +199 -0
  124. package/src/live-templates.ts +315 -0
  125. package/src/locales/en.json +61 -2
  126. package/src/locales/nl.json +61 -2
  127. package/src/markup-context.ts +387 -0
  128. package/src/markup.ts +377 -0
  129. package/src/navigation.ts +6 -6
  130. package/src/paste-history.ts +111 -0
  131. package/src/peek.ts +4 -4
  132. package/src/pick.ts +218 -20
  133. package/src/popups.ts +21 -1
  134. package/src/postfix.ts +224 -0
  135. package/src/project-language.ts +139 -19
  136. package/src/rename.ts +11 -11
  137. package/src/rendered-docs.ts +2 -2
  138. package/src/selection-ranges.ts +3 -3
  139. package/src/signature.ts +6 -6
  140. package/src/snippet-insert.ts +258 -0
  141. package/src/snippet-session.ts +9 -4
  142. package/src/snippet.ts +82 -7
  143. package/src/surround-constructs.ts +85 -0
  144. package/src/surround.ts +430 -0
  145. package/src/symbol-picker.ts +6 -6
  146. package/src/symbols.ts +30 -4
  147. package/src/template-builtins.ts +94 -0
  148. package/src/template-context.ts +65 -0
  149. package/src/templates.ts +197 -0
  150. 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(delta) {
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.activate(this.ids[Math.max(0, Math.min(this.ids.length - 1, index + delta))]);
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
- if (id === this.active) {
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.active = id;
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
- if (this.spec === null || event.metaKey || event.ctrlKey || event.altKey) {
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
- this.activate(this.ids[this.ids.length - 1]);
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.choose(this.active);
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
- return false;
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: spec.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
@@ -10,6 +10,7 @@ export function createPopupStore() {
10
10
  callHierarchy: null,
11
11
  symbols: null,
12
12
  menu: null,
13
- authors: null
13
+ authors: null,
14
+ wrap: null
14
15
  }));
15
16
  }
@@ -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 {};
@@ -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
- acquire(uri: string, languageId: string, editor: Editor): LanguageDocumentHandle;
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
  }