@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
package/dist/markup.js ADDED
@@ -0,0 +1,453 @@
1
+ import { abbreviationBefore, expandAbbreviation } from './emmet.js';
2
+ import { KEY_PRIORITY } from './key-priority.js';
3
+ import { lineTextOf } from './line-text.js';
4
+ import { isVoidElement, markupPlaceAt, markupSyntaxOf, tagPairAt } from './markup-context.js';
5
+ import { isShortcut } from './shortcut-keys.js';
6
+ import { parseSnippet } from './snippet.js';
7
+ import { indentSnippet } from './snippet-indent.js';
8
+ import { positionInInsertion } from './snippet-session.js';
9
+ const NAME = /^[\w:.-]*$/;
10
+ const NAME_BEFORE = /<\/?[\w:.-]*$/;
11
+ /*
12
+ * Writing HTML, XML, the template of a Vue file and the markup of PHP: Emmet abbreviations, end tags that
13
+ * write themselves and tag names that change in pairs. The edits it adds join the undo step of the key that
14
+ * caused them. It does nothing in other languages, in a document over `MAX_MARKUP_SCAN` characters, or with more
15
+ * than one caret.
16
+ */
17
+ export class MarkupFeature {
18
+ language;
19
+ syntax;
20
+ settings = {
21
+ emmet: true,
22
+ closeTags: true,
23
+ linkedTags: true
24
+ };
25
+ link = null;
26
+ wrapping = null;
27
+ /* Raised by every change of the text, so a follow-up edit for an older text is dropped. */
28
+ revision = 0;
29
+ mirroring = false;
30
+ constructor(language) {
31
+ this.language = language;
32
+ this.syntax = markupSyntaxOf(language.languageId);
33
+ if (this.syntax === null) {
34
+ return;
35
+ }
36
+ const {
37
+ editor
38
+ } = language;
39
+ const offs = [language.onKey(event => this.key(event), KEY_PRIORITY.markup), editor.onTextChange(change => this.edited(change)), editor.onCaret(() => this.moved())];
40
+ language.onDispose(() => {
41
+ this.cancelWrap();
42
+ for (const off of offs) {
43
+ off();
44
+ }
45
+ });
46
+ }
47
+ /* Whether the language of the document is markup this feature writes. */
48
+ get supported() {
49
+ return this.syntax !== null;
50
+ }
51
+ configure(settings) {
52
+ this.settings = {
53
+ emmet: settings.emmet !== false,
54
+ closeTags: settings.closeTags !== false,
55
+ linkedTags: settings.linkedTags !== false
56
+ };
57
+ if (!this.settings.linkedTags) {
58
+ this.link = null;
59
+ }
60
+ }
61
+ /* Expands the abbreviation before the caret, its places to fill as a snippet; false when there is none, which leaves Tab its own meaning. */
62
+ expand() {
63
+ const {
64
+ syntax
65
+ } = this;
66
+ const {
67
+ editor
68
+ } = this.language;
69
+ if (syntax === null || !this.settings.emmet || !this.editable()) {
70
+ return false;
71
+ }
72
+ const caret = editor.getCaret();
73
+ const line = lineTextOf(editor, caret.line);
74
+ const found = abbreviationBefore(line, caret.character);
75
+ if (found === null) {
76
+ return false;
77
+ }
78
+ const range = {
79
+ start: {
80
+ line: caret.line,
81
+ character: found.start
82
+ },
83
+ end: {
84
+ line: caret.line,
85
+ character: found.end
86
+ }
87
+ };
88
+ if (markupPlaceAt(editor.getText(), editor.offsetAt(range.start), syntax)?.kind !== 'text') {
89
+ return false;
90
+ }
91
+ const expanded = expandAbbreviation(found.abbreviation, {
92
+ syntax,
93
+ indent: this.indent()
94
+ });
95
+ return expanded !== null && this.insert(range, expanded, line);
96
+ }
97
+ /* Opens the field for an abbreviation to wrap the selection in, or the text of the caret's line without one. */
98
+ startWrap() {
99
+ const {
100
+ editor,
101
+ popups
102
+ } = this.language;
103
+ if (this.syntax === null || !this.editable(true)) {
104
+ return false;
105
+ }
106
+ const range = this.wrapTarget();
107
+ if (range === null) {
108
+ return false;
109
+ }
110
+ this.cancelWrap();
111
+ this.wrapping = editor.trackRange(range);
112
+ popups.setState({
113
+ wrap: {
114
+ anchor: range.start,
115
+ error: null
116
+ }
117
+ });
118
+ return true;
119
+ }
120
+ /* Wraps the text the field was opened over; false, with the reason in the field, when the abbreviation does not expand. */
121
+ submitWrap(abbreviation) {
122
+ const {
123
+ syntax
124
+ } = this;
125
+ const {
126
+ editor,
127
+ popups,
128
+ project
129
+ } = this.language;
130
+ const range = this.wrapping?.get() ?? null;
131
+ if (syntax === null || range === null) {
132
+ this.cancelWrap();
133
+ return false;
134
+ }
135
+ const expanded = expandAbbreviation(abbreviation.trim(), {
136
+ syntax,
137
+ indent: this.indent(),
138
+ wrap: this.wrapLines(range)
139
+ });
140
+ if (expanded === null) {
141
+ popups.setState({
142
+ wrap: {
143
+ anchor: range.start,
144
+ error: project.i18n.t('editor:language.markup.invalid')
145
+ }
146
+ });
147
+ return false;
148
+ }
149
+ this.cancelWrap();
150
+ const inserted = this.insert(range, expanded, lineTextOf(editor, range.start.line));
151
+ editor.focus();
152
+ return inserted;
153
+ }
154
+ cancelWrap() {
155
+ this.wrapping?.dispose();
156
+ this.wrapping = null;
157
+ if (this.language.popups.getState().wrap !== null) {
158
+ this.language.popups.setState({
159
+ wrap: null
160
+ });
161
+ }
162
+ }
163
+ key(event) {
164
+ if (isShortcut(this.language.shortcuts.wrapWithAbbreviation, event)) {
165
+ return this.startWrap();
166
+ }
167
+ if (event.key !== 'Tab' || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.isComposing) {
168
+ return false;
169
+ }
170
+ return this.expand();
171
+ }
172
+ editable(selection = false) {
173
+ const {
174
+ editor
175
+ } = this.language;
176
+ const selections = editor.getSelections();
177
+ if (editor.isReadOnly?.() === true || selections.length !== 1) {
178
+ return false;
179
+ }
180
+ const {
181
+ start,
182
+ end
183
+ } = selections[0];
184
+ return selection || start.line === end.line && start.character === end.character;
185
+ }
186
+ indent() {
187
+ const {
188
+ tabSize,
189
+ insertSpaces
190
+ } = this.language.editor.getIndentation();
191
+ return insertSpaces ? ' '.repeat(tabSize) : '\t';
192
+ }
193
+ /* The selection without the whitespace around it, or the text of the caret's line; null when that is blank. */
194
+ wrapTarget() {
195
+ const {
196
+ editor
197
+ } = this.language;
198
+ const selection = editor.getSelection();
199
+ const text = editor.getText();
200
+ const empty = selection.start.line === selection.end.line && selection.start.character === selection.end.character;
201
+ let from = empty ? editor.offsetAt({
202
+ line: selection.start.line,
203
+ character: 0
204
+ }) : editor.offsetAt(selection.start);
205
+ let to = empty ? from + lineTextOf(editor, selection.start.line).length : editor.offsetAt(selection.end);
206
+ while (from < to && /\s/.test(text[from])) {
207
+ from++;
208
+ }
209
+ while (to > from && /\s/.test(text[to - 1])) {
210
+ to--;
211
+ }
212
+ return from === to ? null : {
213
+ start: editor.positionAt(from),
214
+ end: editor.positionAt(to)
215
+ };
216
+ }
217
+ /* The lines of the wrapped text, without the indentation they share, which the expansion gives them again. */
218
+ wrapLines(range) {
219
+ const lines = this.language.editor.textInRange(range).split(/\r\n|\r|\n/);
220
+ const indents = lines.slice(1).flatMap(line => line.trim() === '' ? [] : [/^[\t ]*/.exec(line)[0].length]);
221
+ const shared = indents.length === 0 ? 0 : Math.min(...indents);
222
+ return lines.map((line, index) => index === 0 ? line : line.slice(Math.min(shared, /^[\t ]*/.exec(line)[0].length)));
223
+ }
224
+ insert(range, snippet, line) {
225
+ const {
226
+ editor,
227
+ snippets
228
+ } = this.language;
229
+ const parsed = indentSnippet(parseSnippet(snippet), line);
230
+ if (parsed.limited === true) {
231
+ return false;
232
+ }
233
+ if (!this.apply([{
234
+ range,
235
+ text: parsed.text
236
+ }])) {
237
+ return false;
238
+ }
239
+ if (parsed.stops.length > 0) {
240
+ snippets.begin(range.start, parsed.text, parsed.stops, parsed.fields);
241
+ } else {
242
+ editor.setCaret(positionInInsertion(range.start, parsed.text, parsed.text.length));
243
+ }
244
+ return true;
245
+ }
246
+ /* Through the snippet session while one runs, so an edit inside its field keeps it going. */
247
+ apply(changes, options) {
248
+ const {
249
+ editor,
250
+ snippets
251
+ } = this.language;
252
+ return snippets.isActive ? snippets.applyEdits(changes, snippets.token, [], options) : editor.applyEdits(changes, options);
253
+ }
254
+ edited(change) {
255
+ const revision = ++this.revision;
256
+ if (this.mirroring) {
257
+ return;
258
+ }
259
+ const edit = change.changes[0];
260
+ // Backspace and the word deletes are commands too; an undo of both names is two changes and lets go.
261
+ if (change.source === 'external' || change.changes.length !== 1 || edit === undefined || edit.range.start.line !== edit.range.end.line) {
262
+ this.link = null;
263
+ return;
264
+ }
265
+ const from = this.language.editor.offsetAt(edit.range.start);
266
+ const removed = edit.range.end.character - edit.range.start.character;
267
+ this.follow(from, removed, edit.text, revision);
268
+ if (change.source !== 'input' || !this.settings.closeTags || removed !== 0 || this.language.editor.getSelections().length !== 1) {
269
+ return;
270
+ }
271
+ if (edit.text === '>') {
272
+ queueMicrotask(() => this.closeTag(from, revision));
273
+ } else if (edit.text === '/') {
274
+ queueMicrotask(() => this.completeEndTag(from, revision));
275
+ }
276
+ }
277
+ /* Keeps the link on the name being typed and copies the name to its other tag once the edit is in. */
278
+ follow(from, removed, inserted, revision) {
279
+ const link = this.link;
280
+ if (link === null) {
281
+ return;
282
+ }
283
+ const {
284
+ own,
285
+ partner
286
+ } = link;
287
+ if (from < own.from || from + removed > own.to || !NAME.test(inserted)) {
288
+ this.link = null;
289
+ return;
290
+ }
291
+ const delta = inserted.length - removed;
292
+ this.link = {
293
+ own: {
294
+ from: own.from,
295
+ to: own.to + delta
296
+ },
297
+ partner: partner.from > own.from ? {
298
+ from: partner.from + delta,
299
+ to: partner.to + delta
300
+ } : partner
301
+ };
302
+ queueMicrotask(() => this.mirror(revision));
303
+ }
304
+ mirror(revision) {
305
+ const link = this.link;
306
+ if (link === null || revision !== this.revision) {
307
+ return;
308
+ }
309
+ const {
310
+ editor
311
+ } = this.language;
312
+ const text = editor.getText();
313
+ const name = text.slice(link.own.from, link.own.to);
314
+ if (name === text.slice(link.partner.from, link.partner.to)) {
315
+ return;
316
+ }
317
+ const range = {
318
+ start: editor.positionAt(link.partner.from),
319
+ end: editor.positionAt(link.partner.to)
320
+ };
321
+ this.mirroring = true;
322
+ try {
323
+ if (!this.apply([{
324
+ range,
325
+ text: name
326
+ }], {
327
+ mergeUndo: true
328
+ })) {
329
+ this.link = null;
330
+ return;
331
+ }
332
+ } finally {
333
+ this.mirroring = false;
334
+ }
335
+ const delta = name.length - (link.partner.to - link.partner.from);
336
+ this.link = {
337
+ own: link.partner.from < link.own.from ? {
338
+ from: link.own.from + delta,
339
+ to: link.own.to + delta
340
+ } : link.own,
341
+ partner: {
342
+ from: link.partner.from,
343
+ to: link.partner.to + delta
344
+ }
345
+ };
346
+ }
347
+ /* The `>` at `at` ended a start tag: its end tag goes after it, with the caret between the two. */
348
+ closeTag(at, revision) {
349
+ const {
350
+ editor
351
+ } = this.language;
352
+ const text = editor.getText();
353
+ if (revision !== this.revision || text[at] !== '>' || text[at - 1] === '/') {
354
+ return;
355
+ }
356
+ const place = markupPlaceAt(text, at, this.syntax);
357
+ if (place?.kind !== 'tag' || place.tag.closing || place.tag.inValue || place.tag.name === '' || isVoidElement(place.tag.name, this.syntax)) {
358
+ return;
359
+ }
360
+ const end = `</${place.tag.name}>`;
361
+ if (text.startsWith(end, at + 1)) {
362
+ return;
363
+ }
364
+ const caret = editor.positionAt(at + 1);
365
+ this.apply([{
366
+ range: {
367
+ start: caret,
368
+ end: caret
369
+ },
370
+ text: end
371
+ }], {
372
+ mergeUndo: true,
373
+ mapSelections: () => ({
374
+ start: caret,
375
+ end: caret
376
+ })
377
+ });
378
+ }
379
+ /* The `/` at `at` follows a `<` in text: the end tag of the innermost open element is written out. */
380
+ completeEndTag(at, revision) {
381
+ const {
382
+ editor
383
+ } = this.language;
384
+ const text = editor.getText();
385
+ if (revision !== this.revision || text[at] !== '/' || text[at - 1] !== '<') {
386
+ return;
387
+ }
388
+ const place = markupPlaceAt(text, at - 1, this.syntax);
389
+ const element = place?.kind === 'text' ? place.open.at(-1) : undefined;
390
+ if (element === undefined) {
391
+ return;
392
+ }
393
+ const position = editor.positionAt(at + 1);
394
+ const closed = text[at + 1] === '>';
395
+ const completion = closed ? element.name : `${element.name}>`;
396
+ const after = {
397
+ line: position.line,
398
+ character: position.character + element.name.length + 1
399
+ };
400
+ this.apply([{
401
+ range: {
402
+ start: position,
403
+ end: position
404
+ },
405
+ text: completion
406
+ }], {
407
+ mergeUndo: true,
408
+ mapSelections: () => ({
409
+ start: after,
410
+ end: after
411
+ })
412
+ });
413
+ }
414
+ /* Links the tag name the caret moved into with its other tag, once the edit that moved it is in. */
415
+ moved() {
416
+ if (!this.settings.linkedTags || this.mirroring) {
417
+ return;
418
+ }
419
+ queueMicrotask(() => {
420
+ this.link = this.linkAtCaret();
421
+ });
422
+ }
423
+ linkAtCaret() {
424
+ const {
425
+ editor
426
+ } = this.language;
427
+ if (!this.editable()) {
428
+ return null;
429
+ }
430
+ const caret = editor.getCaret();
431
+ const offset = editor.offsetAt(caret);
432
+ const link = this.link;
433
+ if (link !== null && link.own.from <= offset && offset <= link.own.to) {
434
+ return link;
435
+ }
436
+ const line = lineTextOf(editor, caret.line);
437
+ if (!NAME_BEFORE.test(line.slice(0, caret.character))) {
438
+ return null;
439
+ }
440
+ const pair = tagPairAt(editor.getText(), offset, this.syntax);
441
+ if (pair === null) {
442
+ return null;
443
+ }
444
+ const inOpen = pair.open.from <= offset && offset <= pair.open.to;
445
+ return inOpen ? {
446
+ own: pair.open,
447
+ partner: pair.close
448
+ } : {
449
+ own: pair.close,
450
+ partner: pair.open
451
+ };
452
+ }
453
+ }
@@ -75,11 +75,10 @@ export class NavigationFeature {
75
75
  });
76
76
  }
77
77
  supports(kind) {
78
- return this.language.project.service.supports(METHODS[kind], this.language.uri);
78
+ return this.language.service.supports(METHODS[kind], this.language.uri);
79
79
  }
80
80
  async go(kind, position = this.language.editor.getCaret()) {
81
81
  const {
82
- project,
83
82
  uri,
84
83
  editor
85
84
  } = this.language;
@@ -89,7 +88,7 @@ export class NavigationFeature {
89
88
  }
90
89
  let result;
91
90
  try {
92
- result = await REQUESTS[kind](project.service, uri, position);
91
+ result = await REQUESTS[kind](this.language.service, uri, position);
93
92
  } catch (error) {
94
93
  if (!(error instanceof StaleResultError)) {
95
94
  this.tell(this.say('failed', {
@@ -118,7 +117,6 @@ export class NavigationFeature {
118
117
  */
119
118
  async goToName(name, anchor, own) {
120
119
  const {
121
- project,
122
120
  uri,
123
121
  editor
124
122
  } = this.language;
@@ -126,11 +124,11 @@ export class NavigationFeature {
126
124
  this.language.goTo(own);
127
125
  return;
128
126
  }
129
- if (!project.service.supports('workspace/symbol', uri)) {
127
+ if (!this.language.service.supports('workspace/symbol', uri)) {
130
128
  return;
131
129
  }
132
130
  try {
133
- const places = placesOfName(await project.service.workspaceSymbols(uri, name), name, editor.getText());
131
+ const places = placesOfName(await this.language.service.workspaceSymbols(uri, name), name, editor.getText());
134
132
  if (places.length === 1) {
135
133
  this.language.goTo(places[0]);
136
134
  } else if (places.length > 1) {
@@ -0,0 +1,7 @@
1
+ import type { EditorLanguage } from './editor-language.ts';
2
+ export declare class PasteHistoryFeature {
3
+ private readonly language;
4
+ constructor(language: EditorLanguage);
5
+ open(): void;
6
+ private paste;
7
+ }
@@ -0,0 +1,104 @@
1
+ import { rememberCopy } from '@adecore/editor';
2
+ import { KEY_PRIORITY } from './key-priority.js';
3
+ import { isShortcut } from './shortcut-keys.js';
4
+ const LABEL_LENGTH = 80;
5
+ /* The first characters of a copy on one line, with each line break shown as a return sign. */
6
+ function labelOf(text) {
7
+ const flat = text.replace(/\r\n|\r|\n/g, '⏎');
8
+ return flat.length > LABEL_LENGTH ? `${flat.slice(0, LABEL_LENGTH)}…` : flat;
9
+ }
10
+ /*
11
+ * Paste from History: a list of what was copied in the editors of the project, newest first. Typing narrows it,
12
+ * Shift with the arrows chooses several entries, which paste as one, and Delete forgets entries.
13
+ */
14
+ export class PasteHistoryFeature {
15
+ language;
16
+ constructor(language) {
17
+ this.language = language;
18
+ const off = language.onKey(event => {
19
+ if (!isShortcut(this.language.shortcuts.pasteFromHistory, event)) {
20
+ return false;
21
+ }
22
+ this.open();
23
+ return true;
24
+ }, KEY_PRIORITY.pasteHistory);
25
+ language.onDispose(off);
26
+ }
27
+ open() {
28
+ const {
29
+ editor,
30
+ project
31
+ } = this.language;
32
+ const {
33
+ clipboard
34
+ } = project;
35
+ if (clipboard.entries.length === 0) {
36
+ editor.announce(project.i18n.t('editor:language.pasteHistory.empty'));
37
+ return;
38
+ }
39
+ const ids = new Map();
40
+ const entries = new Map();
41
+ const groups = () => [{
42
+ title: null,
43
+ rows: clipboard.entries.map(entry => {
44
+ const id = ids.get(entry) ?? String(ids.size);
45
+ ids.set(entry, id);
46
+ entries.set(id, entry);
47
+ return {
48
+ id,
49
+ label: labelOf(entry.text),
50
+ detail: '',
51
+ search: entry.text
52
+ };
53
+ })
54
+ }];
55
+ const rows = groups();
56
+ this.language.pick.open({
57
+ anchor: editor.getCaret(),
58
+ title: project.i18n.t('editor:language.pasteHistory.title'),
59
+ groups: rows,
60
+ active: rows[0].rows[0].id,
61
+ filter: true,
62
+ multiple: true,
63
+ accept: id => this.paste([entries.get(id)]),
64
+ acceptMany: chosen => this.paste(clipboard.entries.filter(entry => chosen.includes(ids.get(entry) ?? ''))),
65
+ preview: async id => ({
66
+ removed: [],
67
+ added: entries.get(id)?.text.split('\n') ?? [],
68
+ note: null,
69
+ kind: 'text'
70
+ }),
71
+ remove: chosen => {
72
+ for (const id of chosen) {
73
+ clipboard.remove(entries.get(id));
74
+ }
75
+ return groups();
76
+ }
77
+ });
78
+ }
79
+ /* Pastes entries in list order, joined by line breaks, as a paste of that text from the clipboard would. */
80
+ paste(chosen) {
81
+ const {
82
+ editor,
83
+ project
84
+ } = this.language;
85
+ const {
86
+ clipboard
87
+ } = project;
88
+ if (chosen.length === 0) {
89
+ return;
90
+ }
91
+ const text = chosen.map(entry => entry.text).join('\n');
92
+ const wholeLines = chosen.length === 1 && chosen[0].wholeLines;
93
+ if (chosen.length === 1) {
94
+ clipboard.promote(chosen[0]);
95
+ } else {
96
+ clipboard.add(text);
97
+ }
98
+ globalThis.navigator?.clipboard?.writeText(text).catch(() => undefined);
99
+ rememberCopy(text, wholeLines);
100
+ editor.paste?.(text, {
101
+ wholeLines
102
+ });
103
+ }
104
+ }
package/dist/peek.js CHANGED
@@ -71,7 +71,7 @@ export class PeekFeature {
71
71
  uri
72
72
  } = this.language;
73
73
  const method = KINDS[kind].method;
74
- if (!project.service.supports(method, uri)) {
74
+ if (!this.language.service.supports(method, uri)) {
75
75
  this.tell(this.say(KINDS[kind].unavailable));
76
76
  return;
77
77
  }
@@ -147,17 +147,16 @@ export class PeekFeature {
147
147
  }
148
148
  async fetch(kind, position) {
149
149
  const {
150
- project,
151
150
  uri
152
151
  } = this.language;
153
152
  if (kind === 'references') {
154
153
  // Without the declaration, so the list holds what the usages count above a declaration counts.
155
154
  return {
156
- locations: (await project.service.references(uri, position, false)) ?? [],
155
+ locations: (await this.language.service.references(uri, position, false)) ?? [],
157
156
  targets: new Map()
158
157
  };
159
158
  }
160
- const places = uniquePlaces(await project.service.definition(uri, position));
159
+ const places = uniquePlaces(await this.language.service.definition(uri, position));
161
160
  const targets = new Map(places.flatMap(({
162
161
  location,
163
162
  target
package/dist/pick.d.ts CHANGED
@@ -11,13 +11,21 @@ export interface PickSpec {
11
11
  onTab?(backwards: boolean): void;
12
12
  attempt?(id: string): Promise<string | null>;
13
13
  preview?(id: string, signal: AbortSignal): Promise<PickPreview | null>;
14
+ readonly filter?: boolean;
15
+ readonly multiple?: boolean;
16
+ acceptMany?(ids: readonly string[]): void;
17
+ remove?(ids: readonly string[]): readonly PickGroup[];
14
18
  }
15
19
  export declare class PickFeature {
16
20
  private readonly language;
17
21
  private readonly timers;
18
22
  private spec;
23
+ private groups;
19
24
  private ids;
20
25
  private active;
26
+ private query;
27
+ private chosen;
28
+ private rangeAnchor;
21
29
  private previewTimer;
22
30
  private controller;
23
31
  private refusals;
@@ -26,9 +34,19 @@ export declare class PickFeature {
26
34
  get isOpen(): boolean;
27
35
  open(spec: PickSpec): void;
28
36
  close(): void;
29
- move(delta: number): void;
30
- choose(id: string): void;
37
+ move(delta: number, extend?: boolean): void;
38
+ private settle;
39
+ choose(id: string, modifiers?: {
40
+ readonly toggle?: boolean;
41
+ readonly range?: boolean;
42
+ }): void;
31
43
  private activate;
44
+ private extendTo;
45
+ private toggle;
46
+ private takeActive;
47
+ private visibleGroups;
48
+ private requery;
49
+ private removeChosen;
32
50
  private key;
33
51
  private schedulePreview;
34
52
  private publish;