@adecore/editor-react 0.43.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.
- package/dist/PickPopup.js +94 -73
- package/dist/completion.d.ts +28 -0
- package/dist/completion.js +76 -15
- package/dist/editor-language.d.ts +4 -0
- package/dist/editor-language.js +6 -0
- package/dist/host-types.d.ts +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/key-priority.d.ts +1 -0
- package/dist/key-priority.js +2 -0
- package/dist/live-templates.d.ts +16 -0
- package/dist/live-templates.js +289 -0
- package/dist/locales/en.json +30 -0
- package/dist/locales/nl.json +30 -0
- package/dist/markup-context.d.ts +18 -0
- package/dist/markup-context.js +122 -5
- package/dist/markup.d.ts +3 -0
- package/dist/markup.js +16 -4
- package/dist/pick.d.ts +1 -0
- package/dist/pick.js +11 -1
- package/dist/popups.d.ts +1 -0
- package/dist/postfix.d.ts +21 -0
- package/dist/postfix.js +212 -0
- package/dist/project-language.d.ts +2 -0
- package/dist/project-language.js +3 -0
- package/dist/shortcut-keys.d.ts +5 -0
- package/dist/snippet-insert.d.ts +10 -0
- package/dist/snippet-insert.js +217 -0
- package/dist/snippet.d.ts +7 -1
- package/dist/snippet.js +65 -8
- package/dist/surround-constructs.d.ts +10 -0
- package/dist/surround-constructs.js +100 -0
- package/dist/surround.d.ts +18 -0
- package/dist/surround.js +399 -0
- package/dist/template-builtins.d.ts +3 -0
- package/dist/template-builtins.js +46 -0
- package/dist/template-context.d.ts +12 -0
- package/dist/template-context.js +56 -0
- package/dist/templates.d.ts +59 -0
- package/dist/templates.js +103 -0
- package/package.json +6 -6
- package/src/PickPopup.tsx +7 -0
- package/src/completion.ts +111 -13
- package/src/editor-language.ts +6 -0
- package/src/host-types.ts +7 -0
- package/src/index.ts +16 -0
- package/src/key-priority.ts +2 -0
- package/src/live-templates.ts +315 -0
- package/src/locales/en.json +30 -0
- package/src/locales/nl.json +30 -0
- package/src/markup-context.ts +131 -6
- package/src/markup.ts +15 -5
- package/src/pick.ts +17 -1
- package/src/popups.ts +2 -0
- package/src/postfix.ts +224 -0
- package/src/project-language.ts +3 -0
- package/src/snippet-insert.ts +258 -0
- package/src/snippet.ts +82 -7
- package/src/surround-constructs.ts +85 -0
- package/src/surround.ts +430 -0
- package/src/template-builtins.ts +94 -0
- package/src/template-context.ts +65 -0
- 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.
|
|
3
|
+
"version": "0.44.0",
|
|
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.
|
|
66
|
-
"@adecore/lsp": "0.
|
|
67
|
-
"@adecore/merge": "0.
|
|
65
|
+
"@adecore/editor": "0.44.0",
|
|
66
|
+
"@adecore/lsp": "0.44.0",
|
|
67
|
+
"@adecore/merge": "0.44.0",
|
|
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.
|
|
76
|
+
"@adecore/ui": "^0.44.0",
|
|
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.
|
|
83
|
+
"@adecore/ui": "0.44.0",
|
|
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
|
-
|
|
732
|
-
|
|
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
|
-
|
|
735
|
-
|
|
736
|
-
|
|
814
|
+
if (request !== this.request || this.controller !== controller || controller.signal.aborted) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
737
817
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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 (
|
|
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
|
|
package/src/editor-language.ts
CHANGED
|
@@ -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';
|
package/src/key-priority.ts
CHANGED
|
@@ -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,
|