@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
@@ -0,0 +1,387 @@
1
+ /* `vue` and `php` are HTML with interpolations or code blocks a markup feature leaves alone. */
2
+ export type MarkupSyntax = 'html' | 'xml' | 'vue' | 'php';
3
+
4
+ export interface OpenElement {
5
+ readonly name: string;
6
+ /* The offset of the first character of the name, after the `<`. */
7
+ readonly nameFrom: number;
8
+ }
9
+
10
+ /* The tag an offset is in, from its `<` up to and including its `>`. */
11
+ export interface TagAt {
12
+ readonly start: number;
13
+ readonly closing: boolean;
14
+ readonly name: string;
15
+ readonly nameFrom: number;
16
+ readonly nameTo: number;
17
+ /* The offset is inside a quoted attribute value. */
18
+ readonly inValue: boolean;
19
+ }
20
+
21
+ export type MarkupPlace =
22
+ | { readonly kind: 'text'; readonly open: readonly OpenElement[] }
23
+ | { readonly kind: 'tag'; readonly tag: TagAt }
24
+ | { readonly kind: 'other' };
25
+
26
+ /* The names of an element that are one start tag and its end tag, which change together. */
27
+ export interface TagPair {
28
+ readonly open: { readonly from: number; readonly to: number };
29
+ readonly close: { readonly from: number; readonly to: number };
30
+ }
31
+
32
+ /* A whole tag, from its `<` up to and after its `>`. */
33
+ export interface TagSpan {
34
+ readonly from: number;
35
+ readonly to: number;
36
+ }
37
+
38
+ /* An element as its tags: `close` is null for one that was never closed. */
39
+ export interface EnclosingElement {
40
+ readonly name: string;
41
+ readonly open: TagSpan;
42
+ readonly close: TagSpan | null;
43
+ }
44
+
45
+ /* Above this a document is not read on a keystroke; the features then do nothing in it. */
46
+ export const MAX_MARKUP_SCAN = 1_000_000;
47
+
48
+ const VOID_ELEMENTS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
49
+ const RAW_ELEMENTS = new Set(['script', 'style', 'textarea', 'title']);
50
+ const NAME_START = /[A-Za-z]/;
51
+ const NAME = /[\w:.-]/;
52
+
53
+ export function markupSyntaxOf(languageId: string): MarkupSyntax | null {
54
+ switch (languageId.toLowerCase()) {
55
+ case 'html':
56
+ case 'xhtml':
57
+ return 'html';
58
+ case 'xml':
59
+ case 'svg':
60
+ case 'xsl':
61
+ return 'xml';
62
+ case 'vue':
63
+ return 'vue';
64
+ case 'php':
65
+ return 'php';
66
+ default:
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /* Whether an element of this name has no end tag, which only HTML has. */
72
+ export function isVoidElement(name: string, syntax: MarkupSyntax): boolean {
73
+ return syntax !== 'xml' && VOID_ELEMENTS.has(name.toLowerCase());
74
+ }
75
+
76
+ class Stop {
77
+ readonly place: MarkupPlace;
78
+
79
+ constructor(place: MarkupPlace) {
80
+ this.place = place;
81
+ }
82
+ }
83
+
84
+ /*
85
+ * A reader of markup that is no parser: it follows tags, comments, raw elements and the code blocks of
86
+ * PHP and Vue far enough to tell what an offset is in, and pairs a start tag with the end tag that closes it.
87
+ */
88
+ class MarkupScanner {
89
+ readonly pairs: TagPair[] = [];
90
+ readonly elements: EnclosingElement[] = [];
91
+ private readonly text: string;
92
+ private readonly syntax: MarkupSyntax;
93
+ private readonly until: number;
94
+ private readonly open: (OpenElement & { readonly nameTo: number; readonly tag: TagSpan })[] = [];
95
+
96
+ constructor(text: string, syntax: MarkupSyntax, until: number) {
97
+ this.text = text;
98
+ this.syntax = syntax;
99
+ this.until = until;
100
+ }
101
+
102
+ run(): MarkupPlace {
103
+ try {
104
+ this.content();
105
+ return this.textPlace();
106
+ } catch (error) {
107
+ if (error instanceof Stop) {
108
+ return error.place;
109
+ }
110
+ throw error;
111
+ }
112
+ }
113
+
114
+ private textPlace(): MarkupPlace {
115
+ return { kind: 'text', open: this.open.map(({ name, nameFrom }) => ({ name, nameFrom })) };
116
+ }
117
+
118
+ /* Leaves the scan with what the offset is in when it lies in `from` to `to`, `to` included where the construct owns its last character. */
119
+ private reached(from: number, to: number, place: () => MarkupPlace): void {
120
+ if (from < this.until && this.until <= to) {
121
+ throw new Stop(place());
122
+ }
123
+ }
124
+
125
+ private content(): void {
126
+ const { text } = this;
127
+ let at = 0;
128
+ while (at < text.length) {
129
+ if (at >= this.until) {
130
+ throw new Stop(this.textPlace());
131
+ }
132
+ const char = text[at]!;
133
+ if (char === '<') {
134
+ at = this.markup(at);
135
+ } else if (this.syntax === 'vue' && char === '{' && text[at + 1] === '{') {
136
+ at = this.skipTo(at, '}}');
137
+ } else {
138
+ at++;
139
+ }
140
+ }
141
+ }
142
+
143
+ /* Skips from `from` past `close`, the whole construct counting as something other than text. */
144
+ private skipTo(from: number, close: string): number {
145
+ const end = this.text.indexOf(close, from + 1);
146
+ const after = end === -1 ? this.text.length : end + close.length;
147
+ this.reached(from, end === -1 ? after : after - 1, () => ({ kind: 'other' }));
148
+ return after;
149
+ }
150
+
151
+ private markup(at: number): number {
152
+ const { text } = this;
153
+ if (text.startsWith('<!--', at)) {
154
+ return this.skipTo(at, '-->');
155
+ }
156
+ if (text.startsWith('<![CDATA[', at)) {
157
+ return this.skipTo(at, ']]>');
158
+ }
159
+ if (text[at + 1] === '?') {
160
+ return this.skipTo(at, this.syntax === 'php' ? '?>' : '>');
161
+ }
162
+ if (text[at + 1] === '!') {
163
+ return this.skipTo(at, '>');
164
+ }
165
+ const closing = text[at + 1] === '/';
166
+ const nameFrom = at + (closing ? 2 : 1);
167
+ if (!NAME_START.test(text[nameFrom] ?? '')) {
168
+ return at + 1;
169
+ }
170
+ let nameTo = nameFrom;
171
+ while (nameTo < text.length && NAME.test(text[nameTo]!)) {
172
+ nameTo++;
173
+ }
174
+ const name = text.slice(nameFrom, nameTo);
175
+ const end = this.tagEnd(at, closing, name, nameFrom, nameTo);
176
+ if (closing) {
177
+ this.close(name, nameFrom, nameTo, { from: at, to: end });
178
+ return end;
179
+ }
180
+ const selfClosing = text[end - 2] === '/' && text[end - 1] === '>';
181
+ if (selfClosing || isVoidElement(name, this.syntax)) {
182
+ return end;
183
+ }
184
+ if (this.syntax !== 'xml' && RAW_ELEMENTS.has(name.toLowerCase())) {
185
+ return this.rawContent({ from: at, to: end }, name, nameFrom, nameTo);
186
+ }
187
+ this.open.push({ name, nameFrom, nameTo, tag: { from: at, to: end } });
188
+ return end;
189
+ }
190
+
191
+ /* Reads the attributes of a tag up to the offset after its `>`, quotes and PHP blocks included. */
192
+ private tagEnd(start: number, closing: boolean, name: string, nameFrom: number, nameTo: number): number {
193
+ const { text } = this;
194
+ let at = nameTo;
195
+ let quote: string | null = null;
196
+ let valueFrom = -1;
197
+ while (at < text.length) {
198
+ const char = text[at]!;
199
+ if (this.syntax === 'php' && char === '<' && text[at + 1] === '?') {
200
+ at = this.skipTo(at, '?>');
201
+ continue;
202
+ }
203
+ if (quote !== null) {
204
+ if (char === quote) {
205
+ this.reached(valueFrom, at, () => ({ kind: 'tag', tag: { start, closing, name, nameFrom, nameTo, inValue: true } }));
206
+ quote = null;
207
+ }
208
+ } else if (char === '"' || char === "'") {
209
+ quote = char;
210
+ valueFrom = at;
211
+ } else if (char === '>') {
212
+ this.reached(start, at, () => ({ kind: 'tag', tag: { start, closing, name, nameFrom, nameTo, inValue: false } }));
213
+ return at + 1;
214
+ } else if (char === '<') {
215
+ // A tag that never closed ends where the next one starts.
216
+ this.reached(start, at, () => ({ kind: 'tag', tag: { start, closing, name, nameFrom, nameTo, inValue: false } }));
217
+ return at;
218
+ }
219
+ at++;
220
+ }
221
+ this.reached(start, text.length, () => ({
222
+ kind: 'tag',
223
+ tag: { start, closing, name, nameFrom, nameTo, inValue: quote !== null && this.until > valueFrom }
224
+ }));
225
+ return text.length;
226
+ }
227
+
228
+ /* The content of `script`, `style` and the like is not markup, up to the end tag of the element. */
229
+ private rawContent(tag: TagSpan, name: string, nameFrom: number, nameTo: number): number {
230
+ const from = tag.to;
231
+ const pattern = new RegExp(`</${name}(?=[\\s/>]|$)`, 'gi');
232
+ pattern.lastIndex = from;
233
+ const match = pattern.exec(this.text);
234
+ const end = match === null ? this.text.length : match.index;
235
+ this.reached(from - 1, end, () => ({ kind: 'other' }));
236
+ if (match === null) {
237
+ return end;
238
+ }
239
+ const closeFrom = end + 2;
240
+ const closeTo = closeFrom + name.length;
241
+ const after = this.tagEnd(end, true, this.text.slice(closeFrom, closeTo), closeFrom, closeTo);
242
+ this.pairs.push({ open: { from: nameFrom, to: nameTo }, close: { from: closeFrom, to: closeTo } });
243
+ this.elements.push({ name, open: tag, close: { from: end, to: after } });
244
+ return after;
245
+ }
246
+
247
+ /* An end tag closes the nearest open element of its name and every element left open inside it. */
248
+ private close(name: string, nameFrom: number, nameTo: number, tag: TagSpan): void {
249
+ const caseless = this.syntax !== 'xml';
250
+ for (let index = this.open.length - 1; index >= 0; index--) {
251
+ const element = this.open[index]!;
252
+ if (element.name === name || (caseless && element.name.toLowerCase() === name.toLowerCase())) {
253
+ this.pairs.push({ open: { from: element.nameFrom, to: element.nameTo }, close: { from: nameFrom, to: nameTo } });
254
+ this.elements.push({ name: element.name, open: element.tag, close: tag });
255
+ this.open.length = index;
256
+ return;
257
+ }
258
+ }
259
+ }
260
+
261
+ /* The elements still open at the end of the text. */
262
+ unclosed(): EnclosingElement[] {
263
+ return this.open.map((element) => ({ name: element.name, open: element.tag, close: null }));
264
+ }
265
+ }
266
+
267
+ /*
268
+ * The elements whose tags hold `offset`, innermost first: each with its start tag and its end tag, which an element left
269
+ * open does not have. Empty for a document too large to read on a keystroke.
270
+ */
271
+ export function enclosingElements(text: string, offset: number, syntax: MarkupSyntax): EnclosingElement[] {
272
+ if (text.length > MAX_MARKUP_SCAN) {
273
+ return [];
274
+ }
275
+ const scanner = new MarkupScanner(text, syntax, Infinity);
276
+ scanner.run();
277
+ return [...scanner.elements, ...scanner.unclosed()]
278
+ .filter((element) => element.open.from <= offset && offset <= (element.close?.to ?? text.length))
279
+ .sort((left, right) => right.open.from - left.open.from);
280
+ }
281
+
282
+ /* A replacement of the text from one offset up to another. */
283
+ export interface TextReplacement {
284
+ readonly from: number;
285
+ readonly to: number;
286
+ readonly text: string;
287
+ }
288
+
289
+ export function lineStartOf(text: string, offset: number): number {
290
+ let at = offset;
291
+
292
+ while (at > 0 && text[at - 1] !== '\n' && text[at - 1] !== '\r') {
293
+ at--;
294
+ }
295
+
296
+ return at;
297
+ }
298
+
299
+ export function lineEndOf(text: string, offset: number): number {
300
+ let at = offset;
301
+
302
+ while (at < text.length && text[at] !== '\n' && text[at] !== '\r') {
303
+ at++;
304
+ }
305
+
306
+ return at;
307
+ }
308
+
309
+ function breakLengthAt(text: string, offset: number): number {
310
+ if (text.startsWith('\r\n', offset)) {
311
+ return 2;
312
+ }
313
+
314
+ return text[offset] === '\n' || text[offset] === '\r' ? 1 : 0;
315
+ }
316
+
317
+ /* The tag alone on its line takes the line with it, and the break before it where no break follows. */
318
+ function tagReplacement(text: string, tag: TagSpan): TextReplacement {
319
+ const start = lineStartOf(text, tag.from);
320
+ const end = lineEndOf(text, tag.to);
321
+
322
+ if (text.slice(start, tag.from).trim() !== '' || text.slice(tag.to, end).trim() !== '') {
323
+ return { from: tag.from, to: tag.to, text: '' };
324
+ }
325
+
326
+ const after = breakLengthAt(text, end);
327
+ const before = start > 0 && after === 0 ? (text.startsWith('\r\n', start - 2) ? 2 : 1) : 0;
328
+
329
+ return { from: start - before, to: end + after, text: '' };
330
+ }
331
+
332
+ /*
333
+ * The edits that take an element's tags away and keep what is between them: each tag that stands alone on its line
334
+ * goes with its line, and when the start tag does, the lines it enclosed lose one indentation unit. The offsets are
335
+ * those of `text`, ascending and apart.
336
+ */
337
+ export function removeTagEdits(text: string, element: EnclosingElement, unit: string): TextReplacement[] {
338
+ const open = tagReplacement(text, element.open);
339
+ const edits: TextReplacement[] = [open];
340
+
341
+ if (element.close === null) {
342
+ return edits;
343
+ }
344
+
345
+ const close = tagReplacement(text, element.close);
346
+ const openAlone = open.from !== element.open.from || open.to !== element.open.to;
347
+ const closeAlone = close.from !== element.close.from || close.to !== element.close.to;
348
+ const indent = unit === '\t' ? /^\t/ : new RegExp(`^ {1,${unit.length}}`);
349
+ const lastLine = lineStartOf(text, element.close.from);
350
+ let line = lineEndOf(text, element.open.to);
351
+
352
+ line += breakLengthAt(text, line);
353
+
354
+ while (openAlone && line <= lastLine && line < text.length && !(closeAlone && line === lastLine)) {
355
+ const found = indent.exec(text.slice(line, lineEndOf(text, line)));
356
+
357
+ if (found !== null) {
358
+ edits.push({ from: line, to: line + found[0].length, text: '' });
359
+ }
360
+
361
+ line = lineEndOf(text, line);
362
+ line += breakLengthAt(text, line);
363
+ }
364
+
365
+ edits.push(close);
366
+
367
+ return edits;
368
+ }
369
+
370
+ /* What `offset` is in; null for a document too large to read on a keystroke. */
371
+ export function markupPlaceAt(text: string, offset: number, syntax: MarkupSyntax): MarkupPlace | null {
372
+ if (text.length > MAX_MARKUP_SCAN) {
373
+ return null;
374
+ }
375
+ return new MarkupScanner(text, syntax, offset).run();
376
+ }
377
+
378
+ /* The start and end tag names of the element whose name holds `offset`, either one, its edges included. */
379
+ export function tagPairAt(text: string, offset: number, syntax: MarkupSyntax): TagPair | null {
380
+ if (text.length > MAX_MARKUP_SCAN) {
381
+ return null;
382
+ }
383
+ const scanner = new MarkupScanner(text, syntax, Infinity);
384
+ scanner.run();
385
+ const holds = (range: { from: number; to: number }): boolean => range.from <= offset && offset <= range.to;
386
+ return scanner.pairs.find((pair) => holds(pair.open) || holds(pair.close)) ?? null;
387
+ }