@adecore/editor-react 0.42.0 → 0.43.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 +190 -70
- 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.js +10 -15
- 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 +19 -1
- package/dist/editor-language.js +77 -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 +6 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +8 -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 +8 -5
- package/dist/key-priority.js +9 -6
- package/dist/linked-editing.d.ts +26 -0
- package/dist/linked-editing.js +215 -0
- package/dist/locales/en.json +31 -2
- package/dist/locales/nl.json +31 -2
- package/dist/markup-context.d.ts +37 -0
- package/dist/markup-context.js +291 -0
- package/dist/markup.d.ts +36 -0
- package/dist/markup.js +453 -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 +20 -2
- package/dist/pick.js +141 -18
- package/dist/popups.d.ts +9 -0
- package/dist/popups.js +2 -1
- package/dist/project-language.d.ts +18 -1
- package/dist/project-language.js +129 -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 +19 -0
- package/dist/signature.js +3 -6
- package/dist/snippet-session.d.ts +2 -2
- package/dist/snippet-session.js +3 -3
- package/dist/symbol-picker.js +4 -6
- package/dist/symbols.js +22 -3
- 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 +40 -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 +15 -15
- 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 +80 -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 +10 -0
- package/src/index.ts +12 -3
- package/src/inlay-hints.ts +2 -2
- package/src/inline-completion.ts +221 -0
- package/src/key-priority.ts +9 -6
- package/src/linked-editing.ts +199 -0
- package/src/locales/en.json +31 -2
- package/src/locales/nl.json +31 -2
- package/src/markup-context.ts +262 -0
- package/src/markup.ts +367 -0
- package/src/navigation.ts +6 -6
- package/src/paste-history.ts +111 -0
- package/src/peek.ts +4 -4
- package/src/pick.ts +201 -19
- package/src/popups.ts +19 -1
- package/src/project-language.ts +136 -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-session.ts +9 -4
- package/src/symbol-picker.ts +6 -6
- package/src/symbols.ts +30 -4
- package/src/testing.ts +126 -1
package/src/project-language.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { disposeSnippetTransforms } from './snippet-transform-queue.ts';
|
|
2
2
|
import i18next, { type i18n } from 'i18next';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
fileUriToPath,
|
|
5
|
+
pathToFileUri,
|
|
6
|
+
unservedLanguageService,
|
|
7
|
+
type ApplyWorkspaceEditResult,
|
|
8
|
+
type ContentChange,
|
|
9
|
+
type Disposable,
|
|
10
|
+
type LanguageService,
|
|
11
|
+
type ShowDocumentParams,
|
|
12
|
+
type ShowDocumentResult,
|
|
13
|
+
type WorkspaceEdit
|
|
14
|
+
} from '@adecore/lsp';
|
|
4
15
|
import type { Editor, EditorPosition } from '@adecore/editor';
|
|
16
|
+
import { ClipboardHistory } from './clipboard-history.ts';
|
|
17
|
+
import { EditLocations } from './edit-locations.ts';
|
|
5
18
|
import { NavigationHistory, type Place } from './navigation-history.ts';
|
|
6
19
|
import { ProjectProblems, serviceProblems } from './project-problems.ts';
|
|
7
20
|
import { applyWorkspaceEdit, type ProjectFiles } from './workspace-edit.ts';
|
|
@@ -16,6 +29,8 @@ export interface LanguageDocumentHandle {
|
|
|
16
29
|
release(): void;
|
|
17
30
|
}
|
|
18
31
|
|
|
32
|
+
const UNSERVED = unservedLanguageService();
|
|
33
|
+
|
|
19
34
|
/* Sends one editor's edits to the document, holding them until the document is open. */
|
|
20
35
|
class DocumentSync {
|
|
21
36
|
readonly ready: Promise<void>;
|
|
@@ -76,10 +91,18 @@ class DocumentSync {
|
|
|
76
91
|
|
|
77
92
|
interface Holder {
|
|
78
93
|
readonly editors: Editor[];
|
|
79
|
-
|
|
94
|
+
/* The editors a server serves, in the order they came; the first one sends the changes. */
|
|
95
|
+
readonly served: Editor[];
|
|
96
|
+
/* Null while no editor of the document is served, so nothing of it is open in the service. */
|
|
97
|
+
sync: DocumentSync | null;
|
|
80
98
|
languageId: string;
|
|
81
99
|
}
|
|
82
100
|
|
|
101
|
+
export interface AcquireOptions {
|
|
102
|
+
/* False registers the editor as open without telling the service, for a document no server serves; true by default. */
|
|
103
|
+
readonly serve?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
83
106
|
/* A file or folder for a file operation notification: a bare path is a file. */
|
|
84
107
|
export type FileOperationTarget = string | { readonly path: string; readonly directory?: boolean };
|
|
85
108
|
|
|
@@ -103,6 +126,8 @@ export class ProjectLanguage {
|
|
|
103
126
|
readonly folder: string;
|
|
104
127
|
readonly problems: ProjectProblems;
|
|
105
128
|
readonly history = new NavigationHistory();
|
|
129
|
+
readonly edits: EditLocations;
|
|
130
|
+
readonly clipboard: ClipboardHistory;
|
|
106
131
|
private readonly holders = new Map<string, Holder>();
|
|
107
132
|
private readonly files: ProjectFiles | null;
|
|
108
133
|
|
|
@@ -113,6 +138,9 @@ export class ProjectLanguage {
|
|
|
113
138
|
private pendingCaret: Place | null = null;
|
|
114
139
|
private lastPlace: Place | null = null;
|
|
115
140
|
private disposed = false;
|
|
141
|
+
private readonly subscriptions: readonly Disposable[];
|
|
142
|
+
/* The result id the server gave each document in its last workspace report, which the next asks with. */
|
|
143
|
+
private readonly workspaceResults = new Map<string, string>();
|
|
116
144
|
|
|
117
145
|
constructor(service: LanguageService, host: LanguageHost = {}) {
|
|
118
146
|
this.service = service;
|
|
@@ -120,8 +148,13 @@ export class ProjectLanguage {
|
|
|
120
148
|
this.i18n = host.i18n ?? i18next;
|
|
121
149
|
this.folder = host.folder ?? '';
|
|
122
150
|
this.files = host.files ?? null;
|
|
151
|
+
this.edits = new EditLocations(host.editHistoryLimit);
|
|
152
|
+
this.clipboard = host.clipboard ?? new ClipboardHistory();
|
|
123
153
|
this.panelBodyHeight = host.panelBodyHeight ?? PANEL_BODY_HEIGHT;
|
|
124
154
|
this.problems = new ProjectProblems(host.problemSource ?? serviceProblems(service, (uri) => this.pathOfLocation(uri) ?? uri));
|
|
155
|
+
const shows = service.onShowDocument?.((params) => this.showDocument(params));
|
|
156
|
+
const messages = host.onServerMessage === undefined ? undefined : service.onServerMessage?.((message) => host.onServerMessage!(message));
|
|
157
|
+
this.subscriptions = [shows, messages].flatMap((subscription) => (subscription === undefined ? [] : [subscription]));
|
|
125
158
|
}
|
|
126
159
|
|
|
127
160
|
/* Open panels of other editors of the project follow a resize. */
|
|
@@ -182,20 +215,73 @@ export class ProjectLanguage {
|
|
|
182
215
|
|
|
183
216
|
/* Tells the servers that files were deleted, once they are. */
|
|
184
217
|
didDeleteFiles(targets: readonly FileOperationTarget[]): Promise<void> {
|
|
185
|
-
|
|
218
|
+
const files = filesOf(targets);
|
|
219
|
+
|
|
220
|
+
this.edits.forget((uri) => files.some((file) => (file.directory ? uri.startsWith(`${file.uri}/`) : uri === file.uri)));
|
|
221
|
+
|
|
222
|
+
return this.service.didDeleteFiles?.(files) ?? Promise.resolve();
|
|
186
223
|
}
|
|
187
224
|
|
|
188
|
-
/*
|
|
225
|
+
/*
|
|
226
|
+
* 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
|
|
227
|
+
* text file. A document of a scheme a server owns, such as a decompiled class, is read from that server.
|
|
228
|
+
*/
|
|
189
229
|
async readText(uri: string): Promise<string | null> {
|
|
190
|
-
const open = this.holders.get(uri)?.
|
|
230
|
+
const open = this.holders.get(uri)?.editors[0]?.getText();
|
|
191
231
|
|
|
192
232
|
if (open !== undefined) {
|
|
193
233
|
return open;
|
|
194
234
|
}
|
|
195
235
|
|
|
196
236
|
const path = fileUriToPath(uri);
|
|
237
|
+
if (path === null) {
|
|
238
|
+
return (await this.service.textDocumentContent?.(uri).catch(() => null)) ?? null;
|
|
239
|
+
}
|
|
197
240
|
|
|
198
|
-
return
|
|
241
|
+
return this.files === null ? null : ((await this.files.read(path))?.text ?? null);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/*
|
|
245
|
+
* Asks the server of `uri` for the problems of every file it knows and lists them in `problems`, for a server that
|
|
246
|
+
* offers workspace diagnostics. A file the server reports as unchanged keeps what it had. False when no server offers them.
|
|
247
|
+
*/
|
|
248
|
+
async pullWorkspaceProblems(uri: string, options: { readonly signal?: AbortSignal } = {}): Promise<boolean> {
|
|
249
|
+
const { service } = this;
|
|
250
|
+
if (service.workspaceDiagnostics === undefined || !service.supports('workspace/diagnostic', uri)) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const previous = [...this.workspaceResults].map(([key, value]) => ({ uri: key, value }));
|
|
254
|
+
const report = await service.workspaceDiagnostics(uri, previous, options);
|
|
255
|
+
const server = this.host.serverNames?.(uri)[0] ?? 'workspace';
|
|
256
|
+
for (const item of report.items) {
|
|
257
|
+
if (item.resultId !== undefined) {
|
|
258
|
+
this.workspaceResults.set(item.uri, item.resultId);
|
|
259
|
+
}
|
|
260
|
+
if (item.kind === 'full') {
|
|
261
|
+
this.problems.report(this.pathOfLocation(item.uri) ?? item.uri, server, item.items);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Shows what a server asks for: a web address outside the editor, anything else as a place of the host. */
|
|
268
|
+
private async showDocument(params: ShowDocumentParams): Promise<ShowDocumentResult> {
|
|
269
|
+
const web = /^(https?|mailto):/i.test(params.uri);
|
|
270
|
+
if (params.external === true || web) {
|
|
271
|
+
if (this.host.openExternal !== undefined) {
|
|
272
|
+
this.host.openExternal(params.uri);
|
|
273
|
+
return { success: true };
|
|
274
|
+
}
|
|
275
|
+
if (typeof window === 'undefined') {
|
|
276
|
+
return { success: false };
|
|
277
|
+
}
|
|
278
|
+
return { success: window.open(params.uri, '_blank', 'noopener,noreferrer') !== null };
|
|
279
|
+
}
|
|
280
|
+
if (this.host.openPlace === undefined) {
|
|
281
|
+
return { success: false };
|
|
282
|
+
}
|
|
283
|
+
this.openPlace({ uri: params.uri, position: params.selection?.start ?? { line: 0, character: 0 } });
|
|
284
|
+
return { success: true };
|
|
199
285
|
}
|
|
200
286
|
|
|
201
287
|
/*
|
|
@@ -234,27 +320,39 @@ export class ProjectLanguage {
|
|
|
234
320
|
return pending.position;
|
|
235
321
|
}
|
|
236
322
|
|
|
323
|
+
/* The editors that hold a document, one per uri in the order the documents were first opened. */
|
|
324
|
+
openEditors(): readonly { uri: string; editor: Editor }[] {
|
|
325
|
+
return [...this.holders].flatMap(([uri, holder]) => (holder.editors[0] === undefined ? [] : [{ uri, editor: holder.editors[0] }]));
|
|
326
|
+
}
|
|
327
|
+
|
|
237
328
|
/* Opens `uri` for the editor, or joins the editors that already hold it. */
|
|
238
|
-
acquire(uri: string, languageId: string, editor: Editor): LanguageDocumentHandle {
|
|
329
|
+
acquire(uri: string, languageId: string, editor: Editor, options: AcquireOptions = {}): LanguageDocumentHandle {
|
|
239
330
|
if (this.disposed) {
|
|
240
331
|
throw new Error('The project language is disposed');
|
|
241
332
|
}
|
|
242
333
|
|
|
334
|
+
const serve = options.serve ?? true;
|
|
243
335
|
let holder = this.holders.get(uri);
|
|
244
336
|
|
|
245
337
|
if (holder === undefined) {
|
|
246
|
-
holder = { editors: [],
|
|
338
|
+
holder = { editors: [], served: [], sync: null, languageId };
|
|
247
339
|
this.holders.set(uri, holder);
|
|
248
340
|
}
|
|
249
341
|
|
|
250
342
|
holder.editors.push(editor);
|
|
343
|
+
|
|
344
|
+
if (serve) {
|
|
345
|
+
holder.served.push(editor);
|
|
346
|
+
holder.sync ??= new DocumentSync(this.service, uri, languageId, editor);
|
|
347
|
+
}
|
|
348
|
+
|
|
251
349
|
let released = false;
|
|
252
350
|
|
|
253
351
|
return {
|
|
254
352
|
uri,
|
|
255
|
-
service: this.service,
|
|
353
|
+
service: serve ? this.service : UNSERVED,
|
|
256
354
|
get ready() {
|
|
257
|
-
return holder.sync.
|
|
355
|
+
return serve ? (holder.sync?.ready ?? Promise.resolve()) : Promise.resolve();
|
|
258
356
|
},
|
|
259
357
|
release: () => {
|
|
260
358
|
if (!released) {
|
|
@@ -273,26 +371,38 @@ export class ProjectLanguage {
|
|
|
273
371
|
}
|
|
274
372
|
|
|
275
373
|
holder.editors.splice(holder.editors.indexOf(editor), 1);
|
|
276
|
-
const next = holder.editors[0];
|
|
277
374
|
|
|
278
|
-
if (
|
|
279
|
-
holder.
|
|
375
|
+
if (holder.served.includes(editor)) {
|
|
376
|
+
holder.served.splice(holder.served.indexOf(editor), 1);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const { sync } = holder;
|
|
380
|
+
|
|
381
|
+
if (holder.editors.length === 0) {
|
|
280
382
|
this.holders.delete(uri);
|
|
383
|
+
}
|
|
281
384
|
|
|
282
|
-
|
|
385
|
+
if (sync === null || sync.editor !== editor) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
sync.dispose();
|
|
390
|
+
const next = holder.served[0];
|
|
391
|
+
|
|
392
|
+
// The editor that sent the changes is gone; the next served one takes over and opens the document with its own text.
|
|
393
|
+
holder.sync = next === undefined ? null : new DocumentSync(this.service, uri, holder.languageId, next);
|
|
394
|
+
|
|
395
|
+
if (holder.sync === null) {
|
|
396
|
+
void sync.ready
|
|
283
397
|
.catch(() => undefined)
|
|
284
398
|
.then(() => {
|
|
285
|
-
if (this.holders.
|
|
399
|
+
if (this.holders.get(uri)?.sync) {
|
|
286
400
|
return undefined;
|
|
287
401
|
}
|
|
288
402
|
|
|
289
403
|
return this.service.closeDocument(uri);
|
|
290
404
|
})
|
|
291
405
|
.catch(() => undefined);
|
|
292
|
-
} else if (holder.sync.editor === editor) {
|
|
293
|
-
// The editor that sent the changes is gone; the next one takes over and opens the document with its own text.
|
|
294
|
-
holder.sync.dispose();
|
|
295
|
-
holder.sync = new DocumentSync(this.service, uri, holder.languageId, next);
|
|
296
406
|
}
|
|
297
407
|
}
|
|
298
408
|
|
|
@@ -302,9 +412,16 @@ export class ProjectLanguage {
|
|
|
302
412
|
}
|
|
303
413
|
|
|
304
414
|
this.disposed = true;
|
|
415
|
+
for (const subscription of this.subscriptions) {
|
|
416
|
+
subscription.dispose();
|
|
417
|
+
}
|
|
305
418
|
disposeSnippetTransforms(this);
|
|
306
419
|
|
|
307
420
|
for (const [uri, holder] of this.holders) {
|
|
421
|
+
if (holder.sync === null) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
|
|
308
425
|
holder.sync.dispose();
|
|
309
426
|
|
|
310
427
|
void holder.sync.ready
|
package/src/rename.ts
CHANGED
|
@@ -67,9 +67,9 @@ export class RenameFeature {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
async start(): Promise<void> {
|
|
70
|
-
const { editor,
|
|
70
|
+
const { editor, uri } = this.language;
|
|
71
71
|
|
|
72
|
-
if (!
|
|
72
|
+
if (!this.language.service.supports(METHOD, uri)) {
|
|
73
73
|
this.tell('error', this.say('unavailable'));
|
|
74
74
|
|
|
75
75
|
return;
|
|
@@ -77,11 +77,11 @@ export class RenameFeature {
|
|
|
77
77
|
|
|
78
78
|
this.cancel();
|
|
79
79
|
const caret = editor.getCaret();
|
|
80
|
-
const prepare =
|
|
80
|
+
const prepare = this.language.service.providerOptions(METHOD, uri)?.prepareProvider === true;
|
|
81
81
|
let answer: PrepareRenameResult;
|
|
82
82
|
|
|
83
83
|
try {
|
|
84
|
-
answer = prepare ? await
|
|
84
|
+
answer = prepare ? await this.language.service.prepareRename(uri, caret) : { defaultBehavior: true };
|
|
85
85
|
} catch (error) {
|
|
86
86
|
if (!(error instanceof StaleResultError)) {
|
|
87
87
|
this.tell('error', this.say('nothing'));
|
|
@@ -127,7 +127,7 @@ export class RenameFeature {
|
|
|
127
127
|
const { project, uri } = this.language;
|
|
128
128
|
|
|
129
129
|
try {
|
|
130
|
-
const edit = await
|
|
130
|
+
const edit = await this.language.service.rename(uri, session.target.range.start, next);
|
|
131
131
|
|
|
132
132
|
if (this.session !== session) {
|
|
133
133
|
return;
|
|
@@ -218,14 +218,14 @@ export class RenameFeature {
|
|
|
218
218
|
|
|
219
219
|
/* Lights up the places the name has in this file, and counts them all; what the servers listed, or null when they did not. */
|
|
220
220
|
private async light(session: Session): Promise<readonly Location[] | null> {
|
|
221
|
-
const { editor,
|
|
221
|
+
const { editor, uri } = this.language;
|
|
222
222
|
|
|
223
|
-
if (!
|
|
223
|
+
if (!this.language.service.supports('textDocument/references', uri)) {
|
|
224
224
|
return null;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
try {
|
|
228
|
-
const locations = await
|
|
228
|
+
const locations = await this.language.service.references(uri, session.target.range.start, true);
|
|
229
229
|
|
|
230
230
|
if (this.session !== session || locations === null) {
|
|
231
231
|
return null;
|
|
@@ -277,14 +277,14 @@ export class RenameFeature {
|
|
|
277
277
|
|
|
278
278
|
/* 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. */
|
|
279
279
|
private async kindOf(session: Session, locations: readonly Location[]): Promise<string | undefined> {
|
|
280
|
-
const {
|
|
280
|
+
const { uri } = this.language;
|
|
281
281
|
|
|
282
|
-
if (!
|
|
282
|
+
if (!this.language.service.supports('textDocument/documentSymbol', uri)) {
|
|
283
283
|
return undefined;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
try {
|
|
287
|
-
const symbols = await
|
|
287
|
+
const symbols = await this.language.service.documentSymbols(uri, { signal: session.names.signal, parallel: true });
|
|
288
288
|
|
|
289
289
|
return renameKindOf(symbols, session.original, [session.target.range, ...occurrencesOf(locations, uri).inFile]);
|
|
290
290
|
} catch {
|
package/src/rendered-docs.ts
CHANGED
|
@@ -254,9 +254,9 @@ export class RenderedDocsFeature {
|
|
|
254
254
|
|
|
255
255
|
/* Whether names in `{@link}` and `@see` can be followed, which takes a server that finds symbols by name. */
|
|
256
256
|
get canFollow(): boolean {
|
|
257
|
-
const {
|
|
257
|
+
const { uri } = this.language;
|
|
258
258
|
|
|
259
|
-
return
|
|
259
|
+
return this.language.service.supports('workspace/symbol', uri);
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
/* Goes to what a link in a comment names. */
|
package/src/selection-ranges.ts
CHANGED
|
@@ -21,14 +21,14 @@ export function selectionChainOf(selection: SelectionRange): EditorRange[] {
|
|
|
21
21
|
*/
|
|
22
22
|
export class SelectionRangesFeature {
|
|
23
23
|
constructor(language: EditorLanguage) {
|
|
24
|
-
const { editor,
|
|
24
|
+
const { editor, uri } = language;
|
|
25
25
|
|
|
26
26
|
editor.setSelectionRanges(async (positions) => {
|
|
27
|
-
if (!
|
|
27
|
+
if (!language.service.supports(METHOD, uri)) {
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const chains = await
|
|
31
|
+
const chains = await language.service.selectionRanges(uri, positions);
|
|
32
32
|
|
|
33
33
|
return chains === null || chains.length !== positions.length ? null : chains.map(selectionChainOf);
|
|
34
34
|
});
|
package/src/signature.ts
CHANGED
|
@@ -111,8 +111,8 @@ export class SignatureFeature {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
private options(): { triggers: readonly string[]; retriggers: readonly string[] } | null {
|
|
114
|
-
const {
|
|
115
|
-
const options =
|
|
114
|
+
const { uri } = this.language;
|
|
115
|
+
const options = this.language.service.providerOptions(METHOD, uri);
|
|
116
116
|
|
|
117
117
|
return options === undefined ? null : { triggers: options.triggerCharacters ?? [], retriggers: options.retriggerCharacters ?? [] };
|
|
118
118
|
}
|
|
@@ -147,9 +147,9 @@ export class SignatureFeature {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
private ask(context: Omit<SignatureHelpContext, 'activeSignatureHelp'>): void {
|
|
150
|
-
const {
|
|
150
|
+
const { uri } = this.language;
|
|
151
151
|
|
|
152
|
-
if (!
|
|
152
|
+
if (!this.language.service.supports(METHOD, uri)) {
|
|
153
153
|
return;
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -160,7 +160,7 @@ export class SignatureFeature {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
private async fetch(context: SignatureHelpContext, request: number): Promise<void> {
|
|
163
|
-
const { editor,
|
|
163
|
+
const { editor, uri } = this.language;
|
|
164
164
|
const caret = editor.getCaret();
|
|
165
165
|
this.controller?.abort();
|
|
166
166
|
const controller = new AbortController();
|
|
@@ -168,7 +168,7 @@ export class SignatureFeature {
|
|
|
168
168
|
let help: SignatureHelp | null;
|
|
169
169
|
|
|
170
170
|
try {
|
|
171
|
-
help = await
|
|
171
|
+
help = await this.language.service.signatureHelp(uri, caret, context, { signal: controller.signal });
|
|
172
172
|
} catch {
|
|
173
173
|
return;
|
|
174
174
|
}
|
package/src/snippet-session.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { acquireSnippetTransforms, type SnippetTransformLease } from './snippet-
|
|
|
5
5
|
import { realTimers, type Timers } from './timers.ts';
|
|
6
6
|
import { KEY_PRIORITY } from './key-priority.ts';
|
|
7
7
|
import { MAX_TRANSFORM_CHARACTERS, type SnippetTransformJob } from './snippet-transform-protocol.ts';
|
|
8
|
-
import type { EditorContentChange, EditorPosition, EditorRange, EditorTextChange } from '@adecore/editor';
|
|
8
|
+
import type { EditorApplyEditsOptions, EditorContentChange, EditorPosition, EditorRange, EditorTextChange } from '@adecore/editor';
|
|
9
9
|
import { comparePositions, endOfInsertion, rangeHolds, sameRange, shiftPosition } from './diagnostics-model.ts';
|
|
10
10
|
import type { EditorLanguage } from './editor-language.ts';
|
|
11
11
|
import { textBeforeOf } from './line-text.ts';
|
|
@@ -367,7 +367,12 @@ export class SnippetFeature {
|
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
/* Imports can touch a hull's edge; their affinity must differ from edits inside its active field. */
|
|
370
|
-
applyEdits(
|
|
370
|
+
applyEdits(
|
|
371
|
+
changes: readonly EditorContentChange[],
|
|
372
|
+
token: number,
|
|
373
|
+
extras: readonly EditorContentChange[] = [],
|
|
374
|
+
options?: EditorApplyEditsOptions
|
|
375
|
+
): boolean {
|
|
371
376
|
const frame = this.frames.at(-1);
|
|
372
377
|
|
|
373
378
|
const fits =
|
|
@@ -385,14 +390,14 @@ export class SnippetFeature {
|
|
|
385
390
|
if (token !== this.generation || !fits) {
|
|
386
391
|
this.end();
|
|
387
392
|
|
|
388
|
-
return this.language.editor.applyEdits(changes);
|
|
393
|
+
return this.language.editor.applyEdits(changes, options);
|
|
389
394
|
}
|
|
390
395
|
|
|
391
396
|
this.applying = true;
|
|
392
397
|
this.extras = extras;
|
|
393
398
|
|
|
394
399
|
try {
|
|
395
|
-
return this.language.editor.applyEdits(changes);
|
|
400
|
+
return this.language.editor.applyEdits(changes, options);
|
|
396
401
|
} finally {
|
|
397
402
|
this.applying = false;
|
|
398
403
|
this.extras = [];
|
package/src/symbol-picker.ts
CHANGED
|
@@ -36,16 +36,16 @@ export class SymbolPickerFeature {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
async open(): Promise<void> {
|
|
39
|
-
const {
|
|
39
|
+
const { uri, popups } = this.language;
|
|
40
40
|
|
|
41
|
-
if (!
|
|
41
|
+
if (!this.language.service.supports('textDocument/documentSymbol', uri)) {
|
|
42
42
|
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: this.language.project.i18n.t('editor:language.symbols.unavailable') });
|
|
43
43
|
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
try {
|
|
48
|
-
const entries = entriesOf(await
|
|
48
|
+
const entries = entriesOf(await this.language.service.documentSymbols(uri));
|
|
49
49
|
popups.setState({ symbols: { file: basenameOf(this.language.uri), entries } });
|
|
50
50
|
} catch (error) {
|
|
51
51
|
if (!(error instanceof StaleResultError)) {
|
|
@@ -79,13 +79,13 @@ export class SymbolPickerFeature {
|
|
|
79
79
|
|
|
80
80
|
/* The symbols of the project that answer to a name, which is a request to the server and so waits for a pause in the typing. */
|
|
81
81
|
async search(text: string, signal: AbortSignal): Promise<WorkspaceEntry[]> {
|
|
82
|
-
const {
|
|
82
|
+
const { uri } = this.language;
|
|
83
83
|
|
|
84
|
-
if (text === '' || !
|
|
84
|
+
if (text === '' || !this.language.service.supports('workspace/symbol', uri)) {
|
|
85
85
|
return [];
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
return workspaceEntriesOf(await
|
|
88
|
+
return workspaceEntriesOf(await this.language.service.workspaceSymbols(uri, text, { signal }));
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
goToWorkspace(entry: WorkspaceEntry): void {
|
package/src/symbols.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EditorBlock } from '@adecore/editor';
|
|
1
|
+
import type { EditorBlock, EditorPosition } from '@adecore/editor';
|
|
2
2
|
import type { DocumentSymbol, DocumentSymbolResult, Range, SymbolInformation } from '@adecore/lsp';
|
|
3
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
4
4
|
import { Refresher } from './refresher.ts';
|
|
@@ -55,6 +55,29 @@ export function symbolSpansOf(result: DocumentSymbolResult): SymbolSpan[] {
|
|
|
55
55
|
return (result as SymbolInformation[]).map((symbol) => ({ name: symbol.name, kind: symbol.kind, range: symbol.location.range }));
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function declarationsOf(result: DocumentSymbolResult): EditorPosition[] {
|
|
59
|
+
if (result === null || result.length === 0) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!('range' in result[0]!)) {
|
|
64
|
+
return (result as SymbolInformation[]).map((symbol) => symbol.location.range.start);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const stops: EditorPosition[] = [];
|
|
68
|
+
|
|
69
|
+
const visit = (symbols: readonly DocumentSymbol[]): void => {
|
|
70
|
+
for (const symbol of symbols) {
|
|
71
|
+
stops.push((symbol.selectionRange ?? symbol.range).start);
|
|
72
|
+
visit(symbol.children ?? []);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
visit(result as DocumentSymbol[]);
|
|
77
|
+
|
|
78
|
+
return stops;
|
|
79
|
+
}
|
|
80
|
+
|
|
58
81
|
/* A line a declaration can have above it: blank, a comment, or an attribute. A bare `#` is no comment, since it heads a Markdown section. */
|
|
59
82
|
const PRELUDE = /^\s*(?:$|\/\/|\/\*|\*|#\[)/;
|
|
60
83
|
|
|
@@ -121,18 +144,20 @@ export class SymbolsFeature {
|
|
|
121
144
|
private readonly listeners = new Set<(result: DocumentSymbolResult) => void>();
|
|
122
145
|
|
|
123
146
|
constructor(language: EditorLanguage, timers: Timers = realTimers) {
|
|
124
|
-
const { editor,
|
|
147
|
+
const { editor, uri } = language;
|
|
125
148
|
|
|
126
149
|
this.refresher = new Refresher(
|
|
127
150
|
async (signal) => {
|
|
128
|
-
if (!
|
|
151
|
+
if (!language.service.supports(METHOD, uri)) {
|
|
129
152
|
return;
|
|
130
153
|
}
|
|
131
154
|
|
|
132
|
-
const result = await
|
|
155
|
+
const result = await language.service.documentSymbols(uri, { signal });
|
|
133
156
|
|
|
134
157
|
if (!signal.aborted) {
|
|
135
158
|
editor.setBlocks(result === null ? null : blocksOf(result, editor.getText()));
|
|
159
|
+
const declarations = declarationsOf(result);
|
|
160
|
+
editor.setDeclarations?.(declarations.length === 0 ? null : declarations);
|
|
136
161
|
|
|
137
162
|
for (const listener of [...this.listeners]) {
|
|
138
163
|
listener(result);
|
|
@@ -154,6 +179,7 @@ export class SymbolsFeature {
|
|
|
154
179
|
providers();
|
|
155
180
|
this.refresher.dispose();
|
|
156
181
|
editor.setBlocks(null);
|
|
182
|
+
editor.setDeclarations?.(null);
|
|
157
183
|
});
|
|
158
184
|
}
|
|
159
185
|
|