@adecore/editor-react 0.21.1 → 0.21.2
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/AnchoredPopup.js +2 -3
- package/dist/HoverSections.js +5 -11
- package/dist/PeekPanel.js +6 -13
- package/dist/RenameCard.js +2 -2
- package/dist/SymbolPicker.js +2 -3
- package/dist/code-actions-model.js +9 -8
- package/dist/code-actions.d.ts +1 -1
- package/dist/code-actions.js +21 -20
- package/dist/code-authors.js +1 -1
- package/dist/code-vision.d.ts +1 -0
- package/dist/code-vision.js +17 -14
- package/dist/completion-call.js +2 -6
- package/dist/completion-model.d.ts +3 -2
- package/dist/completion-model.js +8 -9
- package/dist/completion.js +8 -10
- package/dist/context-menu.js +4 -5
- package/dist/definition-link.js +2 -1
- package/dist/diagnostics-model.d.ts +2 -0
- package/dist/diagnostics-model.js +8 -2
- package/dist/diagnostics.d.ts +1 -0
- package/dist/diagnostics.js +13 -12
- package/dist/editor-language.d.ts +2 -2
- package/dist/editor-language.js +10 -10
- package/dist/error-message.d.ts +1 -0
- package/dist/error-message.js +3 -0
- package/dist/folding.js +5 -24
- package/dist/history.d.ts +1 -0
- package/dist/history.js +9 -12
- package/dist/hover-content.d.ts +1 -0
- package/dist/hover-content.js +5 -4
- package/dist/hover.d.ts +2 -2
- package/dist/hover.js +10 -9
- package/dist/inlay-hints.js +4 -4
- package/dist/language-ids.js +1 -1
- package/dist/line-text.d.ts +3 -0
- package/dist/line-text.js +7 -0
- package/dist/navigation.js +9 -9
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +4 -0
- package/dist/peek.js +11 -4
- package/dist/project-language.js +15 -13
- package/dist/project-problems.d.ts +1 -2
- package/dist/project-problems.js +2 -4
- package/dist/refresher.js +11 -6
- package/dist/rename.d.ts +2 -2
- package/dist/rename.js +14 -12
- package/dist/review-group.d.ts +1 -0
- package/dist/review-group.js +10 -14
- package/dist/row-host.d.ts +13 -20
- package/dist/row-host.js +33 -60
- package/dist/semantic-model.js +4 -6
- package/dist/semantic-tokens.js +2 -4
- package/dist/signature.js +1 -1
- package/dist/snippet-session.js +3 -4
- package/dist/symbol-links.js +2 -2
- package/dist/symbol-picker-model.js +15 -9
- package/dist/symbol-picker.js +2 -1
- package/dist/symbols.d.ts +7 -1
- package/dist/symbols.js +22 -28
- package/dist/testing.js +3 -3
- package/dist/word-diff.js +2 -4
- package/dist/workspace-edit.js +7 -8
- package/package.json +5 -5
- package/src/AnchoredPopup.tsx +2 -3
- package/src/HoverSections.tsx +5 -11
- package/src/PeekPanel.tsx +6 -14
- package/src/RenameCard.tsx +2 -2
- package/src/SymbolPicker.tsx +3 -4
- package/src/code-actions-model.ts +10 -8
- package/src/code-actions.ts +36 -38
- package/src/code-authors.ts +2 -2
- package/src/code-vision.ts +19 -15
- package/src/completion-call.ts +2 -6
- package/src/completion-model.ts +10 -12
- package/src/completion.ts +8 -13
- package/src/context-menu.ts +4 -5
- package/src/definition-link.ts +2 -1
- package/src/diagnostics-model.ts +10 -2
- package/src/diagnostics.ts +15 -13
- package/src/editor-language.ts +11 -11
- package/src/error-message.ts +3 -0
- package/src/folding.ts +6 -26
- package/src/history.ts +11 -12
- package/src/hover-content.ts +6 -4
- package/src/hover.ts +10 -9
- package/src/inlay-hints.ts +4 -4
- package/src/language-ids.ts +1 -1
- package/src/line-text.ts +10 -0
- package/src/navigation.ts +11 -10
- package/src/paths.ts +5 -0
- package/src/peek.ts +11 -10
- package/src/project-language.ts +17 -16
- package/src/project-problems.ts +3 -6
- package/src/refresher.ts +12 -6
- package/src/rename.ts +16 -18
- package/src/review-group.ts +11 -15
- package/src/row-host.ts +54 -76
- package/src/semantic-model.ts +4 -6
- package/src/semantic-tokens.ts +2 -4
- package/src/signature.ts +1 -1
- package/src/snippet-session.ts +3 -4
- package/src/symbol-links.ts +2 -2
- package/src/symbol-picker-model.ts +17 -9
- package/src/symbol-picker.ts +2 -1
- package/src/symbols.ts +29 -28
- package/src/testing.ts +4 -2
- package/src/view-state.ts +2 -0
- package/src/word-diff.ts +2 -4
- package/src/workspace-edit.ts +7 -8
package/dist/rename.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { StaleResultError } from '@adecore/lsp';
|
|
2
|
+
import { lineTextOf } from './line-text.js';
|
|
2
3
|
import { occurrencesOf, renamePreviewOf, renameTargetOf, wordRangeAt } from './rename-model.js';
|
|
3
4
|
import { isShortcut } from './shortcut-keys.js';
|
|
5
|
+
import { messageOf } from './error-message.js';
|
|
4
6
|
const METHOD = 'textDocument/rename';
|
|
5
7
|
const TOAST_ID = 'language-rename';
|
|
6
|
-
/* How
|
|
8
|
+
/* How much is read to tell a name suggester how the symbol is used: its first places, other files and lines around it. */
|
|
7
9
|
const SAMPLE_LOCATIONS = 8;
|
|
8
10
|
const SAMPLE_OTHER_FILES = 3;
|
|
9
11
|
const CONTEXT_LINES = 3;
|
|
@@ -13,9 +15,6 @@ const CONTEXT_LINES = 3;
|
|
|
13
15
|
* Edits to other files land as unsaved drafts (`applyWorkspaceEdit`), so nothing is written to disk here.
|
|
14
16
|
*/
|
|
15
17
|
export class RenameFeature {
|
|
16
|
-
tell(kind, title) {
|
|
17
|
-
this.language.project.host.notify?.({ id: TOAST_ID, kind, title });
|
|
18
|
-
}
|
|
19
18
|
language;
|
|
20
19
|
session = null;
|
|
21
20
|
constructor(language) {
|
|
@@ -60,7 +59,7 @@ export class RenameFeature {
|
|
|
60
59
|
}
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
|
-
const target = renameTargetOf(answer, (range) => editor.textInRange(range), () => wordRangeAt(editor
|
|
62
|
+
const target = renameTargetOf(answer, (range) => editor.textInRange(range), () => wordRangeAt(lineTextOf(editor, caret.line), caret));
|
|
64
63
|
if (target === null) {
|
|
65
64
|
this.tell('error', this.say('nothing'));
|
|
66
65
|
return;
|
|
@@ -110,7 +109,7 @@ export class RenameFeature {
|
|
|
110
109
|
}
|
|
111
110
|
catch (error) {
|
|
112
111
|
if (this.session === session) {
|
|
113
|
-
this.fail(error instanceof StaleResultError ? this.say('stale') :
|
|
112
|
+
this.fail(error instanceof StaleResultError ? this.say('stale') : messageOf(error));
|
|
114
113
|
}
|
|
115
114
|
}
|
|
116
115
|
}
|
|
@@ -127,6 +126,12 @@ export class RenameFeature {
|
|
|
127
126
|
}
|
|
128
127
|
this.finish();
|
|
129
128
|
}
|
|
129
|
+
/* The name was changed, which answers the failure under it. */
|
|
130
|
+
edited() {
|
|
131
|
+
if (this.language.popups.getState().rename?.error != null) {
|
|
132
|
+
this.publish({ error: null });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
130
135
|
async apply(session) {
|
|
131
136
|
const result = await this.language.project.applyWorkspaceEdit(session.edit);
|
|
132
137
|
if (this.session !== session) {
|
|
@@ -146,12 +151,6 @@ export class RenameFeature {
|
|
|
146
151
|
this.publish({ phase: 'input', busy: false, files: [], error: this.say('failed', { message }) });
|
|
147
152
|
}
|
|
148
153
|
}
|
|
149
|
-
/* The name was changed, which answers the failure under it. */
|
|
150
|
-
edited() {
|
|
151
|
-
if (this.language.popups.getState().rename?.error != null) {
|
|
152
|
-
this.publish({ error: null });
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
154
|
finish() {
|
|
156
155
|
this.session?.names.abort();
|
|
157
156
|
this.session = null;
|
|
@@ -251,6 +250,9 @@ export class RenameFeature {
|
|
|
251
250
|
}
|
|
252
251
|
});
|
|
253
252
|
}
|
|
253
|
+
tell(kind, title) {
|
|
254
|
+
this.language.project.host.notify?.({ id: TOAST_ID, kind, title });
|
|
255
|
+
}
|
|
254
256
|
say(key, options) {
|
|
255
257
|
return this.language.project.i18n.t(`editor:language.rename.${key}`, options);
|
|
256
258
|
}
|
package/dist/review-group.d.ts
CHANGED
package/dist/review-group.js
CHANGED
|
@@ -12,31 +12,27 @@ export class ReviewGroup {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
hide(runIds) {
|
|
15
|
-
|
|
16
|
-
member.hide(runIds);
|
|
17
|
-
}
|
|
15
|
+
this.each((member) => member.hide(runIds));
|
|
18
16
|
}
|
|
19
17
|
show(runIds) {
|
|
20
|
-
|
|
21
|
-
member.show(runIds);
|
|
22
|
-
}
|
|
18
|
+
this.each((member) => member.show(runIds));
|
|
23
19
|
}
|
|
24
20
|
refresh() {
|
|
21
|
+
this.each((member) => member.refresh());
|
|
22
|
+
}
|
|
23
|
+
/* Over a copy, since a member may leave while it hears. */
|
|
24
|
+
each(action) {
|
|
25
25
|
for (const member of [...this.members]) {
|
|
26
|
-
member
|
|
26
|
+
action(member);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
const groups = new Map();
|
|
31
31
|
/* The host supplies a shared file key; the group is released with its last editor. */
|
|
32
32
|
export function joinReviewGroup(key) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
groups.set(key, entry);
|
|
37
|
-
}
|
|
38
|
-
entry.holders++;
|
|
39
|
-
const held = entry;
|
|
33
|
+
const held = groups.get(key) ?? { group: new ReviewGroup(), holders: 0 };
|
|
34
|
+
groups.set(key, held);
|
|
35
|
+
held.holders++;
|
|
40
36
|
let left = false;
|
|
41
37
|
return {
|
|
42
38
|
group: held.group,
|
package/dist/row-host.d.ts
CHANGED
|
@@ -5,29 +5,13 @@ export interface HostedRow {
|
|
|
5
5
|
placement: 'above' | 'below';
|
|
6
6
|
height: number;
|
|
7
7
|
}
|
|
8
|
-
export declare class RowHost {
|
|
9
|
-
private readonly editor;
|
|
10
|
-
private readonly owner;
|
|
11
|
-
private readonly containers;
|
|
12
|
-
private readonly listeners;
|
|
13
|
-
private version;
|
|
14
|
-
private signature;
|
|
15
|
-
constructor(editor: Editor, owner: string);
|
|
16
|
-
getVersion: () => number;
|
|
17
|
-
subscribe: (listener: () => void) => (() => void);
|
|
18
|
-
container(id: string): HTMLElement | undefined;
|
|
19
|
-
set(rows: readonly HostedRow[]): void;
|
|
20
|
-
clear(): void;
|
|
21
|
-
private mounted;
|
|
22
|
-
private bump;
|
|
23
|
-
}
|
|
24
8
|
export interface HostedAction {
|
|
25
9
|
id: string;
|
|
26
10
|
line: number;
|
|
27
11
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
declare abstract class ContainerHost {
|
|
13
|
+
protected readonly editor: Editor;
|
|
14
|
+
protected readonly owner: string;
|
|
31
15
|
private readonly containers;
|
|
32
16
|
private readonly listeners;
|
|
33
17
|
private version;
|
|
@@ -36,7 +20,16 @@ export declare class LineActionHost {
|
|
|
36
20
|
getVersion: () => number;
|
|
37
21
|
subscribe: (listener: () => void) => (() => void);
|
|
38
22
|
container(id: string): HTMLElement | undefined;
|
|
23
|
+
protected replace(signature: string, ids: readonly string[]): boolean;
|
|
24
|
+
protected mounted(id: string, container: HTMLElement): void;
|
|
25
|
+
protected bump(): void;
|
|
26
|
+
}
|
|
27
|
+
export declare class RowHost extends ContainerHost {
|
|
28
|
+
set(rows: readonly HostedRow[]): void;
|
|
29
|
+
clear(): void;
|
|
30
|
+
}
|
|
31
|
+
export declare class LineActionHost extends ContainerHost {
|
|
39
32
|
set(actions: readonly HostedAction[]): void;
|
|
40
33
|
clear(): void;
|
|
41
|
-
private bump;
|
|
42
34
|
}
|
|
35
|
+
export {};
|
package/dist/row-host.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
* into them. The editor makes an element again whenever its row scrolls back into view, which is why a
|
|
4
|
-
* row's content never keeps state of its own.
|
|
5
|
-
*/
|
|
6
|
-
export class RowHost {
|
|
1
|
+
/* The elements an editor made for one owner's entries, and a version React reads them by. */
|
|
2
|
+
class ContainerHost {
|
|
7
3
|
editor;
|
|
8
4
|
owner;
|
|
9
5
|
containers = new Map();
|
|
@@ -24,19 +20,42 @@ export class RowHost {
|
|
|
24
20
|
container(id) {
|
|
25
21
|
return this.containers.get(id);
|
|
26
22
|
}
|
|
27
|
-
/*
|
|
28
|
-
|
|
29
|
-
const signature = rows.map((row) => `${row.id}@${row.line}${row.placement}`).join('|');
|
|
23
|
+
/* False for the same entries again, so an editor does not measure them twice; otherwise drops the elements of entries that left. */
|
|
24
|
+
replace(signature, ids) {
|
|
30
25
|
if (signature === this.signature) {
|
|
31
|
-
return;
|
|
26
|
+
return false;
|
|
32
27
|
}
|
|
33
28
|
this.signature = signature;
|
|
34
|
-
const kept = new Set(
|
|
29
|
+
const kept = new Set(ids);
|
|
35
30
|
for (const id of [...this.containers.keys()]) {
|
|
36
31
|
if (!kept.has(id)) {
|
|
37
32
|
this.containers.delete(id);
|
|
38
33
|
}
|
|
39
34
|
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
mounted(id, container) {
|
|
38
|
+
this.containers.set(id, container);
|
|
39
|
+
this.bump();
|
|
40
|
+
}
|
|
41
|
+
bump() {
|
|
42
|
+
this.version++;
|
|
43
|
+
for (const listener of [...this.listeners]) {
|
|
44
|
+
listener();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/*
|
|
49
|
+
* The rows of one owner in an editor, and the elements the editor draws them in, so React can portal
|
|
50
|
+
* into them. The editor makes an element again whenever its row scrolls back into view, which is why a
|
|
51
|
+
* row's content never keeps state of its own.
|
|
52
|
+
*/
|
|
53
|
+
export class RowHost extends ContainerHost {
|
|
54
|
+
set(rows) {
|
|
55
|
+
const signature = rows.map((row) => `${row.id}@${row.line}${row.placement}`).join('|');
|
|
56
|
+
if (!this.replace(signature, rows.map((row) => row.id))) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
40
59
|
this.editor.setWidgets(rows.map((row) => ({
|
|
41
60
|
id: row.id,
|
|
42
61
|
line: row.line,
|
|
@@ -49,72 +68,26 @@ export class RowHost {
|
|
|
49
68
|
clear() {
|
|
50
69
|
this.set([]);
|
|
51
70
|
}
|
|
52
|
-
mounted(id, container) {
|
|
53
|
-
this.containers.set(id, container);
|
|
54
|
-
this.bump();
|
|
55
|
-
}
|
|
56
|
-
bump() {
|
|
57
|
-
this.version++;
|
|
58
|
-
for (const listener of [...this.listeners]) {
|
|
59
|
-
listener();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
71
|
}
|
|
63
72
|
/*
|
|
64
73
|
* The actions of one owner after the end of lines in an editor, and the elements the editor makes for
|
|
65
74
|
* them. An element lives as long as its action does, unlike a row's, so what React draws into it keeps
|
|
66
75
|
* its state.
|
|
67
76
|
*/
|
|
68
|
-
export class LineActionHost {
|
|
69
|
-
editor;
|
|
70
|
-
owner;
|
|
71
|
-
containers = new Map();
|
|
72
|
-
listeners = new Set();
|
|
73
|
-
version = 0;
|
|
74
|
-
signature = '';
|
|
75
|
-
constructor(editor, owner) {
|
|
76
|
-
this.editor = editor;
|
|
77
|
-
this.owner = owner;
|
|
78
|
-
}
|
|
79
|
-
getVersion = () => this.version;
|
|
80
|
-
subscribe = (listener) => {
|
|
81
|
-
this.listeners.add(listener);
|
|
82
|
-
return () => {
|
|
83
|
-
this.listeners.delete(listener);
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
container(id) {
|
|
87
|
-
return this.containers.get(id);
|
|
88
|
-
}
|
|
77
|
+
export class LineActionHost extends ContainerHost {
|
|
89
78
|
set(actions) {
|
|
90
79
|
const signature = actions.map((action) => `${action.id}@${action.line}`).join('|');
|
|
91
|
-
if (signature
|
|
80
|
+
if (!this.replace(signature, actions.map((action) => action.id))) {
|
|
92
81
|
return;
|
|
93
82
|
}
|
|
94
|
-
this.signature = signature;
|
|
95
|
-
const kept = new Set(actions.map((action) => action.id));
|
|
96
|
-
for (const id of [...this.containers.keys()]) {
|
|
97
|
-
if (!kept.has(id)) {
|
|
98
|
-
this.containers.delete(id);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
83
|
this.editor.setLineActions(actions.map((action) => ({
|
|
102
84
|
id: action.id,
|
|
103
85
|
line: action.line,
|
|
104
|
-
render: (container) =>
|
|
105
|
-
this.containers.set(action.id, container);
|
|
106
|
-
this.bump();
|
|
107
|
-
}
|
|
86
|
+
render: (container) => this.mounted(action.id, container)
|
|
108
87
|
})), this.owner);
|
|
109
88
|
this.bump();
|
|
110
89
|
}
|
|
111
90
|
clear() {
|
|
112
91
|
this.set([]);
|
|
113
92
|
}
|
|
114
|
-
bump() {
|
|
115
|
-
this.version++;
|
|
116
|
-
for (const listener of [...this.listeners]) {
|
|
117
|
-
listener();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
93
|
}
|
package/dist/semantic-model.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* The TextMate scopes a classification stands for, so the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* local to a function, so a variable it leaves unmarked lives at the top of a file or comes from a
|
|
7
|
-
* library, which the scheme draws as a global.
|
|
2
|
+
* The TextMate scopes a classification stands for, so the theme colors it as its grammar would; null where
|
|
3
|
+
* the grammar already did better (a keyword, a string, a local variable). Calls, methods, statics and
|
|
4
|
+
* declarations get scopes of their own, as the color scheme tells them apart. `scoped` says the server
|
|
5
|
+
* marks what is local to a function, so a variable it leaves unmarked is drawn as a global.
|
|
8
6
|
*/
|
|
9
7
|
export function scopesOf(type, modifiers, scoped = false) {
|
|
10
8
|
switch (type) {
|
package/dist/semantic-tokens.js
CHANGED
|
@@ -4,10 +4,8 @@ import { realTimers } from './timers.js';
|
|
|
4
4
|
const METHOD = 'textDocument/semanticTokens/full';
|
|
5
5
|
const PAUSE_MS = 200;
|
|
6
6
|
/*
|
|
7
|
-
* Colors
|
|
8
|
-
*
|
|
9
|
-
* typing and when a server says its offer changed, and keeps drawing the last answer, which follows its
|
|
10
|
-
* text through edits, in between, so the colors never blink out while the next one is on its way.
|
|
7
|
+
* Colors from what the language servers know, over the grammar's guess. It asks after a pause in typing
|
|
8
|
+
* and when a server's offer changed, and keeps drawing the last answer meanwhile so the colors never blink out.
|
|
11
9
|
*/
|
|
12
10
|
export class SemanticTokensFeature {
|
|
13
11
|
refresher;
|
package/dist/signature.js
CHANGED
|
@@ -57,7 +57,7 @@ export class SignatureFeature {
|
|
|
57
57
|
trigger() {
|
|
58
58
|
this.ask({ triggerKind: 2, triggerCharacter: '(', isRetrigger: false });
|
|
59
59
|
}
|
|
60
|
-
/* Shows the next
|
|
60
|
+
/* Shows the next or previous overload; false when the card has fewer than two, which leaves the arrow to the caret. */
|
|
61
61
|
cycle(step) {
|
|
62
62
|
const { help } = this;
|
|
63
63
|
if (help === null || help.signatures.length < 2 || !this.isOpen) {
|
package/dist/snippet-session.js
CHANGED
|
@@ -7,10 +7,9 @@ export function positionInInsertion(start, text, offset) {
|
|
|
7
7
|
: { line: start.line + lines.length - 1, character: lines[lines.length - 1].length };
|
|
8
8
|
}
|
|
9
9
|
/*
|
|
10
|
-
* The tab stops of a snippet that was just inserted.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* a stop does not touch the others: they follow the text, but a mirror of a stop is not edited along.
|
|
10
|
+
* The tab stops of a snippet that was just inserted. Tab and Shift+Tab move between them; the last stop,
|
|
11
|
+
* Escape, a caret that leaves the snippet and any change that is not typing end it. A mirror of a stop
|
|
12
|
+
* follows the text but is not edited along.
|
|
14
13
|
*/
|
|
15
14
|
export class SnippetFeature {
|
|
16
15
|
language;
|
package/dist/symbol-links.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* The classes a name in a signature has: a pointer and an underline on hover, and the accent only then, so the signature stays calm. */
|
|
2
2
|
export const SYMBOL_LINK_CLASS = 'cursor-pointer hover:text-accent hover:underline';
|
|
3
|
-
/* A name that reads as a type: it starts with a capital and has a lowercase letter, which leaves `DEFAULT_WEIGHTS` and `T` alone. */
|
|
4
3
|
const TYPE_NAME = /^[A-Z][A-Za-z0-9_]*$/;
|
|
4
|
+
/* A name that starts with a capital and has a lowercase letter, which leaves `DEFAULT_WEIGHTS` and `T` alone. */
|
|
5
5
|
export function isTypeName(name) {
|
|
6
6
|
return TYPE_NAME.test(name) && /[a-z]/.test(name);
|
|
7
7
|
}
|
|
@@ -17,7 +17,7 @@ const DECLARES = /\b(?:class|interface|enum|trait|type|namespace|struct|function
|
|
|
17
17
|
export function declaredNameOf(code) {
|
|
18
18
|
return DECLARES.exec(code)?.[1] ?? null;
|
|
19
19
|
}
|
|
20
|
-
/*
|
|
20
|
+
/* The symbol kinds a type name can mean: module, namespace, class, enum, interface, struct and type parameter. */
|
|
21
21
|
const TYPE_KINDS = new Set([2, 3, 5, 10, 11, 23, 26]);
|
|
22
22
|
/*
|
|
23
23
|
* The places a type name leads to among the symbols a server found for it. Only symbols of exactly that name
|
|
@@ -32,6 +32,7 @@ const GROUP_OF_KIND = {
|
|
|
32
32
|
export function groupOfKind(kind) {
|
|
33
33
|
return GROUP_OF_KIND[kind] ?? 'other';
|
|
34
34
|
}
|
|
35
|
+
const ENUM_MEMBER = 22;
|
|
35
36
|
const LETTERS = {
|
|
36
37
|
namespaces: 'N',
|
|
37
38
|
classes: 'C',
|
|
@@ -46,17 +47,22 @@ const LETTERS = {
|
|
|
46
47
|
};
|
|
47
48
|
/* The letter in the badge in front of a symbol. */
|
|
48
49
|
export function letterOfKind(kind) {
|
|
49
|
-
return kind ===
|
|
50
|
+
return kind === ENUM_MEMBER ? 'e' : LETTERS[groupOfKind(kind)];
|
|
50
51
|
}
|
|
52
|
+
const TONES = {
|
|
53
|
+
namespaces: 'other',
|
|
54
|
+
classes: 'type',
|
|
55
|
+
interfaces: 'type',
|
|
56
|
+
enums: 'type',
|
|
57
|
+
functions: 'callable',
|
|
58
|
+
methods: 'callable',
|
|
59
|
+
properties: 'value',
|
|
60
|
+
variables: 'value',
|
|
61
|
+
constants: 'value',
|
|
62
|
+
other: 'other'
|
|
63
|
+
};
|
|
51
64
|
export function toneOfKind(kind) {
|
|
52
|
-
|
|
53
|
-
return group === 'functions' || group === 'methods'
|
|
54
|
-
? 'callable'
|
|
55
|
-
: group === 'classes' || group === 'interfaces' || group === 'enums'
|
|
56
|
-
? 'type'
|
|
57
|
-
: group === 'properties' || group === 'variables' || group === 'constants'
|
|
58
|
-
? 'value'
|
|
59
|
-
: 'other';
|
|
65
|
+
return TONES[groupOfKind(kind)];
|
|
60
66
|
}
|
|
61
67
|
function isHierarchical(symbols) {
|
|
62
68
|
return symbols.length > 0 && 'selectionRange' in symbols[0];
|
package/dist/symbol-picker.js
CHANGED
|
@@ -2,6 +2,7 @@ import { StaleResultError } from '@adecore/lsp';
|
|
|
2
2
|
import { basenameOf } from './paths.js';
|
|
3
3
|
import { entriesOf, workspaceEntriesOf } from './symbol-picker-model.js';
|
|
4
4
|
import { isShortcut } from './shortcut-keys.js';
|
|
5
|
+
import { messageOf } from './error-message.js';
|
|
5
6
|
const TOAST_ID = 'language-symbols';
|
|
6
7
|
/*
|
|
7
8
|
* Go to symbol: a picker over the symbols the language servers know in the file, filtered as it is typed.
|
|
@@ -39,7 +40,7 @@ export class SymbolPickerFeature {
|
|
|
39
40
|
this.language.project.host.notify?.({
|
|
40
41
|
id: TOAST_ID,
|
|
41
42
|
kind: 'error',
|
|
42
|
-
title: this.language.project.i18n.t('editor:language.symbols.failed', { message:
|
|
43
|
+
title: this.language.project.i18n.t('editor:language.symbols.failed', { message: messageOf(error) })
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
46
|
}
|
package/dist/symbols.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { EditorBlock } from '@adecore/editor';
|
|
2
|
-
import type { DocumentSymbolResult } from '@adecore/lsp';
|
|
2
|
+
import type { DocumentSymbolResult, Range } from '@adecore/lsp';
|
|
3
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
4
4
|
import { type Timers } from './timers.ts';
|
|
5
|
+
export interface SymbolSpan {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly kind: number;
|
|
8
|
+
readonly range: Range;
|
|
9
|
+
}
|
|
10
|
+
export declare function symbolSpansOf(result: DocumentSymbolResult): SymbolSpan[];
|
|
5
11
|
export declare function blocksOf(result: DocumentSymbolResult): EditorBlock[];
|
|
6
12
|
export declare class SymbolsFeature {
|
|
7
13
|
private readonly refresher;
|
package/dist/symbols.js
CHANGED
|
@@ -17,40 +17,34 @@ const KINDS = {
|
|
|
17
17
|
14: 'constant',
|
|
18
18
|
23: 'struct'
|
|
19
19
|
};
|
|
20
|
-
function
|
|
21
|
-
return symbols.length > 0 && 'range' in symbols[0];
|
|
22
|
-
}
|
|
23
|
-
function blockOf(name, kind, startLine, endLine) {
|
|
24
|
-
const label = KINDS[kind];
|
|
25
|
-
// A one-line symbol has no body to pin, and a property or an enum member is part of the block around it.
|
|
26
|
-
return label === undefined || endLine <= startLine ? null : { startLine: startLine + 1, endLine: endLine + 1, name, kind: label };
|
|
27
|
-
}
|
|
28
|
-
function walk(symbols, blocks) {
|
|
20
|
+
function walk(symbols, spans) {
|
|
29
21
|
for (const symbol of symbols) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
blocks.push(block);
|
|
33
|
-
}
|
|
34
|
-
walk(symbol.children ?? [], blocks);
|
|
22
|
+
spans.push({ name: symbol.name, kind: symbol.kind, range: symbol.range });
|
|
23
|
+
walk(symbol.children ?? [], spans);
|
|
35
24
|
}
|
|
36
25
|
}
|
|
37
|
-
/*
|
|
38
|
-
export function
|
|
39
|
-
const
|
|
26
|
+
/* Every symbol of an answer, a parent before its children, whether the server sent a tree or a flat list. */
|
|
27
|
+
export function symbolSpansOf(result) {
|
|
28
|
+
const spans = [];
|
|
40
29
|
if (result === null || result.length === 0) {
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
if (isHierarchical(result)) {
|
|
44
|
-
walk(result, blocks);
|
|
30
|
+
return spans;
|
|
45
31
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (block !== null) {
|
|
50
|
-
blocks.push(block);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
32
|
+
if ('range' in result[0]) {
|
|
33
|
+
walk(result, spans);
|
|
34
|
+
return spans;
|
|
53
35
|
}
|
|
36
|
+
return result.map((symbol) => ({ name: symbol.name, kind: symbol.kind, range: symbol.location.range }));
|
|
37
|
+
}
|
|
38
|
+
function blockOf({ name, kind, range }) {
|
|
39
|
+
const label = KINDS[kind];
|
|
40
|
+
// A one-line symbol has no body to pin, and a property or an enum member is part of the block around it.
|
|
41
|
+
return label === undefined || range.end.line <= range.start.line
|
|
42
|
+
? null
|
|
43
|
+
: { startLine: range.start.line + 1, endLine: range.end.line + 1, name, kind: label };
|
|
44
|
+
}
|
|
45
|
+
/* The multi-line symbols of a file as blocks, outermost first, in one-based lines. */
|
|
46
|
+
export function blocksOf(result) {
|
|
47
|
+
const blocks = symbolSpansOf(result).flatMap((span) => blockOf(span) ?? []);
|
|
54
48
|
return blocks.sort((left, right) => left.startLine - right.startLine || right.endLine - left.endLine);
|
|
55
49
|
}
|
|
56
50
|
/*
|
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyContentChanges, LspError, StaleResultError } from '@adecore/lsp';
|
|
1
|
+
import { applyContentChanges, ErrorCodes, LspError, StaleResultError } from '@adecore/lsp';
|
|
2
2
|
export { ManualTimers } from './timers.js';
|
|
3
3
|
export class FakeLanguageService {
|
|
4
4
|
documents = new Map();
|
|
@@ -20,7 +20,7 @@ export class FakeLanguageService {
|
|
|
20
20
|
async changeDocument(uri, changes) {
|
|
21
21
|
const held = this.documents.get(uri);
|
|
22
22
|
if (held === undefined) {
|
|
23
|
-
throw new LspError('Document is not open',
|
|
23
|
+
throw new LspError('Document is not open', ErrorCodes.ServerNotInitialized);
|
|
24
24
|
}
|
|
25
25
|
held.text = applyContentChanges(held.text, changes);
|
|
26
26
|
held.version++;
|
|
@@ -56,7 +56,7 @@ export class FakeLanguageService {
|
|
|
56
56
|
}
|
|
57
57
|
async request(method, uri, params, options = {}) {
|
|
58
58
|
if (!this.supports(method, uri)) {
|
|
59
|
-
throw new LspError(`${method} is unavailable`,
|
|
59
|
+
throw new LspError(`${method} is unavailable`, ErrorCodes.MethodNotFound);
|
|
60
60
|
}
|
|
61
61
|
options.signal?.throwIfAborted();
|
|
62
62
|
const version = this.documents.get(uri)?.version;
|
package/dist/word-diff.js
CHANGED
|
@@ -2,10 +2,8 @@ import { diffLines } from '@adecore/merge';
|
|
|
2
2
|
/* A word, a run of spaces or one other character, so a change inside a line is found by word and not by letter. */
|
|
3
3
|
const TOKEN = /\w+|\s+|[^\w\s]/g;
|
|
4
4
|
/*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
* removed, since then each removed line has the line it became; any other change has no such pairing
|
|
8
|
-
* and draws no words.
|
|
5
|
+
* The `[start, end)` ranges of each removed line that the added lines replaced. Lines pair up only when as
|
|
6
|
+
* many were added as removed; any other change has no pairing and marks no words.
|
|
9
7
|
*/
|
|
10
8
|
export function replacedWords(removed, added) {
|
|
11
9
|
return removed.map((line, index) => (removed.length === added.length ? replacedRanges(line, added[index]) : []));
|
package/dist/workspace-edit.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { applyTextEdits, fileUriToPath } from '@adecore/lsp';
|
|
2
|
+
import { messageOf } from './error-message.js';
|
|
2
3
|
function refused(failureReason) {
|
|
3
4
|
return { applied: false, failureReason };
|
|
4
5
|
}
|
|
@@ -48,12 +49,10 @@ function contentChangeOf(edit) {
|
|
|
48
49
|
return { range: edit.range, text: edit.newText };
|
|
49
50
|
}
|
|
50
51
|
/*
|
|
51
|
-
* Applies an LSP workspace edit
|
|
52
|
-
* its edits as one undo step
|
|
53
|
-
* nothing is written to disk on a server's word alone, unless the edit also moves a file:
|
|
54
|
-
*
|
|
55
|
-
* the edit creates is made through the machine with the text the edit writes into it. The edit is checked
|
|
56
|
-
* against every text first, so one that does not fit changes nothing.
|
|
52
|
+
* Applies an LSP workspace edit step by step, after checking it against every text so one that does not fit
|
|
53
|
+
* changes nothing. A file an editor holds takes its edits as one undo step; any other file gets an unsaved
|
|
54
|
+
* draft, since nothing is written to disk on a server's word alone, unless the edit also moves a file: then it
|
|
55
|
+
* is one change, saved and moved through the machine. A file the edit creates is made through the machine.
|
|
57
56
|
*/
|
|
58
57
|
export async function applyWorkspaceEdit(edit, host) {
|
|
59
58
|
const steps = stepsOf(edit);
|
|
@@ -141,7 +140,7 @@ async function plan(steps, host) {
|
|
|
141
140
|
created.text = applyTextEdits(created.text, step.edits);
|
|
142
141
|
}
|
|
143
142
|
catch (error) {
|
|
144
|
-
return
|
|
143
|
+
return messageOf(error);
|
|
145
144
|
}
|
|
146
145
|
continue;
|
|
147
146
|
}
|
|
@@ -165,7 +164,7 @@ async function plan(steps, host) {
|
|
|
165
164
|
after = applyTextEdits(held.text, step.edits);
|
|
166
165
|
}
|
|
167
166
|
catch (error) {
|
|
168
|
-
return
|
|
167
|
+
return messageOf(error);
|
|
169
168
|
}
|
|
170
169
|
planned.push({ kind: 'edits', uri: step.uri, edits: step.edits, before: held.text, after, disk: held.disk });
|
|
171
170
|
texts.set(source, { text: after, disk: held.disk });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/editor-react",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Language-enabled editors, feature coordination and React popups over injected language services.",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"test": "bun test --conditions=source"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@adecore/editor": "0.21.
|
|
61
|
-
"@adecore/lsp": "0.21.
|
|
62
|
-
"@adecore/merge": "0.21.
|
|
60
|
+
"@adecore/editor": "0.21.2",
|
|
61
|
+
"@adecore/lsp": "0.21.2",
|
|
62
|
+
"@adecore/merge": "0.21.2",
|
|
63
63
|
"lucide-react": "^1.47.0",
|
|
64
64
|
"react-markdown": "^10.1.0",
|
|
65
65
|
"remark-breaks": "^4.0.0",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"zustand": "^5.0.15"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@adecore/ui": "^0.21.
|
|
70
|
+
"@adecore/ui": "^0.21.2",
|
|
71
71
|
"i18next": "^26.4.2",
|
|
72
72
|
"react": "^19.3.0",
|
|
73
73
|
"react-dom": "^19.3.0",
|
package/src/AnchoredPopup.tsx
CHANGED
|
@@ -14,9 +14,8 @@ export interface AnchoredPopupProps {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/*
|
|
17
|
-
* A card next to a character of the editor,
|
|
18
|
-
*
|
|
19
|
-
* placed from the character's screen rectangle, so it is right at any zoom, and it never leaves the window.
|
|
17
|
+
* A card next to a character of the editor, portaled to the page: inside a scaled canvas node it would be
|
|
18
|
+
* drawn at that scale and clipped by the node. Placed from the character's screen rectangle, so it holds at any zoom.
|
|
20
19
|
*/
|
|
21
20
|
export function AnchoredPopup({ rect, className, children, placement, onPointerEnter, onPointerLeave }: AnchoredPopupProps) {
|
|
22
21
|
const element = useRef<HTMLDivElement>(null);
|