@adecore/editor-react 0.23.1 → 0.24.1
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.d.ts +2 -1
- package/dist/AnchoredPopup.js +2 -2
- package/dist/CodeAuthorsCard.d.ts +4 -0
- package/dist/CodeAuthorsCard.js +8 -4
- package/dist/CompletionPopup.js +7 -3
- package/dist/EditorView.d.ts +8 -3
- package/dist/EditorView.js +79 -9
- package/dist/HoverCard.d.ts +3 -1
- package/dist/HoverCard.js +2 -2
- package/dist/LanguagePopups.d.ts +8 -4
- package/dist/LanguagePopups.js +4 -4
- package/dist/PickPopup.js +2 -1
- package/dist/RenameCard.js +10 -8
- package/dist/RenderedDocs.d.ts +10 -4
- package/dist/RenderedDocs.js +10 -6
- package/dist/call-hierarchy.js +15 -14
- package/dist/code-actions.js +5 -2
- package/dist/completion-model.js +3 -1
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +49 -27
- package/dist/diagnostics-model.d.ts +1 -0
- package/dist/diagnostics-model.js +1 -1
- package/dist/diagnostics.d.ts +1 -0
- package/dist/diagnostics.js +28 -10
- package/dist/diff-overlays.js +2 -2
- package/dist/editor-language.d.ts +3 -1
- package/dist/editor-language.js +10 -0
- package/dist/format-on-type.js +2 -3
- package/dist/highlights.js +3 -2
- package/dist/history.js +3 -2
- package/dist/holder.d.ts +3 -3
- package/dist/holder.js +2 -1
- package/dist/host-types.d.ts +3 -0
- package/dist/hover.d.ts +5 -0
- package/dist/hover.js +13 -5
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -1
- package/dist/key-priority.d.ts +23 -0
- package/dist/key-priority.js +39 -0
- package/dist/locales/en.json +2 -1
- package/dist/locales/nl.json +2 -1
- package/dist/models.d.ts +2 -2
- package/dist/models.js +2 -2
- package/dist/navigation.js +3 -2
- package/dist/peek.js +3 -2
- package/dist/pick.js +2 -1
- package/dist/popup-focus.d.ts +5 -0
- package/dist/popup-focus.js +12 -0
- package/dist/popups.d.ts +1 -1
- package/dist/project-language.js +5 -4
- package/dist/project-problems.d.ts +10 -1
- package/dist/project-problems.js +34 -10
- package/dist/proposal-model.d.ts +0 -1
- package/dist/proposal-model.js +0 -8
- package/dist/rename-model.d.ts +2 -1
- package/dist/rename-model.js +57 -0
- package/dist/rename.d.ts +1 -0
- package/dist/rename.js +21 -5
- package/dist/rendered-docs.js +3 -2
- package/dist/signature.js +4 -3
- package/dist/snippet-session.d.ts +1 -1
- package/dist/snippet-session.js +8 -2
- package/dist/symbol-picker.js +3 -2
- package/package.json +6 -6
- package/src/AnchoredPopup.tsx +4 -2
- package/src/CodeAuthorsCard.tsx +21 -12
- package/src/CompletionPopup.tsx +11 -1
- package/src/EditorView.tsx +122 -11
- package/src/HoverCard.tsx +6 -1
- package/src/LanguagePopups.tsx +23 -4
- package/src/PickPopup.tsx +9 -1
- package/src/RenameCard.tsx +15 -9
- package/src/RenderedDocs.tsx +30 -8
- package/src/call-hierarchy.ts +15 -14
- package/src/code-actions.ts +5 -2
- package/src/completion-model.ts +3 -1
- package/src/completion.ts +48 -28
- package/src/diagnostics-model.ts +1 -1
- package/src/diagnostics.ts +29 -13
- package/src/diff-overlays.tsx +12 -8
- package/src/editor-language.ts +12 -1
- package/src/format-on-type.ts +2 -3
- package/src/highlights.ts +3 -2
- package/src/history.ts +3 -2
- package/src/holder.ts +5 -4
- package/src/host-types.ts +5 -0
- package/src/hover.ts +19 -5
- package/src/index.ts +6 -2
- package/src/key-priority.ts +49 -0
- package/src/locales/en.json +2 -1
- package/src/locales/nl.json +2 -1
- package/src/models.ts +1 -1
- package/src/navigation.ts +3 -2
- package/src/peek.ts +3 -2
- package/src/pick.ts +2 -1
- package/src/popup-focus.ts +17 -0
- package/src/popups.ts +1 -2
- package/src/project-language.ts +5 -4
- package/src/project-problems.ts +46 -11
- package/src/proposal-model.ts +0 -9
- package/src/rename-model.ts +60 -1
- package/src/rename.ts +21 -5
- package/src/rendered-docs.ts +3 -2
- package/src/signature.ts +4 -3
- package/src/snippet-session.ts +9 -3
- package/src/symbol-picker.ts +3 -2
- package/dist/relative-time.d.ts +0 -1
- package/dist/relative-time.js +0 -9
- package/src/relative-time.ts +0 -10
package/dist/call-hierarchy.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StaleResultError } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
2
3
|
import { CALL_LEVEL_LIMIT, childrenOf, moreIdOf, rootsOf, rowsOf, statusIdOf, stepsOf, targetOf } from './call-hierarchy-model.js';
|
|
3
4
|
import { PEEK_HEIGHT } from './peek.js';
|
|
4
5
|
import { snippetOf } from './peek-model.js';
|
|
@@ -34,7 +35,7 @@ export class CallHierarchyFeature {
|
|
|
34
35
|
this.timers = timers;
|
|
35
36
|
const { editor } = language;
|
|
36
37
|
const offs = [
|
|
37
|
-
|
|
38
|
+
language.onKey((event) => {
|
|
38
39
|
if (isShortcut(this.language.shortcuts.callHierarchy, event)) {
|
|
39
40
|
if (this.isOpen) {
|
|
40
41
|
this.setDirection(this.direction === 'incoming' ? 'outgoing' : 'incoming');
|
|
@@ -45,7 +46,7 @@ export class CallHierarchyFeature {
|
|
|
45
46
|
return true;
|
|
46
47
|
}
|
|
47
48
|
return this.key(event);
|
|
48
|
-
}),
|
|
49
|
+
}, KEY_PRIORITY.callHierarchy),
|
|
49
50
|
editor.onTextChange(() => this.close())
|
|
50
51
|
];
|
|
51
52
|
language.onDispose(() => {
|
|
@@ -56,7 +57,7 @@ export class CallHierarchyFeature {
|
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
get isOpen() {
|
|
59
|
-
return
|
|
60
|
+
return this.language.popups.getState().callHierarchy !== null;
|
|
60
61
|
}
|
|
61
62
|
/* Whether a server of this document answers the three requests the tree is made of. */
|
|
62
63
|
get supported() {
|
|
@@ -111,7 +112,7 @@ export class CallHierarchyFeature {
|
|
|
111
112
|
}
|
|
112
113
|
close() {
|
|
113
114
|
this.reset();
|
|
114
|
-
if (this.language.popups.getState().callHierarchy
|
|
115
|
+
if (this.language.popups.getState().callHierarchy === null) {
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
118
|
this.language.popups.setState({ callHierarchy: null });
|
|
@@ -121,7 +122,7 @@ export class CallHierarchyFeature {
|
|
|
121
122
|
}
|
|
122
123
|
/* Starts the tree over from the same symbols for the other direction. */
|
|
123
124
|
setDirection(direction) {
|
|
124
|
-
const view = this.language.popups.getState().callHierarchy
|
|
125
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
125
126
|
if (view === null || view.direction === direction) {
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
@@ -145,7 +146,7 @@ export class CallHierarchyFeature {
|
|
|
145
146
|
}
|
|
146
147
|
if (node.expanded) {
|
|
147
148
|
this.patch(id, { expanded: false });
|
|
148
|
-
const view = this.language.popups.getState().callHierarchy
|
|
149
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
149
150
|
if (view !== null && this.hasAncestor(view.active, id)) {
|
|
150
151
|
this.select(id);
|
|
151
152
|
}
|
|
@@ -170,7 +171,7 @@ export class CallHierarchyFeature {
|
|
|
170
171
|
}
|
|
171
172
|
this.patch(parent, { shown: Math.min(node.children.length, node.shown + CALL_LEVEL_LIMIT) });
|
|
172
173
|
const first = node.children[node.shown];
|
|
173
|
-
const view = this.language.popups.getState().callHierarchy
|
|
174
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
174
175
|
// The row that offered more is gone once everything shows, so the selection moves on to what it brought.
|
|
175
176
|
if (first !== undefined && view?.active === moreIdOf(parent)) {
|
|
176
177
|
this.select(first);
|
|
@@ -178,7 +179,7 @@ export class CallHierarchyFeature {
|
|
|
178
179
|
}
|
|
179
180
|
/* Makes a row the active one and shows the code of its call. */
|
|
180
181
|
select(id, site = 0) {
|
|
181
|
-
const view = this.language.popups.getState().callHierarchy
|
|
182
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
182
183
|
const node = this.nodes.get(id);
|
|
183
184
|
if (view === null || !view.rows.some((row) => row.id === id)) {
|
|
184
185
|
return;
|
|
@@ -192,14 +193,14 @@ export class CallHierarchyFeature {
|
|
|
192
193
|
}
|
|
193
194
|
/* Steps through the places a call is made from when the active one has more than one. */
|
|
194
195
|
stepSite(direction) {
|
|
195
|
-
const view = this.language.popups.getState().callHierarchy
|
|
196
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
196
197
|
if (view !== null && view.sites > 1) {
|
|
197
198
|
this.select(view.active, (view.site + direction + view.sites) % view.sites);
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
/* Goes to the call of a row, or to the symbol of a root, and closes the tree. */
|
|
201
202
|
go(id) {
|
|
202
|
-
const view = this.language.popups.getState().callHierarchy
|
|
203
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
203
204
|
const node = this.nodes.get(id);
|
|
204
205
|
if (view === null || node === undefined) {
|
|
205
206
|
return;
|
|
@@ -223,7 +224,7 @@ export class CallHierarchyFeature {
|
|
|
223
224
|
this.nodes = new Map(roots.map((root) => [root.id, root]));
|
|
224
225
|
}
|
|
225
226
|
mounted(container) {
|
|
226
|
-
const view = this.language.popups.getState().callHierarchy
|
|
227
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
227
228
|
if (view !== null && view.container !== container) {
|
|
228
229
|
this.language.popups.setState({ callHierarchy: { ...view, container } });
|
|
229
230
|
revealRow(this.timers, container);
|
|
@@ -231,7 +232,7 @@ export class CallHierarchyFeature {
|
|
|
231
232
|
}
|
|
232
233
|
patch(id, change) {
|
|
233
234
|
const node = this.nodes.get(id);
|
|
234
|
-
const view = this.language.popups.getState().callHierarchy
|
|
235
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
235
236
|
if (node === undefined || view === null) {
|
|
236
237
|
return;
|
|
237
238
|
}
|
|
@@ -291,7 +292,7 @@ export class CallHierarchyFeature {
|
|
|
291
292
|
this.texts.set(uri, text);
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
|
-
const view = this.language.popups.getState().callHierarchy
|
|
295
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
295
296
|
if (view === null || view.active !== node.id) {
|
|
296
297
|
return;
|
|
297
298
|
}
|
|
@@ -314,7 +315,7 @@ export class CallHierarchyFeature {
|
|
|
314
315
|
return false;
|
|
315
316
|
}
|
|
316
317
|
key(event) {
|
|
317
|
-
const view = this.language.popups.getState().callHierarchy
|
|
318
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
318
319
|
if (view === null || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || event.isComposing) {
|
|
319
320
|
return false;
|
|
320
321
|
}
|
package/dist/code-actions.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ErrorCodes, LspError, StaleResultError } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
2
3
|
import { ACTION_GROUPS, REFACTOR_GROUPS, actionsOf, diagnosticsAt, fixableOnLine, isHint, mergeEntries, previewOf } from './code-actions-model.js';
|
|
3
4
|
import { Refresher } from './refresher.js';
|
|
4
5
|
import { isShortcut } from './shortcut-keys.js';
|
|
@@ -40,11 +41,13 @@ export class CodeActionsFeature {
|
|
|
40
41
|
const offs = [
|
|
41
42
|
editor.onCaret(() => {
|
|
42
43
|
editor.setGutterAction(null);
|
|
44
|
+
// An answer for the caret's last place would light the bulb on the line it moved to.
|
|
45
|
+
this.refresher.cancel();
|
|
43
46
|
this.refresher.later();
|
|
44
47
|
}),
|
|
45
48
|
editor.onTextChange(() => editor.setGutterAction(null)),
|
|
46
49
|
editor.onGutterAction(() => void this.open()),
|
|
47
|
-
|
|
50
|
+
language.onKey((event) => {
|
|
48
51
|
if (isShortcut(this.language.shortcuts.codeActions, event)) {
|
|
49
52
|
void this.open();
|
|
50
53
|
return true;
|
|
@@ -54,7 +57,7 @@ export class CodeActionsFeature {
|
|
|
54
57
|
return true;
|
|
55
58
|
}
|
|
56
59
|
return false;
|
|
57
|
-
}),
|
|
60
|
+
}, KEY_PRIORITY.codeActions),
|
|
58
61
|
language.diagnostics.onChange(() => this.refresher.later()),
|
|
59
62
|
providers
|
|
60
63
|
];
|
package/dist/completion-model.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { labelCollator } from '@adecore/ui/format';
|
|
1
2
|
import { applyCompletionDefaults } from '@adecore/lsp';
|
|
2
3
|
import { comparePositions } from './diagnostics-model.js';
|
|
3
4
|
import { escapeMarkdown, isEmptyHover, splitSignatures } from './hover-content.js';
|
|
@@ -156,6 +157,7 @@ export function prefixFor(item, caret, lineBefore, textBetween) {
|
|
|
156
157
|
* preselected one, and the server's own order. `recency` is higher for a choice made more recently, and 0 for none.
|
|
157
158
|
*/
|
|
158
159
|
export function rankCompletions(items, prefixOf, recency = () => 0, limit = 150) {
|
|
160
|
+
const { compare } = labelCollator();
|
|
159
161
|
return items
|
|
160
162
|
.map((item, index) => {
|
|
161
163
|
const prefix = prefixOf(item);
|
|
@@ -165,7 +167,7 @@ export function rankCompletions(items, prefixOf, recency = () => 0, limit = 150)
|
|
|
165
167
|
.sort((left, right) => left.score - right.score ||
|
|
166
168
|
right.recent - left.recent ||
|
|
167
169
|
Number(right.item.preselect === true) - Number(left.item.preselect === true) ||
|
|
168
|
-
(left.item.sortText ?? left.item.label
|
|
170
|
+
compare(left.item.sortText ?? left.item.label, right.item.sortText ?? right.item.label) ||
|
|
169
171
|
left.index - right.index)
|
|
170
172
|
.slice(0, limit)
|
|
171
173
|
.map(({ item, score, prefix }) => ({ item, score, prefix }));
|
package/dist/completion.d.ts
CHANGED
|
@@ -15,8 +15,10 @@ export declare class CompletionFeature {
|
|
|
15
15
|
private server;
|
|
16
16
|
private request;
|
|
17
17
|
private controller;
|
|
18
|
+
private resolving;
|
|
19
|
+
private accepting;
|
|
20
|
+
private rankedFor;
|
|
18
21
|
private requestTimer;
|
|
19
|
-
private refreshTimer;
|
|
20
22
|
private resolveTimer;
|
|
21
23
|
private textRevision;
|
|
22
24
|
private acceptSequence;
|
|
@@ -27,6 +29,7 @@ export declare class CompletionFeature {
|
|
|
27
29
|
invoke(): void;
|
|
28
30
|
move(delta: number): void;
|
|
29
31
|
accept(replace: boolean, index?: number, commit?: string): Promise<void>;
|
|
32
|
+
private insert;
|
|
30
33
|
private refuseLargeCompletion;
|
|
31
34
|
private placeMirroredCarets;
|
|
32
35
|
private surroundings;
|
|
@@ -38,7 +41,6 @@ export declare class CompletionFeature {
|
|
|
38
41
|
private edited;
|
|
39
42
|
private caretMoved;
|
|
40
43
|
private triggerCharacters;
|
|
41
|
-
private scheduleRefresh;
|
|
42
44
|
private beginRequest;
|
|
43
45
|
private ask;
|
|
44
46
|
private refilter;
|
package/dist/completion.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SnippetTransformQueueError } from './snippet-transform-queue.js';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
2
3
|
import { SnippetTransformError } from './snippet-transform-client.js';
|
|
3
4
|
import { MAX_TRANSFORM_JOBS, MAX_TRANSFORM_WORK } from './snippet-transform-protocol.js';
|
|
4
5
|
import { fileUriToPath, StaleResultError } from '@adecore/lsp';
|
|
@@ -14,10 +15,9 @@ import { textBeforeOf } from './line-text.js';
|
|
|
14
15
|
import { isEmptyRange } from './proposal-model.js';
|
|
15
16
|
import { isShortcut } from './shortcut-keys.js';
|
|
16
17
|
import { realTimers } from './timers.js';
|
|
18
|
+
import { messageOf } from './error-message.js';
|
|
17
19
|
const METHOD = 'textDocument/completion';
|
|
18
20
|
const START_DELAY_MS = 80;
|
|
19
|
-
/* After typing stops, the list is asked for again at the new text: a server only resolves an item for the text it was made for. */
|
|
20
|
-
const REFRESH_DELAY_MS = 150;
|
|
21
21
|
const RESOLVE_DELAY_MS = 120;
|
|
22
22
|
const PAGE = 8;
|
|
23
23
|
const LINES_BACK = 60;
|
|
@@ -78,8 +78,11 @@ export class CompletionFeature {
|
|
|
78
78
|
server = '';
|
|
79
79
|
request = 0;
|
|
80
80
|
controller = null;
|
|
81
|
+
resolving = null;
|
|
82
|
+
accepting = null;
|
|
83
|
+
/* What the list was ranked for last: the caret event and the text event of one keystroke both refilter, and only the first has to rank. */
|
|
84
|
+
rankedFor = null;
|
|
81
85
|
requestTimer;
|
|
82
|
-
refreshTimer;
|
|
83
86
|
resolveTimer;
|
|
84
87
|
textRevision = 0;
|
|
85
88
|
acceptSequence = 0;
|
|
@@ -90,16 +93,18 @@ export class CompletionFeature {
|
|
|
90
93
|
this.timers = timers;
|
|
91
94
|
const { editor } = language;
|
|
92
95
|
const offs = [
|
|
93
|
-
|
|
96
|
+
language.onKey((event) => this.key(event), KEY_PRIORITY.completion),
|
|
94
97
|
editor.onTextChange((change) => this.edited(change)),
|
|
95
98
|
editor.onCaret((position) => this.caretMoved(position)),
|
|
96
99
|
editor.onBlur(() => {
|
|
97
100
|
this.acceptSequence++;
|
|
101
|
+
this.accepting?.abort();
|
|
98
102
|
this.close();
|
|
99
103
|
})
|
|
100
104
|
];
|
|
101
105
|
language.onDispose(() => {
|
|
102
106
|
this.disposed = true;
|
|
107
|
+
this.accepting?.abort();
|
|
103
108
|
for (const off of offs) {
|
|
104
109
|
off();
|
|
105
110
|
}
|
|
@@ -126,14 +131,26 @@ export class CompletionFeature {
|
|
|
126
131
|
this.publish();
|
|
127
132
|
this.scheduleResolve();
|
|
128
133
|
}
|
|
129
|
-
/* Picks a row by a click or a key. */
|
|
134
|
+
/* Picks a row by a click or a key. It never rejects: a failure is told to the person, so a caller can leave the promise alone. */
|
|
130
135
|
async accept(replace, index = this.active, commit) {
|
|
136
|
+
try {
|
|
137
|
+
await this.insert(replace, index, commit);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
const { project } = this.language;
|
|
141
|
+
project.host.notify?.({ id: TOAST_ID, kind: 'error', title: project.i18n.t('editor:language.completion.failed', { message: messageOf(error) }) });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async insert(replace, index, commit) {
|
|
131
145
|
const entry = this.ranked[index];
|
|
132
146
|
if (entry === undefined) {
|
|
133
147
|
return;
|
|
134
148
|
}
|
|
135
149
|
const { editor, project, uri, snippets } = this.language;
|
|
136
150
|
const acceptSequence = ++this.acceptSequence;
|
|
151
|
+
this.accepting?.abort();
|
|
152
|
+
const accepting = new AbortController();
|
|
153
|
+
this.accepting = accepting;
|
|
137
154
|
const snippetToken = snippets.token;
|
|
138
155
|
const inSnippet = snippets.isActive;
|
|
139
156
|
const exitChanges = [];
|
|
@@ -160,7 +177,7 @@ export class CompletionFeature {
|
|
|
160
177
|
this.close();
|
|
161
178
|
if (needsResolve) {
|
|
162
179
|
// Resolving can add the import an item needs; an item that cannot be resolved is still inserted as it is.
|
|
163
|
-
item = await this.resolveNow(entry.item);
|
|
180
|
+
item = await this.resolveNow(entry.item, accepting.signal);
|
|
164
181
|
}
|
|
165
182
|
if (!current()) {
|
|
166
183
|
return;
|
|
@@ -390,10 +407,10 @@ export class CompletionFeature {
|
|
|
390
407
|
* made for. When the person typed on since, the list is asked for again at the new text and the same
|
|
391
408
|
* suggestion is resolved in that one; an item that cannot be resolved is still inserted as it is.
|
|
392
409
|
*/
|
|
393
|
-
async resolveNow(item) {
|
|
410
|
+
async resolveNow(item, signal) {
|
|
394
411
|
const { editor, project, uri } = this.language;
|
|
395
412
|
try {
|
|
396
|
-
return { ...item, ...(await project.service.resolveCompletion(uri, item)) };
|
|
413
|
+
return { ...item, ...(await project.service.resolveCompletion(uri, item, { signal })) };
|
|
397
414
|
}
|
|
398
415
|
catch (error) {
|
|
399
416
|
if (!(error instanceof StaleResultError)) {
|
|
@@ -401,8 +418,8 @@ export class CompletionFeature {
|
|
|
401
418
|
}
|
|
402
419
|
}
|
|
403
420
|
try {
|
|
404
|
-
const fresh = itemsOf(await project.service.completion(uri, editor.getCaret(), { triggerKind: 1 })).items.find((candidate) => candidate.label === item.label && candidate.kind === item.kind);
|
|
405
|
-
return fresh === undefined ? item : { ...fresh, ...(await project.service.resolveCompletion(uri, fresh).catch(() => fresh)) };
|
|
421
|
+
const fresh = itemsOf(await project.service.completion(uri, editor.getCaret(), { triggerKind: 1 }, { signal })).items.find((candidate) => candidate.label === item.label && candidate.kind === item.kind);
|
|
422
|
+
return fresh === undefined ? item : { ...fresh, ...(await project.service.resolveCompletion(uri, fresh, { signal }).catch(() => fresh)) };
|
|
406
423
|
}
|
|
407
424
|
catch {
|
|
408
425
|
return item;
|
|
@@ -411,11 +428,12 @@ export class CompletionFeature {
|
|
|
411
428
|
close() {
|
|
412
429
|
this.request++;
|
|
413
430
|
this.controller?.abort();
|
|
431
|
+
this.resolving?.abort();
|
|
414
432
|
this.timers.clear(this.requestTimer);
|
|
415
|
-
this.timers.clear(this.refreshTimer);
|
|
416
433
|
this.timers.clear(this.resolveTimer);
|
|
417
434
|
this.pool = [];
|
|
418
435
|
this.ranked = [];
|
|
436
|
+
this.rankedFor = null;
|
|
419
437
|
this.touched = false;
|
|
420
438
|
this.explicit = false;
|
|
421
439
|
this.activeKey = null;
|
|
@@ -498,7 +516,6 @@ export class CompletionFeature {
|
|
|
498
516
|
else if (this.isOpen) {
|
|
499
517
|
if (text === '' || typedIdentifier) {
|
|
500
518
|
this.refilter();
|
|
501
|
-
this.scheduleRefresh();
|
|
502
519
|
}
|
|
503
520
|
else {
|
|
504
521
|
this.close();
|
|
@@ -507,6 +524,10 @@ export class CompletionFeature {
|
|
|
507
524
|
else if (text.length === 1 && typedIdentifier && isEmptyRange(range)) {
|
|
508
525
|
this.beginRequest({ triggerKind: 1 }, START_DELAY_MS);
|
|
509
526
|
}
|
|
527
|
+
else if (text !== '') {
|
|
528
|
+
// A request the last letter started, still waiting or out, would open the list after a character that ends the word.
|
|
529
|
+
this.close();
|
|
530
|
+
}
|
|
510
531
|
}
|
|
511
532
|
caretMoved(position) {
|
|
512
533
|
if (this.start === null) {
|
|
@@ -523,19 +544,6 @@ export class CompletionFeature {
|
|
|
523
544
|
const { project, uri } = this.language;
|
|
524
545
|
return project.service.providerOptions(METHOD, uri)?.triggerCharacters ?? [];
|
|
525
546
|
}
|
|
526
|
-
scheduleRefresh() {
|
|
527
|
-
const { project, uri } = this.language;
|
|
528
|
-
this.timers.clear(this.refreshTimer);
|
|
529
|
-
if (this.start === null || !project.service.supports(METHOD, uri)) {
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
const request = this.request;
|
|
533
|
-
this.refreshTimer = this.timers.set(() => {
|
|
534
|
-
if (request === this.request && this.start !== null) {
|
|
535
|
-
void this.ask({ triggerKind: 1 }, request, true);
|
|
536
|
-
}
|
|
537
|
-
}, REFRESH_DELAY_MS);
|
|
538
|
-
}
|
|
539
547
|
beginRequest(context, delay) {
|
|
540
548
|
const { project, uri } = this.language;
|
|
541
549
|
if (!project.service.supports(METHOD, uri)) {
|
|
@@ -600,6 +608,11 @@ export class CompletionFeature {
|
|
|
600
608
|
return;
|
|
601
609
|
}
|
|
602
610
|
const lineBefore = textBeforeOf(editor, caret);
|
|
611
|
+
const typedKey = `${caret.line}:${caret.character}:${lineBefore}`;
|
|
612
|
+
if (this.rankedFor?.pool === this.pool && this.rankedFor.key === typedKey) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
this.rankedFor = { pool: this.pool, key: typedKey };
|
|
603
616
|
// Most items of a list replace the same range, so the text under it is read once and not once per item.
|
|
604
617
|
const typed = new Map();
|
|
605
618
|
const textBetween = (range) => {
|
|
@@ -635,20 +648,29 @@ export class CompletionFeature {
|
|
|
635
648
|
scheduleResolve() {
|
|
636
649
|
const { project, uri } = this.language;
|
|
637
650
|
this.timers.clear(this.resolveTimer);
|
|
651
|
+
this.resolving?.abort();
|
|
638
652
|
const entry = this.ranked[this.active];
|
|
639
653
|
if (entry === undefined || this.resolved.has(entry.item) || project.service.providerOptions('completionItem/resolve', uri) === undefined) {
|
|
640
654
|
return;
|
|
641
655
|
}
|
|
656
|
+
const request = this.request;
|
|
642
657
|
this.resolveTimer = this.timers.set(() => {
|
|
658
|
+
const controller = new AbortController();
|
|
659
|
+
this.resolving = controller;
|
|
643
660
|
void project.service
|
|
644
|
-
.resolveCompletion(uri, entry.item)
|
|
661
|
+
.resolveCompletion(uri, entry.item, { signal: controller.signal })
|
|
645
662
|
.then((item) => {
|
|
646
663
|
this.resolved.set(entry.item, { ...entry.item, ...item });
|
|
647
664
|
if (this.ranked[this.active]?.item === entry.item) {
|
|
648
665
|
this.publish();
|
|
649
666
|
}
|
|
650
667
|
})
|
|
651
|
-
.catch(() =>
|
|
668
|
+
.catch((error) => {
|
|
669
|
+
// A server only resolves an item for the text it was made for, so only an item that is wanted and stale costs a new list.
|
|
670
|
+
if (error instanceof StaleResultError && !controller.signal.aborted && request === this.request && this.start !== null) {
|
|
671
|
+
void this.ask({ triggerKind: 1 }, request, true);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
652
674
|
}, RESOLVE_DELAY_MS);
|
|
653
675
|
}
|
|
654
676
|
publish() {
|
|
@@ -9,6 +9,7 @@ export declare function severityOf(diagnostic: Diagnostic): EditorMarkerSeverity
|
|
|
9
9
|
export declare function markerOf(diagnostic: Diagnostic): EditorMarker;
|
|
10
10
|
export declare function problemCountsOf(diagnostics: readonly Diagnostic[]): ProblemCounts;
|
|
11
11
|
export declare function comparePositions(left: EditorPosition, right: EditorPosition): number;
|
|
12
|
+
export declare function endOfInsertion(start: EditorPosition, text: string): EditorPosition;
|
|
12
13
|
export declare function shiftPosition(position: EditorPosition, change: EditorContentChange): EditorPosition;
|
|
13
14
|
export declare function shiftRange(range: EditorRange, changes: readonly EditorContentChange[]): EditorRange;
|
|
14
15
|
export declare function rangeHolds(range: EditorRange, position: EditorPosition): boolean;
|
|
@@ -26,7 +26,7 @@ export function comparePositions(left, right) {
|
|
|
26
26
|
return left.line === right.line ? left.character - right.character : left.line - right.line;
|
|
27
27
|
}
|
|
28
28
|
/* Where an insertion of `text` at `start` ends, counting a line break of any kind once. */
|
|
29
|
-
function endOfInsertion(start, text) {
|
|
29
|
+
export function endOfInsertion(start, text) {
|
|
30
30
|
const lines = text.split(/\r\n|\r|\n/);
|
|
31
31
|
return lines.length === 1
|
|
32
32
|
? { line: start.line, character: start.character + text.length }
|
package/dist/diagnostics.d.ts
CHANGED
package/dist/diagnostics.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
2
|
+
import { sameRange } from './hover-content.js';
|
|
1
3
|
import { isShortcut } from './shortcut-keys.js';
|
|
2
4
|
import { comparePositions, markerOf, neighborProblem, problemCountsOf, problemsAt, severityOf, shiftRange } from './diagnostics-model.js';
|
|
3
5
|
/*
|
|
@@ -16,19 +18,19 @@ export class DiagnosticsFeature {
|
|
|
16
18
|
const reports = project.service.onDiagnostics((report) => {
|
|
17
19
|
if (report.uri === uri) {
|
|
18
20
|
this.reports.set(report.source, report.diagnostics);
|
|
19
|
-
this.rebuild(
|
|
21
|
+
this.rebuild();
|
|
20
22
|
}
|
|
21
23
|
});
|
|
22
24
|
const edits = editor.onTextChange((change) => this.shift(change.changes));
|
|
23
25
|
// These keys are the editor's while it has the keyboard; elsewhere the menu's commands answer.
|
|
24
|
-
const keys =
|
|
26
|
+
const keys = language.onKey((event) => {
|
|
25
27
|
const next = isShortcut(this.language.shortcuts.nextProblem, event);
|
|
26
28
|
if (!next && !isShortcut(this.language.shortcuts.previousProblem, event)) {
|
|
27
29
|
return false;
|
|
28
30
|
}
|
|
29
31
|
this.step(next ? 1 : -1);
|
|
30
32
|
return true;
|
|
31
|
-
});
|
|
33
|
+
}, KEY_PRIORITY.diagnostics);
|
|
32
34
|
language.onDispose(() => {
|
|
33
35
|
keys();
|
|
34
36
|
reports.dispose();
|
|
@@ -80,19 +82,35 @@ export class DiagnosticsFeature {
|
|
|
80
82
|
const severity = project.i18n.t(`editor:language.announce.severity.${severityOf(problem.diagnostic)}`);
|
|
81
83
|
editor.announce(project.i18n.t('editor:language.announce.problem', { severity, message: problem.diagnostic.message }));
|
|
82
84
|
}
|
|
85
|
+
/* The problems follow the edit as the markers do. Their number does not change, so nobody is told. */
|
|
83
86
|
shift(changes) {
|
|
87
|
+
let moved = false;
|
|
84
88
|
for (const [server, diagnostics] of this.reports) {
|
|
85
|
-
|
|
89
|
+
let shifted = null;
|
|
90
|
+
for (const [index, diagnostic] of diagnostics.entries()) {
|
|
91
|
+
const range = shiftRange(diagnostic.range, changes);
|
|
92
|
+
if (!sameRange(range, diagnostic.range)) {
|
|
93
|
+
shifted ??= [...diagnostics];
|
|
94
|
+
shifted[index] = { ...diagnostic, range };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (shifted !== null) {
|
|
98
|
+
this.reports.set(server, shifted);
|
|
99
|
+
moved = true;
|
|
100
|
+
}
|
|
86
101
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
rebuild(draw) {
|
|
90
|
-
this.list = [...this.reports].flatMap(([server, diagnostics]) => diagnostics.map((diagnostic) => ({ diagnostic, server })));
|
|
91
|
-
if (draw) {
|
|
92
|
-
this.language.editor.setMarkers(this.list.map((problem) => markerOf(problem.diagnostic)));
|
|
102
|
+
if (moved) {
|
|
103
|
+
this.collect();
|
|
93
104
|
}
|
|
105
|
+
}
|
|
106
|
+
rebuild() {
|
|
107
|
+
this.collect();
|
|
108
|
+
this.language.editor.setMarkers(this.list.map((problem) => markerOf(problem.diagnostic)));
|
|
94
109
|
for (const listener of [...this.listeners]) {
|
|
95
110
|
listener();
|
|
96
111
|
}
|
|
97
112
|
}
|
|
113
|
+
collect() {
|
|
114
|
+
this.list = [...this.reports].flatMap(([server, diagnostics]) => diagnostics.map((diagnostic) => ({ diagnostic, server })));
|
|
115
|
+
}
|
|
98
116
|
}
|
package/dist/diff-overlays.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useSyncExternalStore } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { Button } from '@adecore/ui';
|
|
5
|
+
import { Button, Tooltip } from '@adecore/ui';
|
|
6
6
|
import { formatNumber } from '@adecore/ui/format';
|
|
7
7
|
import { RowHost, LineActionHost } from './row-host.js';
|
|
8
8
|
export function DiffOverlays({ mounted, display, unfold, blocked }) {
|
|
@@ -15,7 +15,7 @@ export function DiffOverlays({ mounted, display, unfold, blocked }) {
|
|
|
15
15
|
return null;
|
|
16
16
|
}
|
|
17
17
|
const text = t('diff.hidden', { count: formatNumber(Math.max(row.oldCount, row.newCount)) });
|
|
18
|
-
return createPortal(_jsx("div", { className: "adecore-editor-diff-gap", children: row.unfoldable ? (_jsx(Button, { size: "xs", disabled:
|
|
18
|
+
return createPortal(_jsx("div", { className: "adecore-editor-diff-gap", children: row.unfoldable && blocked?.has(row.id) === true ? (_jsx(Tooltip, { label: t('diff.contextLimit'), children: _jsx("span", { className: "inline-flex", children: _jsx(Button, { size: "xs", disabled: true, children: text }) }) })) : row.unfoldable ? (_jsx(Button, { size: "xs", onClick: () => unfold(row.id), children: text })) : (_jsx("span", { children: text })) }), container, `gap-${row.id}`);
|
|
19
19
|
}), display.rows.map((row, index) => {
|
|
20
20
|
if (row.noNewline === undefined) {
|
|
21
21
|
return null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Keymap } from '@adecore/editor/keymap';
|
|
2
|
-
import type { Editor, EditorPosition } from '@adecore/editor';
|
|
2
|
+
import type { Editor, EditorKeyHandler, EditorPosition } from '@adecore/editor';
|
|
3
3
|
import type { Location } from '@adecore/lsp';
|
|
4
4
|
import { CallHierarchyFeature } from './call-hierarchy.ts';
|
|
5
5
|
import { CodeActionsFeature } from './code-actions.ts';
|
|
@@ -61,6 +61,7 @@ export declare class EditorLanguage {
|
|
|
61
61
|
readonly formatOnType: FormatOnTypeFeature;
|
|
62
62
|
readonly renderedDocs: RenderedDocsFeature;
|
|
63
63
|
private readonly disposers;
|
|
64
|
+
private readonly keys;
|
|
64
65
|
private disposed;
|
|
65
66
|
constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers?: Timers, options?: EditorLanguageOptions);
|
|
66
67
|
get uri(): string;
|
|
@@ -71,6 +72,7 @@ export declare class EditorLanguage {
|
|
|
71
72
|
visit(place: Place): void;
|
|
72
73
|
locations(places: readonly Location[], anchor: EditorPosition, title: string): void;
|
|
73
74
|
onProvidersChanged(listener: () => void): () => void;
|
|
75
|
+
onKey(handler: EditorKeyHandler, priority?: number): () => void;
|
|
74
76
|
onDispose(dispose: () => void): void;
|
|
75
77
|
dispose(): void;
|
|
76
78
|
private placeOpenedCaret;
|
package/dist/editor-language.js
CHANGED
|
@@ -11,6 +11,7 @@ import { FormatOnTypeFeature } from './format-on-type.js';
|
|
|
11
11
|
import { HighlightsFeature } from './highlights.js';
|
|
12
12
|
import { HistoryFeature } from './history.js';
|
|
13
13
|
import { HoverFeature } from './hover.js';
|
|
14
|
+
import { KeyDispatcher } from './key-priority.js';
|
|
14
15
|
import { NavigationFeature, locationRow } from './navigation.js';
|
|
15
16
|
import { PeekFeature } from './peek.js';
|
|
16
17
|
import { PickFeature } from './pick.js';
|
|
@@ -62,6 +63,7 @@ export class EditorLanguage {
|
|
|
62
63
|
formatOnType;
|
|
63
64
|
renderedDocs;
|
|
64
65
|
disposers = [];
|
|
66
|
+
keys = new KeyDispatcher();
|
|
65
67
|
disposed = false;
|
|
66
68
|
constructor(project, editor, uri, languageId, timers = realTimers, options = {}) {
|
|
67
69
|
this.editor = editor;
|
|
@@ -71,6 +73,7 @@ export class EditorLanguage {
|
|
|
71
73
|
this.project = project;
|
|
72
74
|
this.languageId = languageId;
|
|
73
75
|
this.document = project.acquire(uri, languageId, editor);
|
|
76
|
+
this.onDispose(editor.onKeyDown((event) => this.keys.dispatch(event)));
|
|
74
77
|
this.pick = new PickFeature(this, timers);
|
|
75
78
|
this.diagnostics = new DiagnosticsFeature(this);
|
|
76
79
|
this.hover = new HoverFeature(this, timers);
|
|
@@ -154,6 +157,13 @@ export class EditorLanguage {
|
|
|
154
157
|
providers.dispose();
|
|
155
158
|
};
|
|
156
159
|
}
|
|
160
|
+
/*
|
|
161
|
+
* Lets `handler` see the editor's keys before every handler of a lower `priority`, the first to take a key
|
|
162
|
+
* winning. `KEY_PRIORITY` holds the features'; the default, 0, comes after all of them.
|
|
163
|
+
*/
|
|
164
|
+
onKey(handler, priority = 0) {
|
|
165
|
+
return this.keys.add(handler, priority);
|
|
166
|
+
}
|
|
157
167
|
/* Runs `dispose` when the editor lets go of its document. */
|
|
158
168
|
onDispose(dispose) {
|
|
159
169
|
this.disposers.push(dispose);
|
package/dist/format-on-type.js
CHANGED
|
@@ -20,12 +20,11 @@ function overlaps(edits) {
|
|
|
20
20
|
* second caret, a read-only editor or a snippet session, whose tab stops an edit could move. An answer is applied
|
|
21
21
|
* only while the text is exactly what it was asked about, and the caret where it was.
|
|
22
22
|
*
|
|
23
|
-
* The edits join the undo step of the typed character, so one undo takes both back.
|
|
24
|
-
* turns it off for a host that has a setting for it.
|
|
23
|
+
* The edits join the undo step of the typed character, so one undo takes both back. Off by default; a host enables it explicitly with `configure(true)`.
|
|
25
24
|
*/
|
|
26
25
|
export class FormatOnTypeFeature {
|
|
27
26
|
language;
|
|
28
|
-
enabled =
|
|
27
|
+
enabled = false;
|
|
29
28
|
/* Raised by every change of the text, so an answer for an older text is told from the one for the text that is. */
|
|
30
29
|
revision = 0;
|
|
31
30
|
controller = new AbortController();
|
package/dist/highlights.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
1
2
|
import { comparePositions, rangeHolds } from './diagnostics-model.js';
|
|
2
3
|
import { isIdentifierCharacter } from './completion-model.js';
|
|
3
4
|
import { Refresher } from './refresher.js';
|
|
@@ -57,13 +58,13 @@ export class HighlightsFeature {
|
|
|
57
58
|
this.current = [];
|
|
58
59
|
this.refresher.later();
|
|
59
60
|
}),
|
|
60
|
-
|
|
61
|
+
language.onKey((event) => {
|
|
61
62
|
const next = isShortcut(this.language.shortcuts.nextHighlight, event);
|
|
62
63
|
if (!next && !isShortcut(this.language.shortcuts.previousHighlight, event)) {
|
|
63
64
|
return false;
|
|
64
65
|
}
|
|
65
66
|
return this.step(next ? 1 : -1);
|
|
66
|
-
})
|
|
67
|
+
}, KEY_PRIORITY.highlights)
|
|
67
68
|
];
|
|
68
69
|
language.onDispose(() => {
|
|
69
70
|
for (const off of offs) {
|
package/dist/history.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
1
2
|
import { isShortcut } from './shortcut-keys.js';
|
|
2
3
|
/* Back, Forward and the list of recent locations over the history the whole project shares. */
|
|
3
4
|
export class HistoryFeature {
|
|
4
5
|
language;
|
|
5
6
|
constructor(language) {
|
|
6
7
|
this.language = language;
|
|
7
|
-
const off = language.
|
|
8
|
+
const off = language.onKey((event) => {
|
|
8
9
|
if (isShortcut(this.language.shortcuts.historyBack, event)) {
|
|
9
10
|
this.back();
|
|
10
11
|
return true;
|
|
@@ -18,7 +19,7 @@ export class HistoryFeature {
|
|
|
18
19
|
return true;
|
|
19
20
|
}
|
|
20
21
|
return false;
|
|
21
|
-
});
|
|
22
|
+
}, KEY_PRIORITY.history);
|
|
22
23
|
language.onDispose(off);
|
|
23
24
|
}
|
|
24
25
|
/* Returns to where the caret was before the last jump; false when there is no such place. */
|
package/dist/holder.d.ts
CHANGED