@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
package/dist/markup.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { abbreviationBefore, expandAbbreviation } from './emmet.js';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
3
|
+
import { lineTextOf } from './line-text.js';
|
|
4
|
+
import { isVoidElement, markupPlaceAt, markupSyntaxOf, tagPairAt } from './markup-context.js';
|
|
5
|
+
import { isShortcut } from './shortcut-keys.js';
|
|
6
|
+
import { parseSnippet } from './snippet.js';
|
|
7
|
+
import { indentSnippet } from './snippet-indent.js';
|
|
8
|
+
import { positionInInsertion } from './snippet-session.js';
|
|
9
|
+
const NAME = /^[\w:.-]*$/;
|
|
10
|
+
const NAME_BEFORE = /<\/?[\w:.-]*$/;
|
|
11
|
+
/*
|
|
12
|
+
* Writing HTML, XML, the template of a Vue file and the markup of PHP: Emmet abbreviations, end tags that
|
|
13
|
+
* write themselves and tag names that change in pairs. The edits it adds join the undo step of the key that
|
|
14
|
+
* caused them. It does nothing in other languages, in a document over `MAX_MARKUP_SCAN` characters, or with more
|
|
15
|
+
* than one caret.
|
|
16
|
+
*/
|
|
17
|
+
export class MarkupFeature {
|
|
18
|
+
language;
|
|
19
|
+
syntax;
|
|
20
|
+
settings = {
|
|
21
|
+
emmet: true,
|
|
22
|
+
closeTags: true,
|
|
23
|
+
linkedTags: true
|
|
24
|
+
};
|
|
25
|
+
link = null;
|
|
26
|
+
wrapping = null;
|
|
27
|
+
/* Raised by every change of the text, so a follow-up edit for an older text is dropped. */
|
|
28
|
+
revision = 0;
|
|
29
|
+
mirroring = false;
|
|
30
|
+
constructor(language) {
|
|
31
|
+
this.language = language;
|
|
32
|
+
this.syntax = markupSyntaxOf(language.languageId);
|
|
33
|
+
if (this.syntax === null) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const {
|
|
37
|
+
editor
|
|
38
|
+
} = language;
|
|
39
|
+
const offs = [language.onKey(event => this.key(event), KEY_PRIORITY.markup), editor.onTextChange(change => this.edited(change)), editor.onCaret(() => this.moved())];
|
|
40
|
+
language.onDispose(() => {
|
|
41
|
+
this.cancelWrap();
|
|
42
|
+
for (const off of offs) {
|
|
43
|
+
off();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/* Whether the language of the document is markup this feature writes. */
|
|
48
|
+
get supported() {
|
|
49
|
+
return this.syntax !== null;
|
|
50
|
+
}
|
|
51
|
+
configure(settings) {
|
|
52
|
+
this.settings = {
|
|
53
|
+
emmet: settings.emmet !== false,
|
|
54
|
+
closeTags: settings.closeTags !== false,
|
|
55
|
+
linkedTags: settings.linkedTags !== false
|
|
56
|
+
};
|
|
57
|
+
if (!this.settings.linkedTags) {
|
|
58
|
+
this.link = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/* Expands the abbreviation before the caret, its places to fill as a snippet; false when there is none, which leaves Tab its own meaning. */
|
|
62
|
+
expand() {
|
|
63
|
+
const found = this.abbreviation();
|
|
64
|
+
return found !== null && this.insert(found.range, found.expanded, found.line);
|
|
65
|
+
}
|
|
66
|
+
/* Where the abbreviation that `expand` would take starts, without expanding it; null under the conditions where `expand` does nothing. */
|
|
67
|
+
abbreviationAt() {
|
|
68
|
+
return this.abbreviation()?.range.start ?? null;
|
|
69
|
+
}
|
|
70
|
+
abbreviation() {
|
|
71
|
+
const {
|
|
72
|
+
syntax
|
|
73
|
+
} = this;
|
|
74
|
+
const {
|
|
75
|
+
editor
|
|
76
|
+
} = this.language;
|
|
77
|
+
if (syntax === null || !this.settings.emmet || !this.editable()) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const caret = editor.getCaret();
|
|
81
|
+
const line = lineTextOf(editor, caret.line);
|
|
82
|
+
const found = abbreviationBefore(line, caret.character);
|
|
83
|
+
if (found === null) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const range = {
|
|
87
|
+
start: {
|
|
88
|
+
line: caret.line,
|
|
89
|
+
character: found.start
|
|
90
|
+
},
|
|
91
|
+
end: {
|
|
92
|
+
line: caret.line,
|
|
93
|
+
character: found.end
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
if (markupPlaceAt(editor.getText(), editor.offsetAt(range.start), syntax)?.kind !== 'text') {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const expanded = expandAbbreviation(found.abbreviation, {
|
|
100
|
+
syntax,
|
|
101
|
+
indent: this.indent()
|
|
102
|
+
});
|
|
103
|
+
return expanded === null ? null : {
|
|
104
|
+
range,
|
|
105
|
+
expanded,
|
|
106
|
+
line
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/* Opens the field for an abbreviation to wrap the selection in, or the text of the caret's line without one. */
|
|
110
|
+
startWrap() {
|
|
111
|
+
const {
|
|
112
|
+
editor,
|
|
113
|
+
popups
|
|
114
|
+
} = this.language;
|
|
115
|
+
if (this.syntax === null || !this.editable(true)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const range = this.wrapTarget();
|
|
119
|
+
if (range === null) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
this.cancelWrap();
|
|
123
|
+
this.wrapping = editor.trackRange(range);
|
|
124
|
+
popups.setState({
|
|
125
|
+
wrap: {
|
|
126
|
+
anchor: range.start,
|
|
127
|
+
error: null
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
/* Wraps the text the field was opened over; false, with the reason in the field, when the abbreviation does not expand. */
|
|
133
|
+
submitWrap(abbreviation) {
|
|
134
|
+
const {
|
|
135
|
+
syntax
|
|
136
|
+
} = this;
|
|
137
|
+
const {
|
|
138
|
+
editor,
|
|
139
|
+
popups,
|
|
140
|
+
project
|
|
141
|
+
} = this.language;
|
|
142
|
+
const range = this.wrapping?.get() ?? null;
|
|
143
|
+
if (syntax === null || range === null) {
|
|
144
|
+
this.cancelWrap();
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const expanded = expandAbbreviation(abbreviation.trim(), {
|
|
148
|
+
syntax,
|
|
149
|
+
indent: this.indent(),
|
|
150
|
+
wrap: this.wrapLines(range)
|
|
151
|
+
});
|
|
152
|
+
if (expanded === null) {
|
|
153
|
+
popups.setState({
|
|
154
|
+
wrap: {
|
|
155
|
+
anchor: range.start,
|
|
156
|
+
error: project.i18n.t('editor:language.markup.invalid')
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
this.cancelWrap();
|
|
162
|
+
const inserted = this.insert(range, expanded, lineTextOf(editor, range.start.line));
|
|
163
|
+
editor.focus();
|
|
164
|
+
return inserted;
|
|
165
|
+
}
|
|
166
|
+
cancelWrap() {
|
|
167
|
+
this.wrapping?.dispose();
|
|
168
|
+
this.wrapping = null;
|
|
169
|
+
if (this.language.popups.getState().wrap !== null) {
|
|
170
|
+
this.language.popups.setState({
|
|
171
|
+
wrap: null
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
key(event) {
|
|
176
|
+
if (isShortcut(this.language.shortcuts.wrapWithAbbreviation, event)) {
|
|
177
|
+
return this.startWrap();
|
|
178
|
+
}
|
|
179
|
+
if (event.key !== 'Tab' || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.isComposing) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return this.expand();
|
|
183
|
+
}
|
|
184
|
+
editable(selection = false) {
|
|
185
|
+
const {
|
|
186
|
+
editor
|
|
187
|
+
} = this.language;
|
|
188
|
+
const selections = editor.getSelections();
|
|
189
|
+
if (editor.isReadOnly?.() === true || selections.length !== 1) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
const {
|
|
193
|
+
start,
|
|
194
|
+
end
|
|
195
|
+
} = selections[0];
|
|
196
|
+
return selection || start.line === end.line && start.character === end.character;
|
|
197
|
+
}
|
|
198
|
+
indent() {
|
|
199
|
+
const {
|
|
200
|
+
tabSize,
|
|
201
|
+
insertSpaces
|
|
202
|
+
} = this.language.editor.getIndentation();
|
|
203
|
+
return insertSpaces ? ' '.repeat(tabSize) : '\t';
|
|
204
|
+
}
|
|
205
|
+
/* The selection without the whitespace around it, or the text of the caret's line; null when that is blank. */
|
|
206
|
+
wrapTarget() {
|
|
207
|
+
const {
|
|
208
|
+
editor
|
|
209
|
+
} = this.language;
|
|
210
|
+
const selection = editor.getSelection();
|
|
211
|
+
const text = editor.getText();
|
|
212
|
+
const empty = selection.start.line === selection.end.line && selection.start.character === selection.end.character;
|
|
213
|
+
let from = empty ? editor.offsetAt({
|
|
214
|
+
line: selection.start.line,
|
|
215
|
+
character: 0
|
|
216
|
+
}) : editor.offsetAt(selection.start);
|
|
217
|
+
let to = empty ? from + lineTextOf(editor, selection.start.line).length : editor.offsetAt(selection.end);
|
|
218
|
+
while (from < to && /\s/.test(text[from])) {
|
|
219
|
+
from++;
|
|
220
|
+
}
|
|
221
|
+
while (to > from && /\s/.test(text[to - 1])) {
|
|
222
|
+
to--;
|
|
223
|
+
}
|
|
224
|
+
return from === to ? null : {
|
|
225
|
+
start: editor.positionAt(from),
|
|
226
|
+
end: editor.positionAt(to)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/* The lines of the wrapped text, without the indentation they share, which the expansion gives them again. */
|
|
230
|
+
wrapLines(range) {
|
|
231
|
+
const lines = this.language.editor.textInRange(range).split(/\r\n|\r|\n/);
|
|
232
|
+
const indents = lines.slice(1).flatMap(line => line.trim() === '' ? [] : [/^[\t ]*/.exec(line)[0].length]);
|
|
233
|
+
const shared = indents.length === 0 ? 0 : Math.min(...indents);
|
|
234
|
+
return lines.map((line, index) => index === 0 ? line : line.slice(Math.min(shared, /^[\t ]*/.exec(line)[0].length)));
|
|
235
|
+
}
|
|
236
|
+
insert(range, snippet, line) {
|
|
237
|
+
const {
|
|
238
|
+
editor,
|
|
239
|
+
snippets
|
|
240
|
+
} = this.language;
|
|
241
|
+
const parsed = indentSnippet(parseSnippet(snippet), line);
|
|
242
|
+
if (parsed.limited === true) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
if (!this.apply([{
|
|
246
|
+
range,
|
|
247
|
+
text: parsed.text
|
|
248
|
+
}])) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
if (parsed.stops.length > 0) {
|
|
252
|
+
snippets.begin(range.start, parsed.text, parsed.stops, parsed.fields);
|
|
253
|
+
} else {
|
|
254
|
+
editor.setCaret(positionInInsertion(range.start, parsed.text, parsed.text.length));
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
/* Through the snippet session while one runs, so an edit inside its field keeps it going. */
|
|
259
|
+
apply(changes, options) {
|
|
260
|
+
const {
|
|
261
|
+
editor,
|
|
262
|
+
snippets
|
|
263
|
+
} = this.language;
|
|
264
|
+
return snippets.isActive ? snippets.applyEdits(changes, snippets.token, [], options) : editor.applyEdits(changes, options);
|
|
265
|
+
}
|
|
266
|
+
edited(change) {
|
|
267
|
+
const revision = ++this.revision;
|
|
268
|
+
if (this.mirroring) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const edit = change.changes[0];
|
|
272
|
+
// Backspace and the word deletes are commands too; an undo of both names is two changes and lets go.
|
|
273
|
+
if (change.source === 'external' || change.changes.length !== 1 || edit === undefined || edit.range.start.line !== edit.range.end.line) {
|
|
274
|
+
this.link = null;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const from = this.language.editor.offsetAt(edit.range.start);
|
|
278
|
+
const removed = edit.range.end.character - edit.range.start.character;
|
|
279
|
+
this.follow(from, removed, edit.text, revision);
|
|
280
|
+
if (change.source !== 'input' || !this.settings.closeTags || removed !== 0 || this.language.editor.getSelections().length !== 1) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (edit.text === '>') {
|
|
284
|
+
queueMicrotask(() => this.closeTag(from, revision));
|
|
285
|
+
} else if (edit.text === '/') {
|
|
286
|
+
queueMicrotask(() => this.completeEndTag(from, revision));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/* Keeps the link on the name being typed and copies the name to its other tag once the edit is in. */
|
|
290
|
+
follow(from, removed, inserted, revision) {
|
|
291
|
+
const link = this.link;
|
|
292
|
+
if (link === null) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const {
|
|
296
|
+
own,
|
|
297
|
+
partner
|
|
298
|
+
} = link;
|
|
299
|
+
if (from < own.from || from + removed > own.to || !NAME.test(inserted)) {
|
|
300
|
+
this.link = null;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const delta = inserted.length - removed;
|
|
304
|
+
this.link = {
|
|
305
|
+
own: {
|
|
306
|
+
from: own.from,
|
|
307
|
+
to: own.to + delta
|
|
308
|
+
},
|
|
309
|
+
partner: partner.from > own.from ? {
|
|
310
|
+
from: partner.from + delta,
|
|
311
|
+
to: partner.to + delta
|
|
312
|
+
} : partner
|
|
313
|
+
};
|
|
314
|
+
queueMicrotask(() => this.mirror(revision));
|
|
315
|
+
}
|
|
316
|
+
mirror(revision) {
|
|
317
|
+
const link = this.link;
|
|
318
|
+
if (link === null || revision !== this.revision) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const {
|
|
322
|
+
editor
|
|
323
|
+
} = this.language;
|
|
324
|
+
const text = editor.getText();
|
|
325
|
+
const name = text.slice(link.own.from, link.own.to);
|
|
326
|
+
if (name === text.slice(link.partner.from, link.partner.to)) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const range = {
|
|
330
|
+
start: editor.positionAt(link.partner.from),
|
|
331
|
+
end: editor.positionAt(link.partner.to)
|
|
332
|
+
};
|
|
333
|
+
this.mirroring = true;
|
|
334
|
+
try {
|
|
335
|
+
if (!this.apply([{
|
|
336
|
+
range,
|
|
337
|
+
text: name
|
|
338
|
+
}], {
|
|
339
|
+
mergeUndo: true
|
|
340
|
+
})) {
|
|
341
|
+
this.link = null;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
} finally {
|
|
345
|
+
this.mirroring = false;
|
|
346
|
+
}
|
|
347
|
+
const delta = name.length - (link.partner.to - link.partner.from);
|
|
348
|
+
this.link = {
|
|
349
|
+
own: link.partner.from < link.own.from ? {
|
|
350
|
+
from: link.own.from + delta,
|
|
351
|
+
to: link.own.to + delta
|
|
352
|
+
} : link.own,
|
|
353
|
+
partner: {
|
|
354
|
+
from: link.partner.from,
|
|
355
|
+
to: link.partner.to + delta
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/* The `>` at `at` ended a start tag: its end tag goes after it, with the caret between the two. */
|
|
360
|
+
closeTag(at, revision) {
|
|
361
|
+
const {
|
|
362
|
+
editor
|
|
363
|
+
} = this.language;
|
|
364
|
+
const text = editor.getText();
|
|
365
|
+
if (revision !== this.revision || text[at] !== '>' || text[at - 1] === '/') {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const place = markupPlaceAt(text, at, this.syntax);
|
|
369
|
+
if (place?.kind !== 'tag' || place.tag.closing || place.tag.inValue || place.tag.name === '' || isVoidElement(place.tag.name, this.syntax)) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const end = `</${place.tag.name}>`;
|
|
373
|
+
if (text.startsWith(end, at + 1)) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const caret = editor.positionAt(at + 1);
|
|
377
|
+
this.apply([{
|
|
378
|
+
range: {
|
|
379
|
+
start: caret,
|
|
380
|
+
end: caret
|
|
381
|
+
},
|
|
382
|
+
text: end
|
|
383
|
+
}], {
|
|
384
|
+
mergeUndo: true,
|
|
385
|
+
mapSelections: () => ({
|
|
386
|
+
start: caret,
|
|
387
|
+
end: caret
|
|
388
|
+
})
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/* The `/` at `at` follows a `<` in text: the end tag of the innermost open element is written out. */
|
|
392
|
+
completeEndTag(at, revision) {
|
|
393
|
+
const {
|
|
394
|
+
editor
|
|
395
|
+
} = this.language;
|
|
396
|
+
const text = editor.getText();
|
|
397
|
+
if (revision !== this.revision || text[at] !== '/' || text[at - 1] !== '<') {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const place = markupPlaceAt(text, at - 1, this.syntax);
|
|
401
|
+
const element = place?.kind === 'text' ? place.open.at(-1) : undefined;
|
|
402
|
+
if (element === undefined) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const position = editor.positionAt(at + 1);
|
|
406
|
+
const closed = text[at + 1] === '>';
|
|
407
|
+
const completion = closed ? element.name : `${element.name}>`;
|
|
408
|
+
const after = {
|
|
409
|
+
line: position.line,
|
|
410
|
+
character: position.character + element.name.length + 1
|
|
411
|
+
};
|
|
412
|
+
this.apply([{
|
|
413
|
+
range: {
|
|
414
|
+
start: position,
|
|
415
|
+
end: position
|
|
416
|
+
},
|
|
417
|
+
text: completion
|
|
418
|
+
}], {
|
|
419
|
+
mergeUndo: true,
|
|
420
|
+
mapSelections: () => ({
|
|
421
|
+
start: after,
|
|
422
|
+
end: after
|
|
423
|
+
})
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
/* Links the tag name the caret moved into with its other tag, once the edit that moved it is in. */
|
|
427
|
+
moved() {
|
|
428
|
+
if (!this.settings.linkedTags || this.mirroring) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
queueMicrotask(() => {
|
|
432
|
+
this.link = this.linkAtCaret();
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
linkAtCaret() {
|
|
436
|
+
const {
|
|
437
|
+
editor
|
|
438
|
+
} = this.language;
|
|
439
|
+
if (!this.editable()) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
const caret = editor.getCaret();
|
|
443
|
+
const offset = editor.offsetAt(caret);
|
|
444
|
+
const link = this.link;
|
|
445
|
+
if (link !== null && link.own.from <= offset && offset <= link.own.to) {
|
|
446
|
+
return link;
|
|
447
|
+
}
|
|
448
|
+
const line = lineTextOf(editor, caret.line);
|
|
449
|
+
if (!NAME_BEFORE.test(line.slice(0, caret.character))) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
const pair = tagPairAt(editor.getText(), offset, this.syntax);
|
|
453
|
+
if (pair === null) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
const inOpen = pair.open.from <= offset && offset <= pair.open.to;
|
|
457
|
+
return inOpen ? {
|
|
458
|
+
own: pair.open,
|
|
459
|
+
partner: pair.close
|
|
460
|
+
} : {
|
|
461
|
+
own: pair.close,
|
|
462
|
+
partner: pair.open
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
package/dist/navigation.js
CHANGED
|
@@ -75,11 +75,10 @@ export class NavigationFeature {
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
supports(kind) {
|
|
78
|
-
return this.language.
|
|
78
|
+
return this.language.service.supports(METHODS[kind], this.language.uri);
|
|
79
79
|
}
|
|
80
80
|
async go(kind, position = this.language.editor.getCaret()) {
|
|
81
81
|
const {
|
|
82
|
-
project,
|
|
83
82
|
uri,
|
|
84
83
|
editor
|
|
85
84
|
} = this.language;
|
|
@@ -89,7 +88,7 @@ export class NavigationFeature {
|
|
|
89
88
|
}
|
|
90
89
|
let result;
|
|
91
90
|
try {
|
|
92
|
-
result = await REQUESTS[kind](
|
|
91
|
+
result = await REQUESTS[kind](this.language.service, uri, position);
|
|
93
92
|
} catch (error) {
|
|
94
93
|
if (!(error instanceof StaleResultError)) {
|
|
95
94
|
this.tell(this.say('failed', {
|
|
@@ -118,7 +117,6 @@ export class NavigationFeature {
|
|
|
118
117
|
*/
|
|
119
118
|
async goToName(name, anchor, own) {
|
|
120
119
|
const {
|
|
121
|
-
project,
|
|
122
120
|
uri,
|
|
123
121
|
editor
|
|
124
122
|
} = this.language;
|
|
@@ -126,11 +124,11 @@ export class NavigationFeature {
|
|
|
126
124
|
this.language.goTo(own);
|
|
127
125
|
return;
|
|
128
126
|
}
|
|
129
|
-
if (!
|
|
127
|
+
if (!this.language.service.supports('workspace/symbol', uri)) {
|
|
130
128
|
return;
|
|
131
129
|
}
|
|
132
130
|
try {
|
|
133
|
-
const places = placesOfName(await
|
|
131
|
+
const places = placesOfName(await this.language.service.workspaceSymbols(uri, name), name, editor.getText());
|
|
134
132
|
if (places.length === 1) {
|
|
135
133
|
this.language.goTo(places[0]);
|
|
136
134
|
} else if (places.length > 1) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { rememberCopy } from '@adecore/editor';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
3
|
+
import { isShortcut } from './shortcut-keys.js';
|
|
4
|
+
const LABEL_LENGTH = 80;
|
|
5
|
+
/* The first characters of a copy on one line, with each line break shown as a return sign. */
|
|
6
|
+
function labelOf(text) {
|
|
7
|
+
const flat = text.replace(/\r\n|\r|\n/g, '⏎');
|
|
8
|
+
return flat.length > LABEL_LENGTH ? `${flat.slice(0, LABEL_LENGTH)}…` : flat;
|
|
9
|
+
}
|
|
10
|
+
/*
|
|
11
|
+
* Paste from History: a list of what was copied in the editors of the project, newest first. Typing narrows it,
|
|
12
|
+
* Shift with the arrows chooses several entries, which paste as one, and Delete forgets entries.
|
|
13
|
+
*/
|
|
14
|
+
export class PasteHistoryFeature {
|
|
15
|
+
language;
|
|
16
|
+
constructor(language) {
|
|
17
|
+
this.language = language;
|
|
18
|
+
const off = language.onKey(event => {
|
|
19
|
+
if (!isShortcut(this.language.shortcuts.pasteFromHistory, event)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
this.open();
|
|
23
|
+
return true;
|
|
24
|
+
}, KEY_PRIORITY.pasteHistory);
|
|
25
|
+
language.onDispose(off);
|
|
26
|
+
}
|
|
27
|
+
open() {
|
|
28
|
+
const {
|
|
29
|
+
editor,
|
|
30
|
+
project
|
|
31
|
+
} = this.language;
|
|
32
|
+
const {
|
|
33
|
+
clipboard
|
|
34
|
+
} = project;
|
|
35
|
+
if (clipboard.entries.length === 0) {
|
|
36
|
+
editor.announce(project.i18n.t('editor:language.pasteHistory.empty'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const ids = new Map();
|
|
40
|
+
const entries = new Map();
|
|
41
|
+
const groups = () => [{
|
|
42
|
+
title: null,
|
|
43
|
+
rows: clipboard.entries.map(entry => {
|
|
44
|
+
const id = ids.get(entry) ?? String(ids.size);
|
|
45
|
+
ids.set(entry, id);
|
|
46
|
+
entries.set(id, entry);
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
label: labelOf(entry.text),
|
|
50
|
+
detail: '',
|
|
51
|
+
search: entry.text
|
|
52
|
+
};
|
|
53
|
+
})
|
|
54
|
+
}];
|
|
55
|
+
const rows = groups();
|
|
56
|
+
this.language.pick.open({
|
|
57
|
+
anchor: editor.getCaret(),
|
|
58
|
+
title: project.i18n.t('editor:language.pasteHistory.title'),
|
|
59
|
+
groups: rows,
|
|
60
|
+
active: rows[0].rows[0].id,
|
|
61
|
+
filter: true,
|
|
62
|
+
multiple: true,
|
|
63
|
+
accept: id => this.paste([entries.get(id)]),
|
|
64
|
+
acceptMany: chosen => this.paste(clipboard.entries.filter(entry => chosen.includes(ids.get(entry) ?? ''))),
|
|
65
|
+
preview: async id => ({
|
|
66
|
+
removed: [],
|
|
67
|
+
added: entries.get(id)?.text.split('\n') ?? [],
|
|
68
|
+
note: null,
|
|
69
|
+
kind: 'text'
|
|
70
|
+
}),
|
|
71
|
+
remove: chosen => {
|
|
72
|
+
for (const id of chosen) {
|
|
73
|
+
clipboard.remove(entries.get(id));
|
|
74
|
+
}
|
|
75
|
+
return groups();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/* Pastes entries in list order, joined by line breaks, as a paste of that text from the clipboard would. */
|
|
80
|
+
paste(chosen) {
|
|
81
|
+
const {
|
|
82
|
+
editor,
|
|
83
|
+
project
|
|
84
|
+
} = this.language;
|
|
85
|
+
const {
|
|
86
|
+
clipboard
|
|
87
|
+
} = project;
|
|
88
|
+
if (chosen.length === 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const text = chosen.map(entry => entry.text).join('\n');
|
|
92
|
+
const wholeLines = chosen.length === 1 && chosen[0].wholeLines;
|
|
93
|
+
if (chosen.length === 1) {
|
|
94
|
+
clipboard.promote(chosen[0]);
|
|
95
|
+
} else {
|
|
96
|
+
clipboard.add(text);
|
|
97
|
+
}
|
|
98
|
+
globalThis.navigator?.clipboard?.writeText(text).catch(() => undefined);
|
|
99
|
+
rememberCopy(text, wholeLines);
|
|
100
|
+
editor.paste?.(text, {
|
|
101
|
+
wholeLines
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
package/dist/peek.js
CHANGED
|
@@ -71,7 +71,7 @@ export class PeekFeature {
|
|
|
71
71
|
uri
|
|
72
72
|
} = this.language;
|
|
73
73
|
const method = KINDS[kind].method;
|
|
74
|
-
if (!
|
|
74
|
+
if (!this.language.service.supports(method, uri)) {
|
|
75
75
|
this.tell(this.say(KINDS[kind].unavailable));
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
@@ -147,17 +147,16 @@ export class PeekFeature {
|
|
|
147
147
|
}
|
|
148
148
|
async fetch(kind, position) {
|
|
149
149
|
const {
|
|
150
|
-
project,
|
|
151
150
|
uri
|
|
152
151
|
} = this.language;
|
|
153
152
|
if (kind === 'references') {
|
|
154
153
|
// Without the declaration, so the list holds what the usages count above a declaration counts.
|
|
155
154
|
return {
|
|
156
|
-
locations: (await
|
|
155
|
+
locations: (await this.language.service.references(uri, position, false)) ?? [],
|
|
157
156
|
targets: new Map()
|
|
158
157
|
};
|
|
159
158
|
}
|
|
160
|
-
const places = uniquePlaces(await
|
|
159
|
+
const places = uniquePlaces(await this.language.service.definition(uri, position));
|
|
161
160
|
const targets = new Map(places.flatMap(({
|
|
162
161
|
location,
|
|
163
162
|
target
|
package/dist/pick.d.ts
CHANGED
|
@@ -11,13 +11,21 @@ export interface PickSpec {
|
|
|
11
11
|
onTab?(backwards: boolean): void;
|
|
12
12
|
attempt?(id: string): Promise<string | null>;
|
|
13
13
|
preview?(id: string, signal: AbortSignal): Promise<PickPreview | null>;
|
|
14
|
+
readonly filter?: boolean;
|
|
15
|
+
readonly multiple?: boolean;
|
|
16
|
+
acceptMany?(ids: readonly string[]): void;
|
|
17
|
+
remove?(ids: readonly string[]): readonly PickGroup[];
|
|
14
18
|
}
|
|
15
19
|
export declare class PickFeature {
|
|
16
20
|
private readonly language;
|
|
17
21
|
private readonly timers;
|
|
18
22
|
private spec;
|
|
23
|
+
private groups;
|
|
19
24
|
private ids;
|
|
20
25
|
private active;
|
|
26
|
+
private query;
|
|
27
|
+
private chosen;
|
|
28
|
+
private rangeAnchor;
|
|
21
29
|
private previewTimer;
|
|
22
30
|
private controller;
|
|
23
31
|
private refusals;
|
|
@@ -26,10 +34,21 @@ export declare class PickFeature {
|
|
|
26
34
|
get isOpen(): boolean;
|
|
27
35
|
open(spec: PickSpec): void;
|
|
28
36
|
close(): void;
|
|
29
|
-
move(delta: number): void;
|
|
30
|
-
|
|
37
|
+
move(delta: number, extend?: boolean): void;
|
|
38
|
+
private settle;
|
|
39
|
+
choose(id: string, modifiers?: {
|
|
40
|
+
readonly toggle?: boolean;
|
|
41
|
+
readonly range?: boolean;
|
|
42
|
+
}): void;
|
|
31
43
|
private activate;
|
|
44
|
+
private extendTo;
|
|
45
|
+
private toggle;
|
|
46
|
+
private takeActive;
|
|
47
|
+
private visibleGroups;
|
|
48
|
+
private requery;
|
|
49
|
+
private removeChosen;
|
|
32
50
|
private key;
|
|
51
|
+
private takeMnemonic;
|
|
33
52
|
private schedulePreview;
|
|
34
53
|
private publish;
|
|
35
54
|
}
|