@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/project-language.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { disposeSnippetTransforms } from './snippet-transform-queue.js';
|
|
2
2
|
import i18next from 'i18next';
|
|
3
|
-
import { fileUriToPath, pathToFileUri } from '@adecore/lsp';
|
|
3
|
+
import { fileUriToPath, pathToFileUri, unservedLanguageService } from '@adecore/lsp';
|
|
4
|
+
import { ClipboardHistory } from './clipboard-history.js';
|
|
5
|
+
import { TemplateRegistry } from './templates.js';
|
|
6
|
+
import { EditLocations } from './edit-locations.js';
|
|
4
7
|
import { NavigationHistory } from './navigation-history.js';
|
|
5
8
|
import { ProjectProblems, serviceProblems } from './project-problems.js';
|
|
6
9
|
import { applyWorkspaceEdit } from './workspace-edit.js';
|
|
7
10
|
import { relativeTo } from './paths.js';
|
|
8
11
|
import { PANEL_BODY_HEIGHT } from './panel-size.js';
|
|
12
|
+
const UNSERVED = unservedLanguageService();
|
|
9
13
|
/* Sends one editor's edits to the document, holding them until the document is open. */
|
|
10
14
|
class DocumentSync {
|
|
11
15
|
ready;
|
|
@@ -86,6 +90,9 @@ export class ProjectLanguage {
|
|
|
86
90
|
folder;
|
|
87
91
|
problems;
|
|
88
92
|
history = new NavigationHistory();
|
|
93
|
+
edits;
|
|
94
|
+
clipboard;
|
|
95
|
+
templates;
|
|
89
96
|
holders = new Map();
|
|
90
97
|
files;
|
|
91
98
|
/* The body height of the peek and call hierarchy panels, shared by the editors of the project. */
|
|
@@ -94,14 +101,23 @@ export class ProjectLanguage {
|
|
|
94
101
|
pendingCaret = null;
|
|
95
102
|
lastPlace = null;
|
|
96
103
|
disposed = false;
|
|
104
|
+
subscriptions;
|
|
105
|
+
/* The result id the server gave each document in its last workspace report, which the next asks with. */
|
|
106
|
+
workspaceResults = new Map();
|
|
97
107
|
constructor(service, host = {}) {
|
|
98
108
|
this.service = service;
|
|
99
109
|
this.host = host;
|
|
100
110
|
this.i18n = host.i18n ?? i18next;
|
|
101
111
|
this.folder = host.folder ?? '';
|
|
102
112
|
this.files = host.files ?? null;
|
|
113
|
+
this.edits = new EditLocations(host.editHistoryLimit);
|
|
114
|
+
this.clipboard = host.clipboard ?? new ClipboardHistory();
|
|
115
|
+
this.templates = host.templates ?? new TemplateRegistry();
|
|
103
116
|
this.panelBodyHeight = host.panelBodyHeight ?? PANEL_BODY_HEIGHT;
|
|
104
117
|
this.problems = new ProjectProblems(host.problemSource ?? serviceProblems(service, uri => this.pathOfLocation(uri) ?? uri));
|
|
118
|
+
const shows = service.onShowDocument?.(params => this.showDocument(params));
|
|
119
|
+
const messages = host.onServerMessage === undefined ? undefined : service.onServerMessage?.(message => host.onServerMessage(message));
|
|
120
|
+
this.subscriptions = [shows, messages].flatMap(subscription => subscription === undefined ? [] : [subscription]);
|
|
105
121
|
}
|
|
106
122
|
/* Open panels of other editors of the project follow a resize. */
|
|
107
123
|
onPanelBodyChange(listener) {
|
|
@@ -153,16 +169,86 @@ export class ProjectLanguage {
|
|
|
153
169
|
}
|
|
154
170
|
/* Tells the servers that files were deleted, once they are. */
|
|
155
171
|
didDeleteFiles(targets) {
|
|
156
|
-
|
|
172
|
+
const files = filesOf(targets);
|
|
173
|
+
this.edits.forget(uri => files.some(file => file.directory ? uri.startsWith(`${file.uri}/`) : uri === file.uri));
|
|
174
|
+
return this.service.didDeleteFiles?.(files) ?? Promise.resolve();
|
|
157
175
|
}
|
|
158
|
-
/*
|
|
176
|
+
/*
|
|
177
|
+
* The text of a file as it stands now: an open document's, else a draft's or the machine's; null when it is no
|
|
178
|
+
* text file. A document of a scheme a server owns, such as a decompiled class, is read from that server.
|
|
179
|
+
*/
|
|
159
180
|
async readText(uri) {
|
|
160
|
-
const open = this.holders.get(uri)?.
|
|
181
|
+
const open = this.holders.get(uri)?.editors[0]?.getText();
|
|
161
182
|
if (open !== undefined) {
|
|
162
183
|
return open;
|
|
163
184
|
}
|
|
164
185
|
const path = fileUriToPath(uri);
|
|
165
|
-
|
|
186
|
+
if (path === null) {
|
|
187
|
+
return (await this.service.textDocumentContent?.(uri).catch(() => null)) ?? null;
|
|
188
|
+
}
|
|
189
|
+
return this.files === null ? null : (await this.files.read(path))?.text ?? null;
|
|
190
|
+
}
|
|
191
|
+
/*
|
|
192
|
+
* Asks the server of `uri` for the problems of every file it knows and lists them in `problems`, for a server that
|
|
193
|
+
* offers workspace diagnostics. A file the server reports as unchanged keeps what it had. False when no server offers them.
|
|
194
|
+
*/
|
|
195
|
+
async pullWorkspaceProblems(uri, options = {}) {
|
|
196
|
+
const {
|
|
197
|
+
service
|
|
198
|
+
} = this;
|
|
199
|
+
if (service.workspaceDiagnostics === undefined || !service.supports('workspace/diagnostic', uri)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
const previous = [...this.workspaceResults].map(([key, value]) => ({
|
|
203
|
+
uri: key,
|
|
204
|
+
value
|
|
205
|
+
}));
|
|
206
|
+
const report = await service.workspaceDiagnostics(uri, previous, options);
|
|
207
|
+
const server = this.host.serverNames?.(uri)[0] ?? 'workspace';
|
|
208
|
+
for (const item of report.items) {
|
|
209
|
+
if (item.resultId !== undefined) {
|
|
210
|
+
this.workspaceResults.set(item.uri, item.resultId);
|
|
211
|
+
}
|
|
212
|
+
if (item.kind === 'full') {
|
|
213
|
+
this.problems.report(this.pathOfLocation(item.uri) ?? item.uri, server, item.items);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
/* Shows what a server asks for: a web address outside the editor, anything else as a place of the host. */
|
|
219
|
+
async showDocument(params) {
|
|
220
|
+
const web = /^(https?|mailto):/i.test(params.uri);
|
|
221
|
+
if (params.external === true || web) {
|
|
222
|
+
if (this.host.openExternal !== undefined) {
|
|
223
|
+
this.host.openExternal(params.uri);
|
|
224
|
+
return {
|
|
225
|
+
success: true
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (typeof window === 'undefined') {
|
|
229
|
+
return {
|
|
230
|
+
success: false
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
success: window.open(params.uri, '_blank', 'noopener,noreferrer') !== null
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (this.host.openPlace === undefined) {
|
|
238
|
+
return {
|
|
239
|
+
success: false
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
this.openPlace({
|
|
243
|
+
uri: params.uri,
|
|
244
|
+
position: params.selection?.start ?? {
|
|
245
|
+
line: 0,
|
|
246
|
+
character: 0
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
success: true
|
|
251
|
+
};
|
|
166
252
|
}
|
|
167
253
|
/*
|
|
168
254
|
* Opens the file of a place on its line. A file opens on a line only, so the column is kept here until the editor
|
|
@@ -192,27 +278,40 @@ export class ProjectLanguage {
|
|
|
192
278
|
this.pendingCaret = null;
|
|
193
279
|
return pending.position;
|
|
194
280
|
}
|
|
281
|
+
/* The editors that hold a document, one per uri in the order the documents were first opened. */
|
|
282
|
+
openEditors() {
|
|
283
|
+
return [...this.holders].flatMap(([uri, holder]) => holder.editors[0] === undefined ? [] : [{
|
|
284
|
+
uri,
|
|
285
|
+
editor: holder.editors[0]
|
|
286
|
+
}]);
|
|
287
|
+
}
|
|
195
288
|
/* Opens `uri` for the editor, or joins the editors that already hold it. */
|
|
196
|
-
acquire(uri, languageId, editor) {
|
|
289
|
+
acquire(uri, languageId, editor, options = {}) {
|
|
197
290
|
if (this.disposed) {
|
|
198
291
|
throw new Error('The project language is disposed');
|
|
199
292
|
}
|
|
293
|
+
const serve = options.serve ?? true;
|
|
200
294
|
let holder = this.holders.get(uri);
|
|
201
295
|
if (holder === undefined) {
|
|
202
296
|
holder = {
|
|
203
297
|
editors: [],
|
|
204
|
-
|
|
298
|
+
served: [],
|
|
299
|
+
sync: null,
|
|
205
300
|
languageId
|
|
206
301
|
};
|
|
207
302
|
this.holders.set(uri, holder);
|
|
208
303
|
}
|
|
209
304
|
holder.editors.push(editor);
|
|
305
|
+
if (serve) {
|
|
306
|
+
holder.served.push(editor);
|
|
307
|
+
holder.sync ??= new DocumentSync(this.service, uri, languageId, editor);
|
|
308
|
+
}
|
|
210
309
|
let released = false;
|
|
211
310
|
return {
|
|
212
311
|
uri,
|
|
213
|
-
service: this.service,
|
|
312
|
+
service: serve ? this.service : UNSERVED,
|
|
214
313
|
get ready() {
|
|
215
|
-
return holder.sync.
|
|
314
|
+
return serve ? holder.sync?.ready ?? Promise.resolve() : Promise.resolve();
|
|
216
315
|
},
|
|
217
316
|
release: () => {
|
|
218
317
|
if (!released) {
|
|
@@ -228,20 +327,29 @@ export class ProjectLanguage {
|
|
|
228
327
|
return;
|
|
229
328
|
}
|
|
230
329
|
holder.editors.splice(holder.editors.indexOf(editor), 1);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
330
|
+
if (holder.served.includes(editor)) {
|
|
331
|
+
holder.served.splice(holder.served.indexOf(editor), 1);
|
|
332
|
+
}
|
|
333
|
+
const {
|
|
334
|
+
sync
|
|
335
|
+
} = holder;
|
|
336
|
+
if (holder.editors.length === 0) {
|
|
234
337
|
this.holders.delete(uri);
|
|
235
|
-
|
|
236
|
-
|
|
338
|
+
}
|
|
339
|
+
if (sync === null || sync.editor !== editor) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
sync.dispose();
|
|
343
|
+
const next = holder.served[0];
|
|
344
|
+
// The editor that sent the changes is gone; the next served one takes over and opens the document with its own text.
|
|
345
|
+
holder.sync = next === undefined ? null : new DocumentSync(this.service, uri, holder.languageId, next);
|
|
346
|
+
if (holder.sync === null) {
|
|
347
|
+
void sync.ready.catch(() => undefined).then(() => {
|
|
348
|
+
if (this.holders.get(uri)?.sync) {
|
|
237
349
|
return undefined;
|
|
238
350
|
}
|
|
239
351
|
return this.service.closeDocument(uri);
|
|
240
352
|
}).catch(() => undefined);
|
|
241
|
-
} else if (holder.sync.editor === editor) {
|
|
242
|
-
// The editor that sent the changes is gone; the next one takes over and opens the document with its own text.
|
|
243
|
-
holder.sync.dispose();
|
|
244
|
-
holder.sync = new DocumentSync(this.service, uri, holder.languageId, next);
|
|
245
353
|
}
|
|
246
354
|
}
|
|
247
355
|
dispose() {
|
|
@@ -249,8 +357,14 @@ export class ProjectLanguage {
|
|
|
249
357
|
return;
|
|
250
358
|
}
|
|
251
359
|
this.disposed = true;
|
|
360
|
+
for (const subscription of this.subscriptions) {
|
|
361
|
+
subscription.dispose();
|
|
362
|
+
}
|
|
252
363
|
disposeSnippetTransforms(this);
|
|
253
364
|
for (const [uri, holder] of this.holders) {
|
|
365
|
+
if (holder.sync === null) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
254
368
|
holder.sync.dispose();
|
|
255
369
|
void holder.sync.ready.catch(() => undefined).then(() => this.service.closeDocument(uri)).catch(() => undefined);
|
|
256
370
|
}
|
package/dist/rename.js
CHANGED
|
@@ -44,19 +44,18 @@ export class RenameFeature {
|
|
|
44
44
|
async start() {
|
|
45
45
|
const {
|
|
46
46
|
editor,
|
|
47
|
-
project,
|
|
48
47
|
uri
|
|
49
48
|
} = this.language;
|
|
50
|
-
if (!
|
|
49
|
+
if (!this.language.service.supports(METHOD, uri)) {
|
|
51
50
|
this.tell('error', this.say('unavailable'));
|
|
52
51
|
return;
|
|
53
52
|
}
|
|
54
53
|
this.cancel();
|
|
55
54
|
const caret = editor.getCaret();
|
|
56
|
-
const prepare =
|
|
55
|
+
const prepare = this.language.service.providerOptions(METHOD, uri)?.prepareProvider === true;
|
|
57
56
|
let answer;
|
|
58
57
|
try {
|
|
59
|
-
answer = prepare ? await
|
|
58
|
+
answer = prepare ? await this.language.service.prepareRename(uri, caret) : {
|
|
60
59
|
defaultBehavior: true
|
|
61
60
|
};
|
|
62
61
|
} catch (error) {
|
|
@@ -110,7 +109,7 @@ export class RenameFeature {
|
|
|
110
109
|
uri
|
|
111
110
|
} = this.language;
|
|
112
111
|
try {
|
|
113
|
-
const edit = await
|
|
112
|
+
const edit = await this.language.service.rename(uri, session.target.range.start, next);
|
|
114
113
|
if (this.session !== session) {
|
|
115
114
|
return;
|
|
116
115
|
}
|
|
@@ -204,14 +203,13 @@ export class RenameFeature {
|
|
|
204
203
|
async light(session) {
|
|
205
204
|
const {
|
|
206
205
|
editor,
|
|
207
|
-
project,
|
|
208
206
|
uri
|
|
209
207
|
} = this.language;
|
|
210
|
-
if (!
|
|
208
|
+
if (!this.language.service.supports('textDocument/references', uri)) {
|
|
211
209
|
return null;
|
|
212
210
|
}
|
|
213
211
|
try {
|
|
214
|
-
const locations = await
|
|
212
|
+
const locations = await this.language.service.references(uri, session.target.range.start, true);
|
|
215
213
|
if (this.session !== session || locations === null) {
|
|
216
214
|
return null;
|
|
217
215
|
}
|
|
@@ -282,14 +280,13 @@ export class RenameFeature {
|
|
|
282
280
|
/* So a suggester knows a function from a variable; a rename on a use finds the kind through the declaration's place in this file. */
|
|
283
281
|
async kindOf(session, locations) {
|
|
284
282
|
const {
|
|
285
|
-
project,
|
|
286
283
|
uri
|
|
287
284
|
} = this.language;
|
|
288
|
-
if (!
|
|
285
|
+
if (!this.language.service.supports('textDocument/documentSymbol', uri)) {
|
|
289
286
|
return undefined;
|
|
290
287
|
}
|
|
291
288
|
try {
|
|
292
|
-
const symbols = await
|
|
289
|
+
const symbols = await this.language.service.documentSymbols(uri, {
|
|
293
290
|
signal: session.names.signal,
|
|
294
291
|
parallel: true
|
|
295
292
|
});
|
package/dist/rendered-docs.js
CHANGED
|
@@ -192,10 +192,9 @@ export class RenderedDocsFeature {
|
|
|
192
192
|
/* Whether names in `{@link}` and `@see` can be followed, which takes a server that finds symbols by name. */
|
|
193
193
|
get canFollow() {
|
|
194
194
|
const {
|
|
195
|
-
project,
|
|
196
195
|
uri
|
|
197
196
|
} = this.language;
|
|
198
|
-
return
|
|
197
|
+
return this.language.service.supports('workspace/symbol', uri);
|
|
199
198
|
}
|
|
200
199
|
/* Goes to what a link in a comment names. */
|
|
201
200
|
follow(name, comment) {
|
package/dist/selection-ranges.js
CHANGED
|
@@ -15,14 +15,13 @@ export class SelectionRangesFeature {
|
|
|
15
15
|
constructor(language) {
|
|
16
16
|
const {
|
|
17
17
|
editor,
|
|
18
|
-
project,
|
|
19
18
|
uri
|
|
20
19
|
} = language;
|
|
21
20
|
editor.setSelectionRanges(async positions => {
|
|
22
|
-
if (!
|
|
21
|
+
if (!language.service.supports(METHOD, uri)) {
|
|
23
22
|
return null;
|
|
24
23
|
}
|
|
25
|
-
const chains = await
|
|
24
|
+
const chains = await language.service.selectionRanges(uri, positions);
|
|
26
25
|
return chains === null || chains.length !== positions.length ? null : chains.map(selectionChainOf);
|
|
27
26
|
});
|
|
28
27
|
language.onDispose(() => editor.setSelectionRanges(null));
|
package/dist/shortcut-keys.d.ts
CHANGED
|
@@ -12,10 +12,15 @@ export declare function shortcutsOf(keymap: Keymap, apple: boolean): {
|
|
|
12
12
|
autoIndentLines: LanguageShortcut | null;
|
|
13
13
|
callHierarchy: LanguageShortcut | null;
|
|
14
14
|
codeActions: LanguageShortcut | null;
|
|
15
|
+
codeBlockEnd: LanguageShortcut | null;
|
|
16
|
+
codeBlockEndWithSelection: LanguageShortcut | null;
|
|
17
|
+
codeBlockStart: LanguageShortcut | null;
|
|
18
|
+
codeBlockStartWithSelection: LanguageShortcut | null;
|
|
15
19
|
collapse: LanguageShortcut | null;
|
|
16
20
|
collapseAll: LanguageShortcut | null;
|
|
17
21
|
collapseDocComments: LanguageShortcut | null;
|
|
18
22
|
collapseRecursively: LanguageShortcut | null;
|
|
23
|
+
completeCurrentStatement: LanguageShortcut | null;
|
|
19
24
|
convertIndentsToSpaces: LanguageShortcut | null;
|
|
20
25
|
convertIndentsToTabs: LanguageShortcut | null;
|
|
21
26
|
decreaseFontSize: LanguageShortcut | null;
|
|
@@ -41,20 +46,33 @@ export declare function shortcutsOf(keymap: Keymap, apple: boolean): {
|
|
|
41
46
|
goToImplementation: LanguageShortcut | null;
|
|
42
47
|
goToSymbol: LanguageShortcut | null;
|
|
43
48
|
goToTypeDefinition: LanguageShortcut | null;
|
|
49
|
+
hippieBackwardCompletion: LanguageShortcut | null;
|
|
50
|
+
hippieCompletion: LanguageShortcut | null;
|
|
44
51
|
historyBack: LanguageShortcut | null;
|
|
45
52
|
historyForward: LanguageShortcut | null;
|
|
46
53
|
increaseFontSize: LanguageShortcut | null;
|
|
47
54
|
inlineEdit: LanguageShortcut | null;
|
|
55
|
+
insertLiveTemplate: LanguageShortcut | null;
|
|
48
56
|
joinLines: LanguageShortcut | null;
|
|
57
|
+
lastEditLocation: LanguageShortcut | null;
|
|
49
58
|
matchBrace: LanguageShortcut | null;
|
|
59
|
+
methodDown: LanguageShortcut | null;
|
|
60
|
+
methodUp: LanguageShortcut | null;
|
|
50
61
|
moveLineDown: LanguageShortcut | null;
|
|
51
62
|
moveLineUp: LanguageShortcut | null;
|
|
63
|
+
moveStatementDown: LanguageShortcut | null;
|
|
64
|
+
moveStatementUp: LanguageShortcut | null;
|
|
65
|
+
nextChange: LanguageShortcut | null;
|
|
66
|
+
nextEditLocation: LanguageShortcut | null;
|
|
52
67
|
nextHighlight: LanguageShortcut | null;
|
|
53
68
|
nextProblem: LanguageShortcut | null;
|
|
54
69
|
organizeImports: LanguageShortcut | null;
|
|
55
70
|
parameterInfo: LanguageShortcut | null;
|
|
71
|
+
pasteAsPlainText: LanguageShortcut | null;
|
|
72
|
+
pasteFromHistory: LanguageShortcut | null;
|
|
56
73
|
peekDefinition: LanguageShortcut | null;
|
|
57
74
|
peekReferences: LanguageShortcut | null;
|
|
75
|
+
previousChange: LanguageShortcut | null;
|
|
58
76
|
previousHighlight: LanguageShortcut | null;
|
|
59
77
|
previousProblem: LanguageShortcut | null;
|
|
60
78
|
quickInfo: LanguageShortcut | null;
|
|
@@ -66,6 +84,7 @@ export declare function shortcutsOf(keymap: Keymap, apple: boolean): {
|
|
|
66
84
|
resetFontSize: LanguageShortcut | null;
|
|
67
85
|
reverseLines: LanguageShortcut | null;
|
|
68
86
|
save: LanguageShortcut | null;
|
|
87
|
+
saveAsLiveTemplate: LanguageShortcut | null;
|
|
69
88
|
selectAll: LanguageShortcut | null;
|
|
70
89
|
selectAllOccurrences: LanguageShortcut | null;
|
|
71
90
|
selectNextOccurrence: LanguageShortcut | null;
|
|
@@ -84,6 +103,8 @@ export declare function shortcutsOf(keymap: Keymap, apple: boolean): {
|
|
|
84
103
|
startNewLine: LanguageShortcut | null;
|
|
85
104
|
startNewLineBefore: LanguageShortcut | null;
|
|
86
105
|
suggestInline: LanguageShortcut | null;
|
|
106
|
+
surroundWith: LanguageShortcut | null;
|
|
107
|
+
surroundWithLiveTemplate: LanguageShortcut | null;
|
|
87
108
|
textEnd: LanguageShortcut | null;
|
|
88
109
|
textStart: LanguageShortcut | null;
|
|
89
110
|
toggleBlockComment: LanguageShortcut | null;
|
|
@@ -92,10 +113,13 @@ export declare function shortcutsOf(keymap: Keymap, apple: boolean): {
|
|
|
92
113
|
toggleLineComment: LanguageShortcut | null;
|
|
93
114
|
toggleRenderedDocs: LanguageShortcut | null;
|
|
94
115
|
triggerCompletion: LanguageShortcut | null;
|
|
116
|
+
typeHierarchy: LanguageShortcut | null;
|
|
95
117
|
undo: LanguageShortcut | null;
|
|
96
118
|
uniqueLines: LanguageShortcut | null;
|
|
97
119
|
unselectOccurrence: LanguageShortcut | null;
|
|
120
|
+
unwrap: LanguageShortcut | null;
|
|
98
121
|
wordLeft: LanguageShortcut | null;
|
|
99
122
|
wordRight: LanguageShortcut | null;
|
|
123
|
+
wrapWithAbbreviation: LanguageShortcut | null;
|
|
100
124
|
};
|
|
101
125
|
export declare function isShortcut(target: LanguageShortcut | null, event: KeyboardEvent): boolean;
|
package/dist/signature.js
CHANGED
|
@@ -102,10 +102,9 @@ export class SignatureFeature {
|
|
|
102
102
|
}
|
|
103
103
|
options() {
|
|
104
104
|
const {
|
|
105
|
-
project,
|
|
106
105
|
uri
|
|
107
106
|
} = this.language;
|
|
108
|
-
const options =
|
|
107
|
+
const options = this.language.service.providerOptions(METHOD, uri);
|
|
109
108
|
return options === undefined ? null : {
|
|
110
109
|
triggers: options.triggerCharacters ?? [],
|
|
111
110
|
retriggers: options.retriggerCharacters ?? []
|
|
@@ -145,10 +144,9 @@ export class SignatureFeature {
|
|
|
145
144
|
}
|
|
146
145
|
ask(context) {
|
|
147
146
|
const {
|
|
148
|
-
project,
|
|
149
147
|
uri
|
|
150
148
|
} = this.language;
|
|
151
|
-
if (!
|
|
149
|
+
if (!this.language.service.supports(METHOD, uri)) {
|
|
152
150
|
return;
|
|
153
151
|
}
|
|
154
152
|
this.timers.clear(this.timer);
|
|
@@ -164,7 +162,6 @@ export class SignatureFeature {
|
|
|
164
162
|
async fetch(context, request) {
|
|
165
163
|
const {
|
|
166
164
|
editor,
|
|
167
|
-
project,
|
|
168
165
|
uri
|
|
169
166
|
} = this.language;
|
|
170
167
|
const caret = editor.getCaret();
|
|
@@ -173,7 +170,7 @@ export class SignatureFeature {
|
|
|
173
170
|
this.controller = controller;
|
|
174
171
|
let help;
|
|
175
172
|
try {
|
|
176
|
-
help = await
|
|
173
|
+
help = await this.language.service.signatureHelp(uri, caret, context, {
|
|
177
174
|
signal: controller.signal
|
|
178
175
|
});
|
|
179
176
|
} catch {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Editor, EditorRange } from '@adecore/editor';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
export interface SnippetPlacement {
|
|
4
|
+
readonly range: EditorRange;
|
|
5
|
+
readonly selectedText?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function bodyWithIndentUnit(body: string, unit: string): string;
|
|
8
|
+
export declare function indentUnitOf(editor: Editor): string;
|
|
9
|
+
export declare function dedentLines(text: string, base?: string): string;
|
|
10
|
+
export declare function insertSnippet(language: EditorLanguage, source: string, placements: readonly SnippetPlacement[], extra?: (name: string) => string | undefined): Promise<boolean>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { SnippetTransformQueueError } from './snippet-transform-queue.js';
|
|
2
|
+
import { comparePositions, shiftPosition } from './diagnostics-model.js';
|
|
3
|
+
import { lineTextOf, textBeforeOf } from './line-text.js';
|
|
4
|
+
import { wordRangeAt } from './rename-model.js';
|
|
5
|
+
import { jobTable } from './snippet-field-transforms.js';
|
|
6
|
+
import { indentSnippet } from './snippet-indent.js';
|
|
7
|
+
import { positionInInsertion } from './snippet-session.js';
|
|
8
|
+
import { SnippetTransformError } from './snippet-transform-client.js';
|
|
9
|
+
import { MAX_TRANSFORM_JOBS, MAX_TRANSFORM_WORK } from './snippet-transform-protocol.js';
|
|
10
|
+
import { compileSnippet, tabOrder } from './snippet.js';
|
|
11
|
+
import { createSnippetResolver } from './snippet-variables.js';
|
|
12
|
+
import { fileUriToPath } from '@adecore/lsp';
|
|
13
|
+
/* The same limits as an inserted completion item. */
|
|
14
|
+
const MAX_CHARACTERS = 2_000_000;
|
|
15
|
+
const MAX_RANGES = 1_000;
|
|
16
|
+
const TOAST_ID = 'language-completion';
|
|
17
|
+
class LimitError extends Error {}
|
|
18
|
+
/* Each line's leading tabs become the editor's own indentation unit. */
|
|
19
|
+
export function bodyWithIndentUnit(body, unit) {
|
|
20
|
+
return unit === '\t' ? body : body.replace(/^\t+/gm, tabs => unit.repeat(tabs.length));
|
|
21
|
+
}
|
|
22
|
+
/* One level of the editor's indentation: spaces for a tab size, or a tab. */
|
|
23
|
+
export function indentUnitOf(editor) {
|
|
24
|
+
const {
|
|
25
|
+
tabSize,
|
|
26
|
+
insertSpaces
|
|
27
|
+
} = editor.getIndentation();
|
|
28
|
+
return insertSpaces ? ' '.repeat(tabSize) : '\t';
|
|
29
|
+
}
|
|
30
|
+
/* The lines after the first without `base` in front of them, or without the shortest indentation among them. */
|
|
31
|
+
export function dedentLines(text, base) {
|
|
32
|
+
const lines = text.split(/\r\n|\r|\n/).slice(1);
|
|
33
|
+
const indents = lines.filter(line => line.trim() !== '').map(line => /^[\t ]*/.exec(line)[0].length);
|
|
34
|
+
const removed = base?.length ?? (indents.length === 0 ? 0 : Math.min(...indents));
|
|
35
|
+
return removed === 0 ? text : text.replace(/(\r\n|\r|\n)([^\r\n]*)/g, (_match, lineBreak, line) => lineBreak + line.slice(Math.min(removed, /^[\t ]*/.exec(line)[0].length)));
|
|
36
|
+
}
|
|
37
|
+
function sizeOf(parsed) {
|
|
38
|
+
return (parsed.transforms?.length ?? 0) + (parsed.fields?.reduce((count, field) => count + field.ranges.length + (field.linked?.length ?? 0), 1) ?? parsed.stops.length);
|
|
39
|
+
}
|
|
40
|
+
/*
|
|
41
|
+
* Inserts a snippet at every placement, the last of them the primary one, and starts a session on its tab stops. The
|
|
42
|
+
* variables are resolved for each placement (`extra` first, then the host's and the built-in ones), the lines are indented
|
|
43
|
+
* to the text before the placement, and a running session takes the insertion as a nested one. False when nothing was inserted.
|
|
44
|
+
*/
|
|
45
|
+
export async function insertSnippet(language, source, placements, extra) {
|
|
46
|
+
const {
|
|
47
|
+
editor,
|
|
48
|
+
project,
|
|
49
|
+
uri,
|
|
50
|
+
snippets
|
|
51
|
+
} = language;
|
|
52
|
+
if (placements.length === 0 || editor.isReadOnly?.() === true) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const template = compileSnippet(source);
|
|
56
|
+
const now = new Date();
|
|
57
|
+
const token = snippets.token;
|
|
58
|
+
const continuing = snippets.isActive;
|
|
59
|
+
let stale = false;
|
|
60
|
+
const off = editor.onTextChange(() => {
|
|
61
|
+
stale = true;
|
|
62
|
+
});
|
|
63
|
+
const current = () => !stale && snippets.token === token;
|
|
64
|
+
try {
|
|
65
|
+
let characters = 0;
|
|
66
|
+
let ranges = 0;
|
|
67
|
+
const drafts = placements.map((placement, index) => {
|
|
68
|
+
const line = lineTextOf(editor, placement.range.start.line);
|
|
69
|
+
const word = wordRangeAt(line, placement.range.start);
|
|
70
|
+
const resolver = createSnippetResolver({
|
|
71
|
+
uri,
|
|
72
|
+
languageId: language.languageId,
|
|
73
|
+
position: placement.range.start,
|
|
74
|
+
selection: placement.range,
|
|
75
|
+
selectedText: placement.selectedText ?? '',
|
|
76
|
+
currentLine: line,
|
|
77
|
+
currentWord: word === null ? '' : editor.textInRange(word),
|
|
78
|
+
cursorIndex: index,
|
|
79
|
+
cursorCount: placements.length,
|
|
80
|
+
folder: project.host.folder,
|
|
81
|
+
path: fileUriToPath(uri) ?? project.host.pathOfUri?.(uri) ?? undefined
|
|
82
|
+
}, project.host.resolveSnippetVariable?.bind(project.host), {
|
|
83
|
+
now
|
|
84
|
+
});
|
|
85
|
+
const resolve = name => extra?.(name) ?? resolver(name);
|
|
86
|
+
const raw = template.render(resolve, undefined, {
|
|
87
|
+
indentVariables: true
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
placement,
|
|
91
|
+
resolve,
|
|
92
|
+
raw,
|
|
93
|
+
parsed: indentSnippet(raw, textBeforeOf(editor, placement.range.start))
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
for (const {
|
|
97
|
+
parsed
|
|
98
|
+
} of drafts) {
|
|
99
|
+
characters += parsed.text.length;
|
|
100
|
+
ranges += sizeOf(parsed);
|
|
101
|
+
if (parsed.limited === true || characters > MAX_CHARACTERS || ranges > MAX_RANGES) {
|
|
102
|
+
throw new LimitError();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
await transformVariables(language, template, drafts, current);
|
|
106
|
+
if (!current()) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const changes = drafts.map(draft => ({
|
|
110
|
+
range: draft.placement.range,
|
|
111
|
+
text: draft.parsed.text
|
|
112
|
+
}));
|
|
113
|
+
if (changes.reduce((total, change) => total + change.text.length, 0) > MAX_CHARACTERS) {
|
|
114
|
+
throw new LimitError();
|
|
115
|
+
}
|
|
116
|
+
// Insertions before one another move it, so each start is mapped through the changes in front of it.
|
|
117
|
+
const starts = changes.map(change => changes.filter(other => other !== change && comparePositions(other.range.end, change.range.start) <= 0).sort((left, right) => comparePositions(right.range.start, left.range.start)).reduce((place, other) => shiftPosition(place, other), change.range.start));
|
|
118
|
+
if (!(continuing ? snippets.applyEdits(changes, token) : editor.applyEdits(changes))) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
off();
|
|
122
|
+
place(language, drafts, starts, continuing && snippets.isActive && snippets.token === token);
|
|
123
|
+
editor.focus();
|
|
124
|
+
return true;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error instanceof LimitError || error instanceof SnippetTransformError && error.kind === 'range') {
|
|
127
|
+
project.host.notify?.({
|
|
128
|
+
id: TOAST_ID,
|
|
129
|
+
kind: 'error',
|
|
130
|
+
title: project.i18n.t('editor:language.completion.tooLarge')
|
|
131
|
+
});
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (error instanceof SnippetTransformQueueError) {
|
|
135
|
+
project.host.notify?.({
|
|
136
|
+
id: TOAST_ID,
|
|
137
|
+
kind: 'error',
|
|
138
|
+
title: project.i18n.t('editor:language.completion.transformBusy')
|
|
139
|
+
});
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (error instanceof SnippetTransformError) {
|
|
143
|
+
project.host.notify?.({
|
|
144
|
+
id: TOAST_ID,
|
|
145
|
+
kind: 'error',
|
|
146
|
+
title: project.i18n.t('editor:language.completion.transformFailed')
|
|
147
|
+
});
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
throw error;
|
|
151
|
+
} finally {
|
|
152
|
+
off();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/* Runs the transforms of variables through the worker, once for all placements, and renders them again with the answers. */
|
|
156
|
+
async function transformVariables(language, template, drafts, current) {
|
|
157
|
+
const {
|
|
158
|
+
editor,
|
|
159
|
+
project,
|
|
160
|
+
snippets
|
|
161
|
+
} = language;
|
|
162
|
+
const {
|
|
163
|
+
jobs,
|
|
164
|
+
slotOf
|
|
165
|
+
} = jobTable();
|
|
166
|
+
const slots = drafts.map(draft => (draft.raw.transforms ?? []).map(range => slotOf({
|
|
167
|
+
...range.transform,
|
|
168
|
+
value: draft.raw.text.slice(range.start, range.end)
|
|
169
|
+
})));
|
|
170
|
+
if (jobs.length === 0 || project.host.snippetTransformWorker === undefined) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (jobs.length > MAX_TRANSFORM_JOBS || jobs.reduce((sum, job) => sum + job.value.length, 0) > MAX_TRANSFORM_WORK) {
|
|
174
|
+
throw new LimitError();
|
|
175
|
+
}
|
|
176
|
+
const values = await snippets.transformVariables(jobs, current);
|
|
177
|
+
if (values === null || !current()) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
drafts.forEach((draft, index) => {
|
|
181
|
+
const raw = template.render(draft.resolve, slots[index].map(slot => values[slot]), {
|
|
182
|
+
indentVariables: true
|
|
183
|
+
});
|
|
184
|
+
const parsed = indentSnippet(raw, textBeforeOf(editor, draft.placement.range.start));
|
|
185
|
+
if (parsed.limited === true) {
|
|
186
|
+
throw new LimitError();
|
|
187
|
+
}
|
|
188
|
+
draft.raw = raw;
|
|
189
|
+
draft.parsed = parsed;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/* Starts the session on the tab stops, or puts the caret after the primary insertion when there are none. */
|
|
193
|
+
function place(language, drafts, starts, nested) {
|
|
194
|
+
const {
|
|
195
|
+
editor,
|
|
196
|
+
snippets
|
|
197
|
+
} = language;
|
|
198
|
+
const primary = drafts.at(-1).parsed;
|
|
199
|
+
if (primary.stops.length > 0) {
|
|
200
|
+
snippets.beginInstances(drafts.map((draft, index) => ({
|
|
201
|
+
start: starts[index],
|
|
202
|
+
text: draft.parsed.text,
|
|
203
|
+
stops: tabOrder(draft.parsed),
|
|
204
|
+
fields: draft.parsed.fields
|
|
205
|
+
})), {
|
|
206
|
+
nested
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
editor.setSelections(drafts.map((draft, index) => {
|
|
211
|
+
const end = positionInInsertion(starts[index], draft.parsed.text, draft.parsed.text.length);
|
|
212
|
+
return {
|
|
213
|
+
start: end,
|
|
214
|
+
end
|
|
215
|
+
};
|
|
216
|
+
}));
|
|
217
|
+
}
|