@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.
- package/dist/CallHierarchyPanel.js +177 -181
- package/dist/EditorContextMenu.js +118 -105
- package/dist/LanguagePopups.js +7 -0
- package/dist/PickPopup.js +245 -104
- package/dist/WrapCard.d.ts +8 -0
- package/dist/WrapCard.js +172 -0
- package/dist/call-hierarchy-model.d.ts +4 -2
- package/dist/call-hierarchy-model.js +20 -0
- package/dist/call-hierarchy.d.ts +3 -0
- package/dist/call-hierarchy.js +54 -22
- package/dist/clipboard-history.d.ts +31 -0
- package/dist/clipboard-history.js +70 -0
- package/dist/code-actions.d.ts +3 -1
- package/dist/code-actions.js +50 -10
- package/dist/code-vision.js +2 -3
- package/dist/completion.d.ts +28 -0
- package/dist/completion.js +83 -27
- package/dist/context-menu-rows.d.ts +1 -1
- package/dist/context-menu-rows.js +4 -0
- package/dist/definition-link.js +15 -5
- package/dist/document-colors.d.ts +24 -0
- package/dist/document-colors.js +194 -0
- package/dist/document-links.d.ts +22 -0
- package/dist/document-links.js +133 -0
- package/dist/document-query.d.ts +15 -0
- package/dist/document-query.js +54 -0
- package/dist/edit-locations.d.ts +20 -0
- package/dist/edit-locations.js +136 -0
- package/dist/editor-language.d.ts +23 -1
- package/dist/editor-language.js +83 -2
- package/dist/emmet.d.ts +13 -0
- package/dist/emmet.js +62 -0
- package/dist/folding.js +2 -3
- package/dist/format-on-type.js +3 -5
- package/dist/highlights.js +2 -3
- package/dist/history.d.ts +7 -0
- package/dist/history.js +79 -1
- package/dist/host-types.d.ts +10 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +10 -0
- package/dist/inlay-hints.js +2 -2
- package/dist/inline-completion.d.ts +39 -0
- package/dist/inline-completion.js +221 -0
- package/dist/key-priority.d.ts +9 -5
- package/dist/key-priority.js +11 -6
- package/dist/linked-editing.d.ts +26 -0
- package/dist/linked-editing.js +215 -0
- package/dist/live-templates.d.ts +16 -0
- package/dist/live-templates.js +289 -0
- package/dist/locales/en.json +61 -2
- package/dist/locales/nl.json +61 -2
- package/dist/markup-context.d.ts +55 -0
- package/dist/markup-context.js +408 -0
- package/dist/markup.d.ts +39 -0
- package/dist/markup.js +465 -0
- package/dist/navigation.js +4 -6
- package/dist/paste-history.d.ts +7 -0
- package/dist/paste-history.js +104 -0
- package/dist/peek.js +3 -4
- package/dist/pick.d.ts +21 -2
- package/dist/pick.js +152 -19
- package/dist/popups.d.ts +10 -0
- package/dist/popups.js +2 -1
- package/dist/postfix.d.ts +21 -0
- package/dist/postfix.js +212 -0
- package/dist/project-language.d.ts +20 -1
- package/dist/project-language.js +132 -18
- package/dist/rename.js +8 -11
- package/dist/rendered-docs.js +1 -2
- package/dist/selection-ranges.js +2 -3
- package/dist/shortcut-keys.d.ts +24 -0
- package/dist/signature.js +3 -6
- package/dist/snippet-insert.d.ts +10 -0
- package/dist/snippet-insert.js +217 -0
- package/dist/snippet-session.d.ts +2 -2
- package/dist/snippet-session.js +3 -3
- 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/symbol-picker.js +4 -6
- package/dist/symbols.js +22 -3
- 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/dist/testing.d.ts +23 -1
- package/dist/testing.js +114 -0
- package/package.json +7 -6
- package/src/CallHierarchyPanel.tsx +26 -11
- package/src/EditorContextMenu.tsx +7 -0
- package/src/LanguagePopups.tsx +4 -0
- package/src/PickPopup.tsx +47 -5
- package/src/WrapCard.tsx +64 -0
- package/src/call-hierarchy-model.ts +25 -3
- package/src/call-hierarchy.ts +62 -20
- package/src/clipboard-history.ts +102 -0
- package/src/code-actions.ts +48 -11
- package/src/code-vision.ts +3 -3
- package/src/completion.ts +123 -25
- package/src/context-menu-rows.ts +3 -2
- package/src/definition-link.ts +17 -5
- package/src/document-colors.ts +189 -0
- package/src/document-links.ts +129 -0
- package/src/document-query.ts +71 -0
- package/src/edit-locations.ts +162 -0
- package/src/editor-language.ts +86 -3
- package/src/emmet-module.d.ts +44 -0
- package/src/emmet.ts +73 -0
- package/src/folding.ts +3 -3
- package/src/format-on-type.ts +5 -5
- package/src/highlights.ts +3 -3
- package/src/history.ts +81 -1
- package/src/host-types.ts +17 -0
- package/src/index.ts +28 -3
- package/src/inlay-hints.ts +2 -2
- package/src/inline-completion.ts +221 -0
- package/src/key-priority.ts +11 -6
- package/src/linked-editing.ts +199 -0
- package/src/live-templates.ts +315 -0
- package/src/locales/en.json +61 -2
- package/src/locales/nl.json +61 -2
- package/src/markup-context.ts +387 -0
- package/src/markup.ts +377 -0
- package/src/navigation.ts +6 -6
- package/src/paste-history.ts +111 -0
- package/src/peek.ts +4 -4
- package/src/pick.ts +218 -20
- package/src/popups.ts +21 -1
- package/src/postfix.ts +224 -0
- package/src/project-language.ts +139 -19
- package/src/rename.ts +11 -11
- package/src/rendered-docs.ts +2 -2
- package/src/selection-ranges.ts +3 -3
- package/src/signature.ts +6 -6
- package/src/snippet-insert.ts +258 -0
- package/src/snippet-session.ts +9 -4
- package/src/snippet.ts +82 -7
- package/src/surround-constructs.ts +85 -0
- package/src/surround.ts +430 -0
- package/src/symbol-picker.ts +6 -6
- package/src/symbols.ts +30 -4
- package/src/template-builtins.ts +94 -0
- package/src/template-context.ts +65 -0
- package/src/templates.ts +197 -0
- package/src/testing.ts +126 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { realTimers } from './timers.js';
|
|
2
|
+
const METHOD = 'textDocument/linkedEditingRange';
|
|
3
|
+
/* How long the caret rests before the ranges around it are asked. */
|
|
4
|
+
const DELAY_MS = 100;
|
|
5
|
+
/* The server's word pattern, anchored, or null when it gave none or one that does not compile. */
|
|
6
|
+
export function wordPatternOf(ranges) {
|
|
7
|
+
if (ranges.wordPattern === undefined) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return new RegExp(`^(?:${ranges.wordPattern})$`, 'u');
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/*
|
|
17
|
+
* Ranges a server says are edited together, such as the names of a start tag and its end tag: a change inside
|
|
18
|
+
* one goes into every other, in the undo step of the key. The ranges are asked once the caret rests, and the link
|
|
19
|
+
* ends when an edit falls outside them, the text stops matching the server's word pattern, or there is more
|
|
20
|
+
* than one caret.
|
|
21
|
+
*/
|
|
22
|
+
export class LinkedEditingFeature {
|
|
23
|
+
language;
|
|
24
|
+
timers;
|
|
25
|
+
session = null;
|
|
26
|
+
timer = null;
|
|
27
|
+
controller = null;
|
|
28
|
+
revision = 0;
|
|
29
|
+
mirroring = false;
|
|
30
|
+
enabled = true;
|
|
31
|
+
constructor(language, timers = realTimers) {
|
|
32
|
+
this.language = language;
|
|
33
|
+
this.timers = timers;
|
|
34
|
+
const {
|
|
35
|
+
editor
|
|
36
|
+
} = language;
|
|
37
|
+
const offs = [editor.onTextChange(change => this.edited(change)), editor.onCaret(() => this.moved())];
|
|
38
|
+
language.onDispose(() => {
|
|
39
|
+
this.stop();
|
|
40
|
+
for (const off of offs) {
|
|
41
|
+
off();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
get supported() {
|
|
46
|
+
const {
|
|
47
|
+
service
|
|
48
|
+
} = this.language;
|
|
49
|
+
return this.enabled && service.linkedEditingRanges !== undefined && service.supports(METHOD, this.language.uri);
|
|
50
|
+
}
|
|
51
|
+
/* Whether ranges are linked now. */
|
|
52
|
+
get isActive() {
|
|
53
|
+
return this.session !== null;
|
|
54
|
+
}
|
|
55
|
+
/* Off, nothing is linked; on unless false. */
|
|
56
|
+
configure(settings) {
|
|
57
|
+
this.enabled = settings.enabled !== false;
|
|
58
|
+
if (!this.enabled) {
|
|
59
|
+
this.stop();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
stop() {
|
|
63
|
+
this.session = null;
|
|
64
|
+
this.timers.clear(this.timer);
|
|
65
|
+
this.controller?.abort();
|
|
66
|
+
}
|
|
67
|
+
caretOffset() {
|
|
68
|
+
const {
|
|
69
|
+
editor
|
|
70
|
+
} = this.language;
|
|
71
|
+
const selections = editor.getSelections();
|
|
72
|
+
if (selections.length !== 1) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const {
|
|
76
|
+
start,
|
|
77
|
+
end
|
|
78
|
+
} = selections[0];
|
|
79
|
+
return start.line === end.line && start.character === end.character ? editor.offsetAt(start) : null;
|
|
80
|
+
}
|
|
81
|
+
moved() {
|
|
82
|
+
if (this.mirroring) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// The caret moves before the text change of the same key is told, so the session is checked once both are in.
|
|
86
|
+
queueMicrotask(() => {
|
|
87
|
+
const offset = this.caretOffset();
|
|
88
|
+
if (offset !== null && this.session?.spans.some(span => span.from <= offset && offset <= span.to)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.stop();
|
|
92
|
+
if (offset !== null && this.supported) {
|
|
93
|
+
this.timer = this.timers.set(() => void this.ask(offset), DELAY_MS);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async ask(offset) {
|
|
98
|
+
const {
|
|
99
|
+
editor,
|
|
100
|
+
service,
|
|
101
|
+
uri
|
|
102
|
+
} = this.language;
|
|
103
|
+
const revision = this.revision;
|
|
104
|
+
const controller = new AbortController();
|
|
105
|
+
this.controller = controller;
|
|
106
|
+
try {
|
|
107
|
+
const ranges = await service.linkedEditingRanges(uri, editor.positionAt(offset), {
|
|
108
|
+
signal: controller.signal
|
|
109
|
+
});
|
|
110
|
+
if (ranges === null || ranges.ranges.length < 2 || revision !== this.revision || this.caretOffset() !== offset) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.session = {
|
|
114
|
+
spans: ranges.ranges.map(range => ({
|
|
115
|
+
from: editor.offsetAt(range.start),
|
|
116
|
+
to: editor.offsetAt(range.end)
|
|
117
|
+
})),
|
|
118
|
+
pattern: wordPatternOf(ranges)
|
|
119
|
+
};
|
|
120
|
+
} catch {
|
|
121
|
+
// No ranges is no link; the next rest of the caret asks again.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
edited(change) {
|
|
125
|
+
const revision = ++this.revision;
|
|
126
|
+
const session = this.session;
|
|
127
|
+
if (this.mirroring || session === null) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const edit = change.changes[0];
|
|
131
|
+
if (change.source === 'external' || change.changes.length !== 1 || edit === undefined || edit.range.start.line !== edit.range.end.line) {
|
|
132
|
+
this.stop();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const from = this.language.editor.offsetAt(edit.range.start);
|
|
136
|
+
const removed = edit.range.end.character - edit.range.start.character;
|
|
137
|
+
const own = session.spans.findIndex(span => span.from <= from && from + removed <= span.to);
|
|
138
|
+
if (own === -1) {
|
|
139
|
+
this.stop();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const delta = edit.text.length - removed;
|
|
143
|
+
const edited = session.spans[own];
|
|
144
|
+
session.spans = session.spans.map((span, index) => index === own ? {
|
|
145
|
+
from: span.from,
|
|
146
|
+
to: span.to + delta
|
|
147
|
+
} : span.from > edited.from ? {
|
|
148
|
+
from: span.from + delta,
|
|
149
|
+
to: span.to + delta
|
|
150
|
+
} : span);
|
|
151
|
+
queueMicrotask(() => this.mirror(own, revision));
|
|
152
|
+
}
|
|
153
|
+
mirror(own, revision) {
|
|
154
|
+
const session = this.session;
|
|
155
|
+
if (session === null || revision !== this.revision) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const {
|
|
159
|
+
editor
|
|
160
|
+
} = this.language;
|
|
161
|
+
const text = editor.getText();
|
|
162
|
+
const source = session.spans[own];
|
|
163
|
+
const value = text.slice(source.from, source.to);
|
|
164
|
+
if (session.pattern !== null && value !== '' && !session.pattern.test(value)) {
|
|
165
|
+
this.stop();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const others = session.spans.map((span, index) => ({
|
|
169
|
+
span,
|
|
170
|
+
index
|
|
171
|
+
})).filter(({
|
|
172
|
+
span,
|
|
173
|
+
index
|
|
174
|
+
}) => index !== own && text.slice(span.from, span.to) !== value);
|
|
175
|
+
if (others.length === 0) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this.mirroring = true;
|
|
179
|
+
try {
|
|
180
|
+
const applied = editor.applyEdits(others.map(({
|
|
181
|
+
span
|
|
182
|
+
}) => ({
|
|
183
|
+
range: {
|
|
184
|
+
start: editor.positionAt(span.from),
|
|
185
|
+
end: editor.positionAt(span.to)
|
|
186
|
+
},
|
|
187
|
+
text: value
|
|
188
|
+
})), {
|
|
189
|
+
mergeUndo: true
|
|
190
|
+
});
|
|
191
|
+
if (!applied) {
|
|
192
|
+
this.stop();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
} finally {
|
|
196
|
+
this.mirroring = false;
|
|
197
|
+
}
|
|
198
|
+
// Each replaced span moves every span after it by what its length changed.
|
|
199
|
+
const changed = others.map(({
|
|
200
|
+
span
|
|
201
|
+
}) => ({
|
|
202
|
+
at: span.from,
|
|
203
|
+
delta: value.length - (span.to - span.from)
|
|
204
|
+
})).sort((left, right) => left.at - right.at);
|
|
205
|
+
const shift = offset => offset + changed.filter(entry => entry.at < offset).reduce((total, entry) => total + entry.delta, 0);
|
|
206
|
+
session.spans = session.spans.map((span, index) => {
|
|
207
|
+
const from = shift(span.from);
|
|
208
|
+
const replaced = others.some(other => other.index === index);
|
|
209
|
+
return {
|
|
210
|
+
from,
|
|
211
|
+
to: replaced ? from + value.length : shift(span.to)
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
2
|
+
export declare class TemplatesFeature {
|
|
3
|
+
private readonly language;
|
|
4
|
+
private readonly postfix;
|
|
5
|
+
constructor(language: EditorLanguage);
|
|
6
|
+
expand(): boolean;
|
|
7
|
+
insert(): void;
|
|
8
|
+
saveSelection(): boolean;
|
|
9
|
+
private customKey;
|
|
10
|
+
private matchAtCarets;
|
|
11
|
+
private matchAt;
|
|
12
|
+
private expandAtCarets;
|
|
13
|
+
private run;
|
|
14
|
+
private rows;
|
|
15
|
+
private row;
|
|
16
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { identifierPrefix, isIdentifierCharacter } from './completion-model.js';
|
|
2
|
+
import { comparePositions } from './diagnostics-model.js';
|
|
3
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
4
|
+
import { textBeforeOf } from './line-text.js';
|
|
5
|
+
import { isShortcut } from './shortcut-keys.js';
|
|
6
|
+
import { PostfixFeature } from './postfix.js';
|
|
7
|
+
import { bodyWithIndentUnit, dedentLines, indentUnitOf, insertSnippet } from './snippet-insert.js';
|
|
8
|
+
import { escapeSnippetText } from './snippet.js';
|
|
9
|
+
import { SNIPPET_KIND, sortAfterServer, templateApplies, templateContextAt } from './template-context.js';
|
|
10
|
+
const TOAST_ID = 'language-templates';
|
|
11
|
+
const IDENTIFIER_START = /[\p{L}_$]/u;
|
|
12
|
+
/* Sorts the rows of a list of templates by group, then key. */
|
|
13
|
+
function grouped(template) {
|
|
14
|
+
return `${template.group ?? template.languages[0]}\u0000${template.key}`;
|
|
15
|
+
}
|
|
16
|
+
/* A key that starts in the middle of a word is no key, unless it starts with a character no word has. */
|
|
17
|
+
function startsInsideWord(before, index) {
|
|
18
|
+
return index > 0 && IDENTIFIER_START.test(before[index]) && isIdentifierCharacter(before[index - 1]);
|
|
19
|
+
}
|
|
20
|
+
/* The longest start of `key` that the text before the caret ends with, as a number of characters; 0 when none. */
|
|
21
|
+
function prefixLength(key, before) {
|
|
22
|
+
for (let length = Math.min(key.length, before.length); length >= 1; length--) {
|
|
23
|
+
if (before.endsWith(key.slice(0, length)) && !startsInsideWord(before, before.length - length)) {
|
|
24
|
+
return length;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
/*
|
|
30
|
+
* Live templates: a key typed before the caret and Tab expands a snippet, Insert Live Template lists the ones that
|
|
31
|
+
* apply, and Save as Live Template hands a selection to the host. The templates come from the registry of the
|
|
32
|
+
* project. Tab comes after a running snippet and before Emmet, and a key that starts before an Emmet abbreviation
|
|
33
|
+
* wins over it, as in IntelliJ.
|
|
34
|
+
*/
|
|
35
|
+
export class TemplatesFeature {
|
|
36
|
+
language;
|
|
37
|
+
postfix;
|
|
38
|
+
constructor(language) {
|
|
39
|
+
this.language = language;
|
|
40
|
+
this.postfix = new PostfixFeature(language);
|
|
41
|
+
language.onDispose(language.onKey(event => {
|
|
42
|
+
if (event.key === 'Tab' && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey && !event.isComposing) {
|
|
43
|
+
return this.expand();
|
|
44
|
+
}
|
|
45
|
+
if (isShortcut(language.shortcuts.insertLiveTemplate, event)) {
|
|
46
|
+
this.insert();
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (isShortcut(language.shortcuts.saveAsLiveTemplate, event)) {
|
|
50
|
+
this.saveSelection();
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}, KEY_PRIORITY.templates));
|
|
55
|
+
language.onDispose(language.completion.addSource({
|
|
56
|
+
items: request => [...this.rows(request.position), ...this.postfix.rows(request.position)]
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
/*
|
|
60
|
+
* Expands the template whose key ends at the caret, or lists them when several share the key. False where Tab has
|
|
61
|
+
* nothing to expand, and where the key is an Emmet abbreviation, which the markup feature expands on the same Tab.
|
|
62
|
+
*/
|
|
63
|
+
expand() {
|
|
64
|
+
const {
|
|
65
|
+
editor,
|
|
66
|
+
completion
|
|
67
|
+
} = this.language;
|
|
68
|
+
const selections = editor.getSelections();
|
|
69
|
+
if (editor.isReadOnly?.() === true || selections.some(selection => comparePositions(selection.start, selection.end) !== 0)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const match = this.matchAtCarets(selections.map(selection => selection.start));
|
|
73
|
+
const custom = selections.length === 1 ? this.customKey() : null;
|
|
74
|
+
if (custom !== null && (match === null || comparePositions(match.start, custom.start) >= 0)) {
|
|
75
|
+
return custom.expand?.() ?? false;
|
|
76
|
+
}
|
|
77
|
+
if (match === null) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (match.templates.length === 1) {
|
|
81
|
+
this.expandAtCarets(match.templates[0], match.start);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
completion.showItems(match.templates.map(template => this.row(template, template.key, grouped(template), {
|
|
85
|
+
start: match.start,
|
|
86
|
+
end: editor.getCaret()
|
|
87
|
+
}, () => this.expandAtCarets(template, match.start))), match.start);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
/* Lists the templates that apply, only the ones whose key or description fits the word before the caret when some do. */
|
|
91
|
+
insert() {
|
|
92
|
+
const {
|
|
93
|
+
editor,
|
|
94
|
+
completion,
|
|
95
|
+
project
|
|
96
|
+
} = this.language;
|
|
97
|
+
if (editor.isReadOnly?.() === true) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const selection = editor.getSelection();
|
|
101
|
+
const selected = comparePositions(selection.start, selection.end) !== 0;
|
|
102
|
+
const caret = editor.getCaret();
|
|
103
|
+
const before = textBeforeOf(editor, caret);
|
|
104
|
+
const word = selected ? '' : identifierPrefix(before);
|
|
105
|
+
const wordStart = {
|
|
106
|
+
line: caret.line,
|
|
107
|
+
character: caret.character - word.length
|
|
108
|
+
};
|
|
109
|
+
const context = templateContextAt(this.language, selected ? selection.start : wordStart);
|
|
110
|
+
const applicable = project.templates.live.filter(template => templateApplies(template, context));
|
|
111
|
+
if (applicable.length === 0) {
|
|
112
|
+
const title = project.i18n.t('editor:language.templates.none');
|
|
113
|
+
project.host.notify?.({
|
|
114
|
+
id: TOAST_ID,
|
|
115
|
+
kind: 'error',
|
|
116
|
+
title
|
|
117
|
+
});
|
|
118
|
+
editor.announce(title);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const keyed = template => word !== '' && prefixLength(template.key, before) > 0;
|
|
122
|
+
const described = template => word !== '' && (template.description?.toLowerCase().includes(word.toLowerCase()) ?? false);
|
|
123
|
+
const matching = applicable.filter(template => keyed(template) || described(template));
|
|
124
|
+
const start = matching.length === 0 ? caret : wordStart;
|
|
125
|
+
const text = selected ? editor.textInRange(selection) : '';
|
|
126
|
+
const rows = (matching.length === 0 ? applicable : matching).map(template => this.row(template, keyed(template) || matching.length === 0 ? template.key : word, grouped(template), {
|
|
127
|
+
start,
|
|
128
|
+
end: caret
|
|
129
|
+
}, () => {
|
|
130
|
+
const range = selected ? selection : {
|
|
131
|
+
start,
|
|
132
|
+
end: editor.getCaret()
|
|
133
|
+
};
|
|
134
|
+
void this.run(template, [{
|
|
135
|
+
range,
|
|
136
|
+
selectedText: selected ? dedentLines(text) : undefined
|
|
137
|
+
}]);
|
|
138
|
+
}));
|
|
139
|
+
completion.showItems(rows, start);
|
|
140
|
+
}
|
|
141
|
+
/* Hands the selection to the host as a draft of a template; false without a selection or without a host that saves. */
|
|
142
|
+
saveSelection() {
|
|
143
|
+
const {
|
|
144
|
+
editor,
|
|
145
|
+
project
|
|
146
|
+
} = this.language;
|
|
147
|
+
const selection = editor.getSelection();
|
|
148
|
+
if (comparePositions(selection.start, selection.end) === 0) {
|
|
149
|
+
editor.announce(project.i18n.t('editor:language.templates.selectToSave'));
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if (project.host.saveAsLiveTemplate === undefined) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const context = templateContextAt(this.language, selection.start);
|
|
156
|
+
project.host.saveAsLiveTemplate({
|
|
157
|
+
body: escapeSnippetText(dedentLines(editor.textInRange(selection))),
|
|
158
|
+
languages: [context.families[0]],
|
|
159
|
+
places: [context.place]
|
|
160
|
+
});
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
/* The keys that compete with a live template for Tab: an Emmet abbreviation, then a postfix key. */
|
|
164
|
+
customKey() {
|
|
165
|
+
const abbreviation = this.language.markup.abbreviationAt();
|
|
166
|
+
return abbreviation === null ? this.postfix.key() : {
|
|
167
|
+
start: abbreviation,
|
|
168
|
+
expand: null
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/* The templates every caret has the same key of, ending at the caret, in a context they apply in. */
|
|
172
|
+
matchAtCarets(carets) {
|
|
173
|
+
const matches = carets.map(caret => this.matchAt(caret));
|
|
174
|
+
const primary = matches.at(-1);
|
|
175
|
+
if (primary === null || primary === undefined) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const templates = primary.templates.filter(template => matches.every(match => match?.templates.some(other => other.id === template.id) === true));
|
|
179
|
+
return templates.length === 0 ? null : {
|
|
180
|
+
start: primary.start,
|
|
181
|
+
templates
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/*
|
|
185
|
+
* The templates whose key is the longest one that ends at the caret, as IntelliJ looks them up: when the longest
|
|
186
|
+
* key found applies nowhere, no shorter key is tried.
|
|
187
|
+
*/
|
|
188
|
+
matchAt(caret) {
|
|
189
|
+
const {
|
|
190
|
+
editor,
|
|
191
|
+
project
|
|
192
|
+
} = this.language;
|
|
193
|
+
const {
|
|
194
|
+
live
|
|
195
|
+
} = project.templates;
|
|
196
|
+
const before = textBeforeOf(editor, caret);
|
|
197
|
+
const longest = live.reduce((length, template) => Math.max(length, template.key.length), 0);
|
|
198
|
+
for (let length = Math.min(longest, before.length); length >= 1; length--) {
|
|
199
|
+
const index = before.length - length;
|
|
200
|
+
const key = before.slice(index);
|
|
201
|
+
const found = live.filter(template => template.key === key);
|
|
202
|
+
if (found.length === 0 || startsInsideWord(before, index)) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const start = {
|
|
206
|
+
line: caret.line,
|
|
207
|
+
character: index
|
|
208
|
+
};
|
|
209
|
+
const context = templateContextAt(this.language, start);
|
|
210
|
+
const templates = found.filter(template => templateApplies(template, context));
|
|
211
|
+
return templates.length === 0 ? null : {
|
|
212
|
+
start,
|
|
213
|
+
templates
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
/* Replaces the text between `start` and the caret, as far as the primary caret is from it, at every caret. */
|
|
219
|
+
expandAtCarets(template, start) {
|
|
220
|
+
const {
|
|
221
|
+
editor
|
|
222
|
+
} = this.language;
|
|
223
|
+
const length = Math.max(0, editor.getCaret().character - start.character);
|
|
224
|
+
void this.run(template, editor.getSelections().map(selection => ({
|
|
225
|
+
range: {
|
|
226
|
+
start: {
|
|
227
|
+
line: selection.end.line,
|
|
228
|
+
character: Math.max(0, selection.end.character - length)
|
|
229
|
+
},
|
|
230
|
+
end: selection.end
|
|
231
|
+
}
|
|
232
|
+
})));
|
|
233
|
+
}
|
|
234
|
+
run(template, placements) {
|
|
235
|
+
return insertSnippet(this.language, bodyWithIndentUnit(template.body, indentUnitOf(this.language.editor)), placements);
|
|
236
|
+
}
|
|
237
|
+
/* The rows for the word before the caret, each a template whose key starts with it. */
|
|
238
|
+
rows(position) {
|
|
239
|
+
const {
|
|
240
|
+
editor,
|
|
241
|
+
project
|
|
242
|
+
} = this.language;
|
|
243
|
+
if (!project.templates.settings.liveInCompletion) {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
const before = textBeforeOf(editor, position);
|
|
247
|
+
if (identifierPrefix(before) === '') {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
const contexts = new Map();
|
|
251
|
+
return project.templates.live.flatMap(template => {
|
|
252
|
+
const length = prefixLength(template.key, before);
|
|
253
|
+
if (length === 0) {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
const start = {
|
|
257
|
+
line: position.line,
|
|
258
|
+
character: position.character - length
|
|
259
|
+
};
|
|
260
|
+
const context = contexts.get(length) ?? templateContextAt(this.language, start);
|
|
261
|
+
contexts.set(length, context);
|
|
262
|
+
return templateApplies(template, context) ? [this.row(template, template.key, sortAfterServer(template.key), {
|
|
263
|
+
start,
|
|
264
|
+
end: position
|
|
265
|
+
}, () => this.expandAtCarets(template, start))] : [];
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
row(template, filterText, sortText, range, accept) {
|
|
269
|
+
const item = {
|
|
270
|
+
label: template.key,
|
|
271
|
+
kind: SNIPPET_KIND,
|
|
272
|
+
detail: template.description,
|
|
273
|
+
documentation: {
|
|
274
|
+
kind: 'markdown',
|
|
275
|
+
value: `\`\`\`\n${template.body}\n\`\`\``
|
|
276
|
+
},
|
|
277
|
+
filterText,
|
|
278
|
+
sortText,
|
|
279
|
+
textEdit: {
|
|
280
|
+
range,
|
|
281
|
+
newText: template.key
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
return {
|
|
285
|
+
item,
|
|
286
|
+
accept
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
package/dist/locales/en.json
CHANGED
|
@@ -70,6 +70,12 @@
|
|
|
70
70
|
"delete": "Deletes"
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
|
+
"markup": {
|
|
74
|
+
"wrap": "Wrap with abbreviation",
|
|
75
|
+
"placeholder": "Abbreviation, such as div.card>p",
|
|
76
|
+
"apply": "Wrap",
|
|
77
|
+
"invalid": "Not an abbreviation"
|
|
78
|
+
},
|
|
73
79
|
"navigation": {
|
|
74
80
|
"unavailable": "Not available here",
|
|
75
81
|
"none": "Nothing found",
|
|
@@ -119,13 +125,30 @@
|
|
|
119
125
|
"site": "Call {{current}} of {{total}}",
|
|
120
126
|
"previousSite": "Previous call",
|
|
121
127
|
"nextSite": "Next call",
|
|
128
|
+
"typesUnavailable": "Type hierarchy unavailable in this file",
|
|
129
|
+
"typesNone": "No type here",
|
|
130
|
+
"typesFailed": "Could not look up types: {{message}}",
|
|
131
|
+
"typesLabel": "Type hierarchy",
|
|
132
|
+
"supertypes": "Supertypes",
|
|
133
|
+
"subtypes": "Subtypes",
|
|
134
|
+
"noSupertypes": "No supertypes",
|
|
135
|
+
"noSubtypes": "No subtypes",
|
|
122
136
|
"announce": {
|
|
123
137
|
"incoming_one": "{{count}} caller of {{name}}",
|
|
124
138
|
"incoming_other": "{{count}} callers of {{name}}",
|
|
125
139
|
"outgoing_one": "{{count}} callee of {{name}}",
|
|
126
|
-
"outgoing_other": "{{count}} callees of {{name}}"
|
|
140
|
+
"outgoing_other": "{{count}} callees of {{name}}",
|
|
141
|
+
"supertypes_one": "{{count}} supertype of {{name}}",
|
|
142
|
+
"supertypes_other": "{{count}} supertypes of {{name}}",
|
|
143
|
+
"subtypes_one": "{{count}} subtype of {{name}}",
|
|
144
|
+
"subtypes_other": "{{count}} subtypes of {{name}}"
|
|
127
145
|
}
|
|
128
146
|
},
|
|
147
|
+
"colors": {
|
|
148
|
+
"swatch": "Color {{color}}",
|
|
149
|
+
"title": "Color",
|
|
150
|
+
"other": "Pick another color…"
|
|
151
|
+
},
|
|
129
152
|
"codeVision": {
|
|
130
153
|
"usagesNone": "No usages",
|
|
131
154
|
"usages_one": "{{formatted}} usage",
|
|
@@ -192,6 +215,40 @@
|
|
|
192
215
|
"history": {
|
|
193
216
|
"recent": "Recent locations"
|
|
194
217
|
},
|
|
218
|
+
"pasteHistory": {
|
|
219
|
+
"title": "Choose Content to Paste",
|
|
220
|
+
"empty": "Nothing was copied yet"
|
|
221
|
+
},
|
|
222
|
+
"templates": {
|
|
223
|
+
"none": "No templates defined in this context",
|
|
224
|
+
"selectToSave": "Select the text to save as a live template"
|
|
225
|
+
},
|
|
226
|
+
"postfix": {
|
|
227
|
+
"expressions": "Expressions"
|
|
228
|
+
},
|
|
229
|
+
"surround": {
|
|
230
|
+
"title": "Surround With",
|
|
231
|
+
"selectTemplate": "Select Template",
|
|
232
|
+
"liveTemplates": "Live templates",
|
|
233
|
+
"configure": "Configure live templates…",
|
|
234
|
+
"comments": "{{construct}} Comments",
|
|
235
|
+
"none": "Couldn't find Surround With variants applicable to the current context",
|
|
236
|
+
"noTemplates": "No surround templates defined in this context",
|
|
237
|
+
"unwrap": {
|
|
238
|
+
"title": "Choose the statement to unwrap/remove",
|
|
239
|
+
"if": "Unwrap 'if...'",
|
|
240
|
+
"else": "Unwrap 'else...'",
|
|
241
|
+
"removeElse": "Remove 'else...'",
|
|
242
|
+
"for": "Unwrap 'for...'",
|
|
243
|
+
"while": "Unwrap 'while...'",
|
|
244
|
+
"do": "Unwrap 'do...'",
|
|
245
|
+
"try": "Unwrap 'try...'",
|
|
246
|
+
"removeCatch": "Remove 'catch...'",
|
|
247
|
+
"removeFinally": "Remove 'finally...'",
|
|
248
|
+
"braces": "Unwrap braces",
|
|
249
|
+
"tag": "Remove enclosing tag: {{name}}"
|
|
250
|
+
}
|
|
251
|
+
},
|
|
195
252
|
"announce": {
|
|
196
253
|
"problem": "{{severity}}: {{message}}",
|
|
197
254
|
"severity": {
|
|
@@ -236,10 +293,12 @@
|
|
|
236
293
|
"goToTypeDefinition": "Go to type definition",
|
|
237
294
|
"goToImplementation": "Go to implementation",
|
|
238
295
|
"callHierarchy": "Call hierarchy",
|
|
296
|
+
"typeHierarchy": "Type hierarchy",
|
|
239
297
|
"peekReferences": "Peek references",
|
|
240
298
|
"renameSymbol": "Rename symbol",
|
|
241
299
|
"codeActions": "Code actions",
|
|
242
|
-
"formatDocument": "Format document"
|
|
300
|
+
"formatDocument": "Format document",
|
|
301
|
+
"formatSelection": "Format selection"
|
|
243
302
|
},
|
|
244
303
|
"find": {
|
|
245
304
|
"label": "Find",
|