@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
@@ -5,7 +5,7 @@ import { acquireSnippetTransforms, type SnippetTransformLease } from './snippet-
5
5
  import { realTimers, type Timers } from './timers.ts';
6
6
  import { KEY_PRIORITY } from './key-priority.ts';
7
7
  import { MAX_TRANSFORM_CHARACTERS, type SnippetTransformJob } from './snippet-transform-protocol.ts';
8
- import type { EditorContentChange, EditorPosition, EditorRange, EditorTextChange } from '@adecore/editor';
8
+ import type { EditorApplyEditsOptions, EditorContentChange, EditorPosition, EditorRange, EditorTextChange } from '@adecore/editor';
9
9
  import { comparePositions, endOfInsertion, rangeHolds, sameRange, shiftPosition } from './diagnostics-model.ts';
10
10
  import type { EditorLanguage } from './editor-language.ts';
11
11
  import { textBeforeOf } from './line-text.ts';
@@ -367,7 +367,12 @@ export class SnippetFeature {
367
367
  }
368
368
 
369
369
  /* Imports can touch a hull's edge; their affinity must differ from edits inside its active field. */
370
- applyEdits(changes: readonly EditorContentChange[], token: number, extras: readonly EditorContentChange[] = []): boolean {
370
+ applyEdits(
371
+ changes: readonly EditorContentChange[],
372
+ token: number,
373
+ extras: readonly EditorContentChange[] = [],
374
+ options?: EditorApplyEditsOptions
375
+ ): boolean {
371
376
  const frame = this.frames.at(-1);
372
377
 
373
378
  const fits =
@@ -385,14 +390,14 @@ export class SnippetFeature {
385
390
  if (token !== this.generation || !fits) {
386
391
  this.end();
387
392
 
388
- return this.language.editor.applyEdits(changes);
393
+ return this.language.editor.applyEdits(changes, options);
389
394
  }
390
395
 
391
396
  this.applying = true;
392
397
  this.extras = extras;
393
398
 
394
399
  try {
395
- return this.language.editor.applyEdits(changes);
400
+ return this.language.editor.applyEdits(changes, options);
396
401
  } finally {
397
402
  this.applying = false;
398
403
  this.extras = [];
package/src/snippet.ts CHANGED
@@ -40,15 +40,21 @@ export interface ParsedSnippet {
40
40
  }
41
41
 
42
42
  type Node =
43
- | { kind: 'text'; text: string; transform?: SnippetTransform }
43
+ // `indent` marks the value of a variable whose lines after the first take the indentation of the line it starts on.
44
+ | { kind: 'text'; text: string; transform?: SnippetTransform; indent?: true }
44
45
  | { kind: 'field'; index: number; body?: Node[]; choices?: string[]; transform?: SnippetTransform }
45
46
  | { kind: 'variable'; name: string; body?: Node[]; transform?: SnippetTransform };
46
47
 
47
48
  export type SnippetVariableResolver = (name: string) => string | undefined;
48
49
 
50
+ export interface SnippetRenderOptions {
51
+ /* The way VS Code treats `TM_SELECTED_TEXT`: the lines of a value after its first take the leading whitespace of the output line the variable starts on. */
52
+ readonly indentVariables?: boolean;
53
+ }
54
+
49
55
  export interface SnippetTemplate {
50
56
  readonly source: string;
51
- render(resolve?: SnippetVariableResolver, variableTransforms?: readonly string[]): ParsedSnippet;
57
+ render(resolve?: SnippetVariableResolver, variableTransforms?: readonly string[], options?: SnippetRenderOptions): ParsedSnippet;
52
58
  }
53
59
 
54
60
  type FieldNode = Extract<Node, { kind: 'field' }>;
@@ -235,6 +241,13 @@ class SnippetParser {
235
241
  }
236
242
  }
237
243
 
244
+ /* The lines of a value after its first, behind the whitespace the last line of `before` starts with; blank lines stay blank. */
245
+ function indentLines(value: string, before: string): string {
246
+ const prefix = /^[\t ]*/.exec(before.slice(before.lastIndexOf('\n') + 1))![0];
247
+
248
+ return prefix === '' ? value : value.replace(/(\r\n|\r|\n)(?=[^\r\n])/g, `$1${prefix}`);
249
+ }
250
+
238
251
  function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly string[]): ParsedSnippet {
239
252
  const definitions = new Map<number, FieldNode>();
240
253
 
@@ -304,7 +317,8 @@ function renderSnippet(nodes: readonly Node[], variableTransforms?: readonly str
304
317
 
305
318
  if (node.kind === 'text') {
306
319
  const start = text.length;
307
- const value = node.transform === undefined || variableTransforms === undefined ? node.text : variableTransforms[transforms.length];
320
+ const given = node.transform === undefined || variableTransforms === undefined ? node.text : variableTransforms[transforms.length];
321
+ const value = node.indent === true && typeof given === 'string' ? indentLines(given, text) : given;
308
322
 
309
323
  if (typeof value !== 'string') {
310
324
  throw new RangeError('The variable transform count does not match the snippet.');
@@ -453,7 +467,7 @@ function variableIndices(nodes: readonly Node[]): ReadonlyMap<string, number> {
453
467
  );
454
468
  }
455
469
 
456
- function resolveVariables(nodes: readonly Node[], indices: ReadonlyMap<string, number>, resolve?: SnippetVariableResolver): Node[] {
470
+ function resolveVariables(nodes: readonly Node[], indices: ReadonlyMap<string, number>, resolve?: SnippetVariableResolver, indentVariables = false): Node[] {
457
471
  const unknown = new Map<string, { index: number; body: Node[]; defaulted: boolean; definition: Node }>();
458
472
  const values = new Map<string, string | undefined>();
459
473
 
@@ -513,7 +527,11 @@ function resolveVariables(nodes: readonly Node[], indices: ReadonlyMap<string, n
513
527
  return [{ kind: 'text', text: value ?? '', transform: node.transform }];
514
528
  }
515
529
 
516
- return value !== undefined && value !== '' ? [{ kind: 'text', text: value }] : normalize(node.body ?? [], visiting, depth + 1);
530
+ if (value !== undefined && value !== '') {
531
+ return [{ kind: 'text', text: value, ...(indentVariables ? { indent: true as const } : {}) }];
532
+ }
533
+
534
+ return normalize(node.body ?? [], visiting, depth + 1);
517
535
  }
518
536
 
519
537
  if (node.kind === 'field') {
@@ -565,13 +583,13 @@ export function compileSnippet(source: string): SnippetTemplate {
565
583
 
566
584
  return {
567
585
  source,
568
- render: (resolve, variableTransforms) => {
586
+ render: (resolve, variableTransforms, options) => {
569
587
  if (nodes === null) {
570
588
  return { text: source, stops: [], limited: true };
571
589
  }
572
590
 
573
591
  try {
574
- return renderSnippet(resolveVariables(nodes, indices, resolve), variableTransforms);
592
+ return renderSnippet(resolveVariables(nodes, indices, resolve, options?.indentVariables === true), variableTransforms);
575
593
  } catch (error) {
576
594
  if (!(error instanceof SnippetLimitError)) {
577
595
  throw error;
@@ -615,3 +633,60 @@ export function tabOrder(parsed: ParsedSnippet): SnippetStop[] {
615
633
 
616
634
  return [...visited, first.get(0) ?? { index: 0, start: parsed.text.length, end: parsed.text.length }];
617
635
  }
636
+
637
+ /* The names of the variables a snippet uses, the editable ones that are no built-in too. A source that does not parse names none. */
638
+ export function snippetVariables(source: string): ReadonlySet<string> {
639
+ const names = new Set<string>();
640
+
641
+ const scan = (nodes: readonly Node[]): void => {
642
+ for (const node of nodes) {
643
+ if (node.kind === 'variable') {
644
+ names.add(node.name);
645
+ }
646
+
647
+ if (node.kind !== 'text' && node.body !== undefined) {
648
+ scan(node.body);
649
+ }
650
+ }
651
+ };
652
+
653
+ if (source.length > MAX_SNIPPET_TEXT) {
654
+ return names;
655
+ }
656
+
657
+ try {
658
+ scan(new SnippetParser(source).parse());
659
+ } catch {
660
+ return new Set();
661
+ }
662
+
663
+ return names;
664
+ }
665
+
666
+ /* Why a snippet cannot be used, in English for a host to show, or null: it does not parse, or it is over the limits an insertion keeps. */
667
+ export function checkSnippet(source: string): string | null {
668
+ if (source.length > MAX_SNIPPET_TEXT) {
669
+ return `The text is longer than ${MAX_SNIPPET_TEXT} characters.`;
670
+ }
671
+
672
+ try {
673
+ new SnippetParser(source).parse();
674
+ } catch (error) {
675
+ if (error instanceof MalformedSnippetError) {
676
+ return 'A transform in the text is not valid.';
677
+ }
678
+
679
+ if (error instanceof SnippetLimitError) {
680
+ return 'The text has too many parts or is nested too deeply.';
681
+ }
682
+
683
+ throw error;
684
+ }
685
+
686
+ return compileSnippet(source).render().limited === true ? 'The text expands to more than a snippet can hold.' : null;
687
+ }
688
+
689
+ /* Escapes `\`, `$` and `}`, so the text reads back as itself in a snippet. */
690
+ export function escapeSnippetText(text: string): string {
691
+ return text.replace(/[\\$}]/g, '\\$&');
692
+ }
@@ -0,0 +1,85 @@
1
+ import type { CommentSyntax } from '@adecore/editor';
2
+ import { escapeSnippetText } from './snippet.ts';
3
+ import type { TemplateLanguage } from './templates.ts';
4
+
5
+ /* A row of Surround With that wraps the selection in a snippet. */
6
+ export interface Construct {
7
+ readonly id: string;
8
+
9
+ /* Code, so it is not translated. */
10
+ readonly label: string;
11
+
12
+ /* Snippet syntax; a tab is one indentation unit. */
13
+ readonly body: string;
14
+ }
15
+
16
+ const SELECTED = '$TM_SELECTED_TEXT';
17
+
18
+ function block(head: string, tail = ''): string {
19
+ return `${head} {\n\t${SELECTED}\n}${tail}`;
20
+ }
21
+
22
+ const JS_STATEMENTS: readonly Construct[] = [
23
+ { id: 'if', label: 'if', body: block('if ($1)') },
24
+ { id: 'ifElse', label: 'if / else', body: block('if ($1)', ' else {\n\t$2\n}') },
25
+ { id: 'while', label: 'while', body: block('while ($1)') },
26
+ { id: 'doWhile', label: 'do / while', body: `do {\n\t${SELECTED}\n} while ($1);` },
27
+ { id: 'for', label: 'for', body: block('for (let ${1:i} = 0; $1 < $2; $1++)') },
28
+ { id: 'tryCatch', label: 'try / catch', body: block('try', ' catch (${1:e}) {\n\t$0\n}') },
29
+ { id: 'tryFinally', label: 'try / finally', body: block('try', ' finally {\n\t$0\n}') },
30
+ { id: 'tryCatchFinally', label: 'try / catch / finally', body: block('try', ' catch (${1:e}) {\n\t$2\n} finally {\n\t$0\n}') },
31
+ { id: 'braces', label: '{ }', body: `{\n\t${SELECTED}\n}` },
32
+ { id: 'function', label: 'function', body: block('function ${1:name}()') }
33
+ ];
34
+
35
+ const PHP_STATEMENTS: readonly Construct[] = [
36
+ JS_STATEMENTS[0]!,
37
+ JS_STATEMENTS[1]!,
38
+ JS_STATEMENTS[2]!,
39
+ JS_STATEMENTS[3]!,
40
+ { id: 'for', label: 'for', body: block('for (\\$${1:i} = 0; \\$$1 < $2; \\$$1++)') },
41
+ { id: 'foreach', label: 'foreach', body: block('foreach (\\$${1:array} as \\$${2:value})') },
42
+ { id: 'tryCatch', label: 'try / catch', body: block('try', ' catch (${1:\\\\Exception} \\$${2:e}) {\n\t$0\n}') },
43
+ JS_STATEMENTS[6]!,
44
+ JS_STATEMENTS[7]!,
45
+ JS_STATEMENTS[9]!
46
+ ];
47
+
48
+ const EXPRESSIONS: readonly Construct[] = [
49
+ { id: 'parentheses', label: '(expr)', body: `(${SELECTED})` },
50
+ { id: 'not', label: '!(expr)', body: `!(${SELECTED})` }
51
+ ];
52
+
53
+ /* The statement surrounders of the first of the families that has any; JavaScript and TypeScript share theirs. */
54
+ export function statementConstructs(families: readonly TemplateLanguage[]): readonly Construct[] {
55
+ if (families.includes('javascript')) {
56
+ return JS_STATEMENTS;
57
+ }
58
+
59
+ return families.includes('php') ? PHP_STATEMENTS : [];
60
+ }
61
+
62
+ /* The surrounders of one expression, which JavaScript, TypeScript and PHP share. */
63
+ export function expressionConstructs(families: readonly TemplateLanguage[]): readonly Construct[] {
64
+ return families.includes('javascript') || families.includes('php') ? EXPRESSIONS : [];
65
+ }
66
+
67
+ /*
68
+ * The two comment pairs that fold a region, written with the language's own comments: the line comment, or the block
69
+ * pair where there is none. There is no space after the marker, so the output reads `//region Description`.
70
+ */
71
+ export function regionConstructs(syntax: CommentSyntax): readonly Construct[] {
72
+ const marker = syntax.line ?? syntax.block?.open;
73
+
74
+ if (marker === undefined) {
75
+ return [];
76
+ }
77
+
78
+ const start = escapeSnippetText(marker);
79
+ const end = syntax.line === null ? escapeSnippetText(syntax.block?.close ?? '') : '';
80
+
81
+ return [
82
+ { id: 'region', label: 'region...endregion', body: `${start}region \${1:Description}${end}\n${SELECTED}\n${start}endregion${end}` },
83
+ { id: 'editorFold', label: '<editor-fold...>', body: `${start}<editor-fold desc="\${1:Description}">${end}\n${SELECTED}\n${start}</editor-fold>${end}` }
84
+ ];
85
+ }