@adecore/editor-react 0.43.0 → 0.44.1

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 (63) hide show
  1. package/dist/PickPopup.js +94 -73
  2. package/dist/completion.d.ts +28 -0
  3. package/dist/completion.js +76 -15
  4. package/dist/editor-language.d.ts +4 -0
  5. package/dist/editor-language.js +6 -0
  6. package/dist/host-types.d.ts +4 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +2 -0
  9. package/dist/key-priority.d.ts +1 -0
  10. package/dist/key-priority.js +2 -0
  11. package/dist/live-templates.d.ts +16 -0
  12. package/dist/live-templates.js +289 -0
  13. package/dist/locales/en.json +30 -0
  14. package/dist/locales/nl.json +30 -0
  15. package/dist/markup-context.d.ts +18 -0
  16. package/dist/markup-context.js +122 -5
  17. package/dist/markup.d.ts +3 -0
  18. package/dist/markup.js +16 -4
  19. package/dist/pick.d.ts +1 -0
  20. package/dist/pick.js +11 -1
  21. package/dist/popups.d.ts +1 -0
  22. package/dist/postfix.d.ts +21 -0
  23. package/dist/postfix.js +212 -0
  24. package/dist/project-language.d.ts +2 -0
  25. package/dist/project-language.js +3 -0
  26. package/dist/shortcut-keys.d.ts +5 -0
  27. package/dist/snippet-insert.d.ts +10 -0
  28. package/dist/snippet-insert.js +217 -0
  29. package/dist/snippet.d.ts +7 -1
  30. package/dist/snippet.js +65 -8
  31. package/dist/surround-constructs.d.ts +10 -0
  32. package/dist/surround-constructs.js +100 -0
  33. package/dist/surround.d.ts +18 -0
  34. package/dist/surround.js +399 -0
  35. package/dist/template-builtins.d.ts +3 -0
  36. package/dist/template-builtins.js +46 -0
  37. package/dist/template-context.d.ts +12 -0
  38. package/dist/template-context.js +56 -0
  39. package/dist/templates.d.ts +59 -0
  40. package/dist/templates.js +103 -0
  41. package/package.json +6 -6
  42. package/src/PickPopup.tsx +7 -0
  43. package/src/completion.ts +111 -13
  44. package/src/editor-language.ts +6 -0
  45. package/src/host-types.ts +7 -0
  46. package/src/index.ts +16 -0
  47. package/src/key-priority.ts +2 -0
  48. package/src/live-templates.ts +315 -0
  49. package/src/locales/en.json +30 -0
  50. package/src/locales/nl.json +30 -0
  51. package/src/markup-context.ts +131 -6
  52. package/src/markup.ts +15 -5
  53. package/src/pick.ts +17 -1
  54. package/src/popups.ts +2 -0
  55. package/src/postfix.ts +224 -0
  56. package/src/project-language.ts +3 -0
  57. package/src/snippet-insert.ts +258 -0
  58. package/src/snippet.ts +82 -7
  59. package/src/surround-constructs.ts +85 -0
  60. package/src/surround.ts +430 -0
  61. package/src/template-builtins.ts +94 -0
  62. package/src/template-context.ts +65 -0
  63. package/src/templates.ts +197 -0
@@ -0,0 +1,59 @@
1
+ import { BUILT_IN_LIVE_TEMPLATES, BUILT_IN_POSTFIX_TEMPLATES } from './template-builtins.ts';
2
+ export { BUILT_IN_LIVE_TEMPLATES, BUILT_IN_POSTFIX_TEMPLATES };
3
+ export { type TemplatesFeature } from './live-templates.ts';
4
+ export type TemplateLanguage = 'javascript' | 'typescript' | 'php' | 'css' | 'html' | 'xml' | 'other';
5
+ export type TemplatePlace = 'code' | 'comment' | 'string';
6
+ export interface LiveTemplate {
7
+ readonly id: string;
8
+ readonly key: string;
9
+ readonly body: string;
10
+ readonly description?: string;
11
+ readonly languages: readonly TemplateLanguage[];
12
+ readonly places?: readonly TemplatePlace[];
13
+ readonly group?: string;
14
+ }
15
+ export interface PostfixTemplate {
16
+ readonly id: string;
17
+ readonly key: string;
18
+ readonly body: string;
19
+ readonly description?: string;
20
+ readonly example?: string;
21
+ readonly languages: readonly ('javascript' | 'typescript' | 'php')[];
22
+ readonly expression?: 'any' | 'statement';
23
+ }
24
+ export interface TemplateSettings {
25
+ readonly liveInCompletion?: boolean;
26
+ readonly postfix?: boolean;
27
+ readonly postfixInCompletion?: boolean;
28
+ }
29
+ export interface LiveTemplateDraft {
30
+ readonly body: string;
31
+ readonly languages: readonly TemplateLanguage[];
32
+ readonly places: readonly TemplatePlace[];
33
+ }
34
+ export interface TemplateRegistryOptions {
35
+ readonly builtins?: boolean;
36
+ }
37
+ export interface TemplateUpdate {
38
+ readonly live?: readonly LiveTemplate[];
39
+ readonly postfix?: readonly PostfixTemplate[];
40
+ readonly disabled?: readonly string[];
41
+ readonly settings?: TemplateSettings;
42
+ }
43
+ export declare class TemplateRegistry {
44
+ private readonly builtins;
45
+ private readonly listeners;
46
+ private own;
47
+ private off;
48
+ private current;
49
+ private liveList;
50
+ private postfixList;
51
+ constructor(options?: TemplateRegistryOptions);
52
+ get live(): readonly LiveTemplate[];
53
+ get postfix(): readonly PostfixTemplate[];
54
+ get settings(): Required<TemplateSettings>;
55
+ set(update: TemplateUpdate): void;
56
+ onChange(listener: () => void): () => void;
57
+ private listed;
58
+ }
59
+ export declare function checkTemplate(template: LiveTemplate | PostfixTemplate, kind?: 'live' | 'postfix'): string | null;
@@ -0,0 +1,103 @@
1
+ import { BUILT_IN_LIVE_TEMPLATES, BUILT_IN_POSTFIX_TEMPLATES } from './template-builtins.js';
2
+ import { checkSnippet } from './snippet.js';
3
+ export { BUILT_IN_LIVE_TEMPLATES, BUILT_IN_POSTFIX_TEMPLATES };
4
+ export {} from './live-templates.js';
5
+ const MAX_KEY_LENGTH = 40;
6
+ const MAX_BODY_LENGTH = 20_000;
7
+ const POSTFIX_LANGUAGES = ['javascript', 'typescript', 'php'];
8
+ const LANGUAGES = ['javascript', 'typescript', 'php', 'css', 'html', 'xml', 'other'];
9
+ const PLACES = ['code', 'comment', 'string'];
10
+ /*
11
+ * The templates the editors of a project know: the built-in ones and the ones a host sets. A host that keeps
12
+ * templates feeds them through `set`, and the editors read the registry on every use.
13
+ */
14
+ export class TemplateRegistry {
15
+ builtins;
16
+ listeners = new Set();
17
+ own = {
18
+ live: [],
19
+ postfix: []
20
+ };
21
+ off = new Set();
22
+ current = {
23
+ liveInCompletion: true,
24
+ postfix: true,
25
+ postfixInCompletion: true
26
+ };
27
+ liveList;
28
+ postfixList;
29
+ constructor(options = {}) {
30
+ this.builtins = options.builtins !== false;
31
+ this.liveList = this.listed(BUILT_IN_LIVE_TEMPLATES, []);
32
+ this.postfixList = this.listed(BUILT_IN_POSTFIX_TEMPLATES, []);
33
+ }
34
+ /* The built-in templates that are not disabled, then the host's. */
35
+ get live() {
36
+ return this.liveList;
37
+ }
38
+ get postfix() {
39
+ return this.postfixList;
40
+ }
41
+ get settings() {
42
+ return this.current;
43
+ }
44
+ /* Replaces what the update names. A template that fails `checkTemplate` is dropped; this never throws. */
45
+ set(update) {
46
+ this.own = {
47
+ live: update.live === undefined ? this.own.live : update.live.filter(template => checkTemplate(template, 'live') === null),
48
+ postfix: update.postfix === undefined ? this.own.postfix : update.postfix.filter(template => checkTemplate(template, 'postfix') === null)
49
+ };
50
+ this.off = update.disabled === undefined ? this.off : new Set(update.disabled);
51
+ if (update.settings !== undefined) {
52
+ this.current = {
53
+ liveInCompletion: update.settings.liveInCompletion !== false,
54
+ postfix: update.settings.postfix !== false,
55
+ postfixInCompletion: update.settings.postfixInCompletion !== false
56
+ };
57
+ }
58
+ this.liveList = this.listed(BUILT_IN_LIVE_TEMPLATES, this.own.live);
59
+ this.postfixList = this.listed(BUILT_IN_POSTFIX_TEMPLATES, this.own.postfix);
60
+ for (const listener of [...this.listeners]) {
61
+ listener();
62
+ }
63
+ }
64
+ /* Calls `listener` after every `set`. Returns the function that stops it. */
65
+ onChange(listener) {
66
+ this.listeners.add(listener);
67
+ return () => {
68
+ this.listeners.delete(listener);
69
+ };
70
+ }
71
+ listed(builtins, own) {
72
+ return [...(this.builtins ? builtins.filter(template => !this.off.has(template.id)) : []), ...own];
73
+ }
74
+ }
75
+ /*
76
+ * Why a template cannot be used, in English for a host to show, or null. `kind` says which rules apply; without it a
77
+ * template with an `expression` or an `example` counts as a postfix one.
78
+ */
79
+ export function checkTemplate(template, kind) {
80
+ const postfix = kind === undefined ? 'expression' in template || 'example' in template : kind === 'postfix';
81
+ if (template.id === '') {
82
+ return 'The template has no id.';
83
+ }
84
+ if (template.key === '' || template.key.length > MAX_KEY_LENGTH) {
85
+ return `The key is one to ${MAX_KEY_LENGTH} characters.`;
86
+ }
87
+ if (postfix ? !/^[\p{L}\p{N}_$]+$/u.test(template.key) : /\s/.test(template.key)) {
88
+ return postfix ? 'The key holds letters, digits, underscores and dollar signs only.' : 'The key holds no whitespace.';
89
+ }
90
+ if (template.body.trim() === '') {
91
+ return 'The text is empty.';
92
+ }
93
+ if (template.body.length > MAX_BODY_LENGTH) {
94
+ return `The text is longer than ${MAX_BODY_LENGTH} characters.`;
95
+ }
96
+ if (template.languages.length === 0 || template.languages.some(language => !(postfix ? POSTFIX_LANGUAGES : LANGUAGES).includes(language))) {
97
+ return 'The languages are empty or not known.';
98
+ }
99
+ if (!postfix && template.places?.some(place => !PLACES.includes(place)) === true) {
100
+ return 'The places are not known.';
101
+ }
102
+ return checkSnippet(template.body);
103
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/editor-react",
3
- "version": "0.43.0",
3
+ "version": "0.44.1",
4
4
  "type": "module",
5
5
  "description": "Language-enabled editors, feature coordination and React popups over injected language services.",
6
6
  "license": "FSL-1.1-MIT",
@@ -62,9 +62,9 @@
62
62
  "test": "bun test --conditions=source"
63
63
  },
64
64
  "dependencies": {
65
- "@adecore/editor": "0.43.0",
66
- "@adecore/lsp": "0.43.0",
67
- "@adecore/merge": "0.43.0",
65
+ "@adecore/editor": "0.44.1",
66
+ "@adecore/lsp": "0.44.1",
67
+ "@adecore/merge": "0.44.1",
68
68
  "emmet": "^2.4.11",
69
69
  "lucide-react": "^1.61.0",
70
70
  "react-markdown": "^10.1.0",
@@ -73,14 +73,14 @@
73
73
  "zustand": "^5.0.16"
74
74
  },
75
75
  "peerDependencies": {
76
- "@adecore/ui": "^0.43.0",
76
+ "@adecore/ui": "^0.44.1",
77
77
  "i18next": "^26.4.2",
78
78
  "react": "^19.3.0",
79
79
  "react-dom": "^19.3.0",
80
80
  "react-i18next": "^17.0.15"
81
81
  },
82
82
  "devDependencies": {
83
- "@adecore/ui": "0.43.0",
83
+ "@adecore/ui": "0.44.1",
84
84
  "@types/bun": "^1.4.4",
85
85
  "@types/react": "^19.3.0",
86
86
  "@types/react-dom": "^19.3.0",
package/src/PickPopup.tsx CHANGED
@@ -105,6 +105,7 @@ export function PickPopup({ language, view, rect }: { language: EditorLanguage;
105
105
  }
106
106
 
107
107
  const activeId = optionIds.get(view.active);
108
+ const mnemonics = view.groups.some((group) => group.rows.some((row) => row.mnemonic !== undefined));
108
109
 
109
110
  useInputPopup(language, listId, activeId);
110
111
 
@@ -135,11 +136,17 @@ export function PickPopup({ language, view, rect }: { language: EditorLanguage;
135
136
  role="option"
136
137
  tabIndex={-1}
137
138
  aria-selected={row.id === view.active || view.chosen.includes(row.id)}
139
+ aria-keyshortcuts={row.mnemonic}
138
140
  data-active={row.id === view.active}
139
141
  data-chosen={view.chosen.includes(row.id)}
140
142
  className="flex h-7 w-full items-center gap-2 rounded-md px-2 text-left text-xs cursor-row data-[chosen=true]:bg-accent/15"
141
143
  onClick={(event) => language.pick.choose(row.id, { toggle: event.metaKey || event.ctrlKey, range: event.shiftKey })}
142
144
  >
145
+ {mnemonics && (
146
+ <span aria-hidden="true" className="w-3 shrink-0 text-center font-mono text-text-faint">
147
+ {row.mnemonic}
148
+ </span>
149
+ )}
143
150
  <span className={row.path ? 'shrink-0' : 'min-w-0 truncate'}>
144
151
  <MatchedLabel label={row.label} query={view.query} />
145
152
  </span>
package/src/completion.ts CHANGED
@@ -91,6 +91,30 @@ function mapCompletionEdits(
91
91
  return valid ? mapped : null;
92
92
  }
93
93
 
94
+ /** A row a source of the editor offers beside the language server's. */
95
+ export interface LocalCompletionItem {
96
+ /** The label, kind, detail, documentation, filter text and sort text of the row; its edit range is the typed prefix on the caret's line, ending at the caret. */
97
+ readonly item: CompletionItem;
98
+
99
+ /** Runs instead of the insertion: the source inserts the row itself. */
100
+ accept(): void;
101
+ }
102
+
103
+ /** What a source is asked for when the list is requested. */
104
+ export interface CompletionRequest {
105
+ readonly position: EditorPosition;
106
+
107
+ /** Whether the person asked for the list with the keyboard. */
108
+ readonly explicit: boolean;
109
+ readonly triggerCharacter?: string;
110
+ }
111
+
112
+ /** Rows that come from the editor itself, such as templates, merged into the list of the language server. */
113
+ export interface CompletionSource {
114
+ /** Synchronous and cheap; asked on every request of the list. */
115
+ items(request: CompletionRequest): readonly LocalCompletionItem[];
116
+ }
117
+
94
118
  /*
95
119
  * The suggestions that open while a word is typed. A single typed identifier character or a trigger
96
120
  * character of the server asks for the list; every character after that only filters the list that
@@ -129,6 +153,13 @@ export class CompletionFeature {
129
153
  private acceptSequence = 0;
130
154
  private disposed = false;
131
155
  private readonly resolved = new Map<CompletionItem, CompletionItem>();
156
+ private readonly sources = new Set<CompletionSource>();
157
+
158
+ /* The rows of the list that a source inserts itself. */
159
+ private locals = new Map<CompletionItem, LocalCompletionItem>();
160
+
161
+ /* Whether the list holds the rows `showItems` gave it and no request refreshes it. */
162
+ private shown = false;
132
163
 
133
164
  constructor(language: EditorLanguage, timers: Timers = realTimers) {
134
165
  this.language = language;
@@ -169,6 +200,30 @@ export class CompletionFeature {
169
200
  this.beginRequest({ triggerKind: 1 }, 0);
170
201
  }
171
202
 
203
+ /** Adds rows to every list, also in a document without a language server. Returns the function that takes the source away again. */
204
+ addSource(source: CompletionSource): () => void {
205
+ this.sources.add(source);
206
+
207
+ return () => {
208
+ this.sources.delete(source);
209
+ };
210
+ }
211
+
212
+ /** Opens the list with only these rows, the typed prefix starting at `start`, as Insert Live Template does. */
213
+ showItems(items: readonly LocalCompletionItem[], start: EditorPosition): void {
214
+ this.language.pick.close();
215
+ this.close();
216
+ this.shown = true;
217
+ this.locals = new Map(items.map((local) => [local.item, local]));
218
+ this.pool = items.map((local) => local.item);
219
+ this.incomplete = false;
220
+ this.start = start;
221
+ this.active = 0;
222
+ this.touched = false;
223
+ this.server = '';
224
+ this.refilter();
225
+ }
226
+
172
227
  move(delta: number): void {
173
228
  if (this.ranked.length === 0) {
174
229
  return;
@@ -199,6 +254,16 @@ export class CompletionFeature {
199
254
  return;
200
255
  }
201
256
 
257
+ const local = this.locals.get(entry.item);
258
+
259
+ if (local !== undefined) {
260
+ recentChoicesOf(this.language.languageId).record(entry.item);
261
+ this.close();
262
+ local.accept();
263
+
264
+ return;
265
+ }
266
+
202
267
  const { editor, project, uri, snippets } = this.language;
203
268
  const acceptSequence = ++this.acceptSequence;
204
269
  this.accepting?.abort();
@@ -584,6 +649,8 @@ export class CompletionFeature {
584
649
  this.activeKey = null;
585
650
  this.start = null;
586
651
  this.resolved.clear();
652
+ this.locals = new Map();
653
+ this.shown = false;
587
654
 
588
655
  if (this.language.popups.getState().completion !== null) {
589
656
  this.language.popups.setState({ completion: null });
@@ -659,6 +726,17 @@ export class CompletionFeature {
659
726
  private edited(change: EditorTextChange): void {
660
727
  this.textRevision++;
661
728
 
729
+ // The rows were given, so typing only filters them.
730
+ if (this.shown && this.isOpen) {
731
+ if (change.source === 'input' && change.changes.length === 1 && Array.from(change.changes[0]!.text).every(isIdentifierCharacter)) {
732
+ this.refilter();
733
+ } else {
734
+ this.close();
735
+ }
736
+
737
+ return;
738
+ }
739
+
662
740
  if (change.source !== 'input' || change.changes.length !== 1) {
663
741
  this.close();
664
742
 
@@ -711,7 +789,7 @@ export class CompletionFeature {
711
789
  private beginRequest(context: CompletionContext, delay: number): void {
712
790
  const { uri } = this.language;
713
791
 
714
- if (!this.language.service.supports(METHOD, uri)) {
792
+ if (!this.language.service.supports(METHOD, uri) && this.sources.size === 0) {
715
793
  return;
716
794
  }
717
795
 
@@ -726,26 +804,41 @@ export class CompletionFeature {
726
804
  this.controller?.abort();
727
805
  const controller = new AbortController();
728
806
  this.controller = controller;
729
- let normalized: ReturnType<typeof itemsOf>;
807
+ let normalized: ReturnType<typeof itemsOf> = { items: [], incomplete: false };
808
+ let failed = false;
730
809
 
731
- try {
732
- const result = await this.language.service.completion(uri, position, context, { signal: controller.signal });
810
+ if (this.language.service.supports(METHOD, uri)) {
811
+ try {
812
+ const result = await this.language.service.completion(uri, position, context, { signal: controller.signal });
733
813
 
734
- if (request !== this.request || this.controller !== controller || controller.signal.aborted) {
735
- return;
736
- }
814
+ if (request !== this.request || this.controller !== controller || controller.signal.aborted) {
815
+ return;
816
+ }
737
817
 
738
- normalized = itemsOf(result);
739
- } catch {
740
- if (request === this.request && this.controller === controller && !controller.signal.aborted) {
741
- this.close();
818
+ normalized = itemsOf(result);
819
+ } catch {
820
+ if (request !== this.request || this.controller !== controller || controller.signal.aborted) {
821
+ return;
822
+ }
823
+
824
+ failed = true;
742
825
  }
826
+ }
827
+
828
+ const locals = [...this.sources].flatMap((source) =>
829
+ source.items({ position, explicit: this.explicit && !refresh, triggerCharacter: context.triggerCharacter })
830
+ );
831
+
832
+ // A failed request still leaves the rows of the sources.
833
+ if (failed && locals.length === 0) {
834
+ this.close();
743
835
 
744
836
  return;
745
837
  }
746
838
 
747
839
  const { items, incomplete } = normalized;
748
- this.pool = items;
840
+ this.pool = [...items, ...locals.map((local) => local.item)];
841
+ this.locals = new Map(locals.map((local) => [local.item, local]));
749
842
  this.incomplete = incomplete;
750
843
  this.resolved.clear();
751
844
  this.server = project.host.serverNames?.(uri)[0] ?? '';
@@ -844,7 +937,12 @@ export class CompletionFeature {
844
937
  this.resolving?.abort();
845
938
  const entry = this.ranked[this.active];
846
939
 
847
- if (entry === undefined || this.resolved.has(entry.item) || this.language.service.providerOptions('completionItem/resolve', uri) === undefined) {
940
+ if (
941
+ entry === undefined ||
942
+ this.locals.has(entry.item) ||
943
+ this.resolved.has(entry.item) ||
944
+ this.language.service.providerOptions('completionItem/resolve', uri) === undefined
945
+ ) {
848
946
  return;
849
947
  }
850
948
 
@@ -34,7 +34,9 @@ import { SelectionRangesFeature } from './selection-ranges.ts';
34
34
  import { SemanticTokensFeature, type SemanticTokensOptions } from './semantic-tokens.ts';
35
35
  import { SignatureFeature } from './signature.ts';
36
36
  import { SnippetFeature } from './snippet-session.ts';
37
+ import { SurroundFeature } from './surround.ts';
37
38
  import { SymbolsFeature } from './symbols.ts';
39
+ import { TemplatesFeature } from './live-templates.ts';
38
40
  import type { Place } from './navigation-history.ts';
39
41
  import type { LanguageDocumentHandle, ProjectLanguage } from './project-language.ts';
40
42
 
@@ -75,6 +77,8 @@ export class EditorLanguage {
75
77
  readonly signature: SignatureFeature;
76
78
  readonly snippets: SnippetFeature;
77
79
  readonly markup: MarkupFeature;
80
+ readonly templates: TemplatesFeature;
81
+ readonly surround: SurroundFeature;
78
82
  readonly highlights: HighlightsFeature;
79
83
  readonly pick: PickFeature;
80
84
  readonly codeActions: CodeActionsFeature;
@@ -117,6 +121,8 @@ export class EditorLanguage {
117
121
  this.completion = new CompletionFeature(this, timers);
118
122
  this.snippets = new SnippetFeature(this, timers);
119
123
  this.markup = new MarkupFeature(this);
124
+ this.templates = new TemplatesFeature(this);
125
+ this.surround = new SurroundFeature(this);
120
126
  this.signature = new SignatureFeature(this, timers);
121
127
  this.highlights = new HighlightsFeature(this, timers);
122
128
  this.symbols = new SymbolsFeature(this, timers);
package/src/host-types.ts CHANGED
@@ -6,6 +6,7 @@ import type { ClipboardHistory } from './clipboard-history.ts';
6
6
  import type { ProjectFiles } from './workspace-edit.ts';
7
7
  import type { Place } from './navigation-history.ts';
8
8
  import type { ProblemSource } from './project-problems.ts';
9
+ import type { LiveTemplateDraft, TemplateRegistry } from './templates.ts';
9
10
  import type { ServerMessage } from '@adecore/lsp';
10
11
 
11
12
  export interface DiskText {
@@ -68,6 +69,12 @@ export interface LanguageHost {
68
69
  readonly keymap?: Keymap;
69
70
  /* What Paste from History lists. A new `ClipboardHistory` that lives as long as the project without one; pass one to choose its size or to keep its entries. */
70
71
  readonly clipboard?: ClipboardHistory;
72
+ /* What templates the editors of the project know. A registry of the built-in ones that lives as long as the project without one. */
73
+ readonly templates?: TemplateRegistry;
74
+ /* Save as Live Template: the host opens its own form with this draft. Without it the command does nothing. */
75
+ saveAsLiveTemplate?(draft: LiveTemplateDraft): void;
76
+ /* The last row of the template lists, "Configure live templates…"; the row is left out without it. */
77
+ openTemplateSettings?(): void;
71
78
  /* How many places Last Edit Location and Next Edit Location remember; 150 without one. */
72
79
  readonly editHistoryLimit?: number;
73
80
  /* The height in pixels of the list-and-preview area of the peek and the call hierarchy, so a person's drag outlives the panel; 180 without one. */
package/src/index.ts CHANGED
@@ -66,6 +66,7 @@ export type {
66
66
  WrapView
67
67
  } from './popups.ts';
68
68
  export { type MarkupFeature } from './markup.ts';
69
+ export { type SurroundFeature } from './surround.ts';
69
70
  export type { MarkupSettings } from './markup.ts';
70
71
  export { RowHost, LineActionHost } from './row-host.ts';
71
72
  export type { HostedRow, HostedAction } from './row-host.ts';
@@ -75,6 +76,21 @@ export type { Place } from './navigation-history.ts';
75
76
  export { EditLocations, type EditPlace } from './edit-locations.ts';
76
77
  export { ClipboardHistory, type ClipboardEntry, type ClipboardHistoryOptions } from './clipboard-history.ts';
77
78
  export { type PasteHistoryFeature } from './paste-history.ts';
79
+ export {
80
+ TemplateRegistry,
81
+ checkTemplate,
82
+ BUILT_IN_LIVE_TEMPLATES,
83
+ BUILT_IN_POSTFIX_TEMPLATES,
84
+ type TemplatesFeature,
85
+ type LiveTemplate,
86
+ type PostfixTemplate,
87
+ type LiveTemplateDraft,
88
+ type TemplateLanguage,
89
+ type TemplatePlace,
90
+ type TemplateSettings,
91
+ type TemplateRegistryOptions,
92
+ type TemplateUpdate
93
+ } from './templates.ts';
78
94
  export { openingPlace, viewStates, followViewStates, viewStateKey, forgetMovesFrom } from './view-state.ts';
79
95
  export type { ViewState, RevealLineRequest } from './view-state.ts';
80
96
  export { lspLanguageIdOf, shikiLanguageOf, shikiLanguageOfPath } from './language-ids.ts';
@@ -10,6 +10,8 @@ export const KEY_PRIORITY = {
10
10
  hover: 1400,
11
11
  completion: 1300,
12
12
  snippets: 1200,
13
+ // Between snippets and markup, so the spacing of a hundred bends here: Tab has to reach the templates after a running snippet and before Emmet.
14
+ templates: 1150,
13
15
  markup: 1100,
14
16
  signature: 1000,
15
17
  highlights: 900,