@adecore/editor-react 0.23.0 → 0.24.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/AnchoredPopup.d.ts +2 -1
- package/dist/AnchoredPopup.js +2 -2
- package/dist/CallHierarchyPanel.js +6 -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/PanelResizeHandle.d.ts +8 -0
- package/dist/PanelResizeHandle.js +71 -0
- package/dist/PeekPanel.js +6 -2
- 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-model.js +3 -9
- package/dist/call-hierarchy.js +17 -17
- 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 +11 -1
- 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 +5 -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 +5 -1
- package/dist/locales/nl.json +5 -1
- package/dist/models.d.ts +2 -2
- package/dist/models.js +2 -2
- package/dist/navigation.js +3 -2
- package/dist/panel-size.d.ts +6 -0
- package/dist/panel-size.js +25 -0
- package/dist/peek.d.ts +3 -1
- package/dist/peek.js +8 -3
- 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.d.ts +4 -0
- package/dist/project-language.js +23 -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.d.ts +2 -0
- package/dist/rendered-docs.js +15 -8
- 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/dist/timers.d.ts +1 -0
- package/dist/timers.js +4 -0
- package/dist/use-panel-body.d.ts +6 -0
- package/dist/use-panel-body.js +13 -0
- package/package.json +6 -6
- package/src/AnchoredPopup.tsx +4 -2
- package/src/CallHierarchyPanel.tsx +9 -3
- 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/PanelResizeHandle.tsx +104 -0
- package/src/PeekPanel.tsx +9 -3
- package/src/PickPopup.tsx +9 -1
- package/src/RenameCard.tsx +15 -9
- package/src/RenderedDocs.tsx +30 -8
- package/src/call-hierarchy-model.ts +15 -22
- package/src/call-hierarchy.ts +17 -17
- 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 +13 -2
- 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 +9 -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 +5 -1
- package/src/locales/nl.json +5 -1
- package/src/models.ts +1 -1
- package/src/navigation.ts +3 -2
- package/src/panel-size.ts +28 -0
- package/src/peek.ts +8 -3
- package/src/pick.ts +2 -1
- package/src/popup-focus.ts +17 -0
- package/src/popups.ts +1 -2
- package/src/project-language.ts +25 -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 +17 -8
- package/src/signature.ts +4 -3
- package/src/snippet-session.ts +9 -3
- package/src/symbol-picker.ts +3 -2
- package/src/timers.ts +5 -0
- package/src/use-panel-body.ts +17 -0
- package/dist/relative-time.d.ts +0 -1
- package/dist/relative-time.js +0 -9
- package/src/relative-time.ts +0 -10
package/dist/RenderedDocs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
|
-
import { useSyncExternalStore } from 'react';
|
|
3
|
+
import { memo, useSyncExternalStore } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { DOC_LINK_PREFIX, sectionsOf } from './doc-comment-model.js';
|
|
6
6
|
import { markdownParts } from './hover-content.js';
|
|
@@ -39,12 +39,11 @@ function TagRow({ tag, language, linkable, spaced }) {
|
|
|
39
39
|
const link = target === null ? null : /^https?:\/\//i.test(target) ? `[${target}](${target})` : `[\`${target}\`](${DOC_LINK_PREFIX}${encodeURIComponent(target)})`;
|
|
40
40
|
return (_jsx("dd", { className: `col-start-2 m-0 flex min-w-0 flex-wrap items-baseline gap-x-2 ${spaced ? 'mt-1' : ''}`, children: tag.name === 'example' ? (_jsx("div", { className: "w-full rounded-md bg-surface-sunken px-2 py-1.5", children: _jsx(Signature, { code: tag.text, language: language }) })) : (_jsxs(_Fragment, { children: [tag.subject !== null && target === null && _jsx(Code, { children: tag.subject }), tag.type !== null && _jsx(Code, { faint: true, children: tag.type }), (link !== null || tag.text !== '') && (_jsx("div", { className: "min-w-0 grow basis-48 text-text", children: _jsx(Prose, { text: [link, tag.text].filter((part) => part !== null && part !== '').join(' '), language: language, linkable: linkable, compact: true }) }))] })) }));
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
function DocBlock({ language, comment, parsed, linkable }) {
|
|
43
43
|
const { t } = useTranslation('editor');
|
|
44
44
|
const feature = language.renderedDocs;
|
|
45
|
-
const { description, tags } =
|
|
45
|
+
const { description, tags } = parsed;
|
|
46
46
|
const highlighter = shikiLanguageOf(language.languageId);
|
|
47
|
-
const linkable = feature.canFollow;
|
|
48
47
|
const sections = sectionsOf(tags);
|
|
49
48
|
const deprecated = sections.find((section) => section.name === 'deprecated');
|
|
50
49
|
const shown = sections.filter((section) => section.name !== 'deprecated');
|
|
@@ -61,7 +60,7 @@ export function RenderedDocBlock({ language, entry }) {
|
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
62
|
if (href.startsWith(DOC_LINK_PREFIX)) {
|
|
64
|
-
feature.follow(decodeURIComponent(href.slice(DOC_LINK_PREFIX.length)),
|
|
63
|
+
feature.follow(decodeURIComponent(href.slice(DOC_LINK_PREFIX.length)), comment);
|
|
65
64
|
}
|
|
66
65
|
else if (/^(https?:|mailto:)/i.test(href)) {
|
|
67
66
|
window.open(href, '_blank', 'noopener,noreferrer');
|
|
@@ -72,6 +71,8 @@ export function RenderedDocBlock({ language, entry }) {
|
|
|
72
71
|
...section.tags.map((tag, index) => (_jsx(TagRow, { tag: tag, language: highlighter, linkable: linkable, spaced: row > 0 && index === 0 }, `${section.name}:${index}`)))
|
|
73
72
|
]) }))] }));
|
|
74
73
|
}
|
|
74
|
+
/* Drawn again only when its comment or its links change, since every scroll of the editor draws the popups again. */
|
|
75
|
+
export const RenderedDocBlock = memo(DocBlock);
|
|
75
76
|
/*
|
|
76
77
|
* The documentation comments the editor drew as rows, filled with their formatted text. It portals into
|
|
77
78
|
* the element the editor made for each row, so the editor decides what is in view; a comment is parsed
|
|
@@ -80,5 +81,8 @@ export function RenderedDocBlock({ language, entry }) {
|
|
|
80
81
|
export function RenderedDocs({ language }) {
|
|
81
82
|
const feature = language.renderedDocs;
|
|
82
83
|
useSyncExternalStore(feature.subscribe, feature.getVersion, feature.getVersion);
|
|
83
|
-
|
|
84
|
+
const linkable = feature.canFollow;
|
|
85
|
+
return (_jsx(_Fragment, { children: feature
|
|
86
|
+
.entries()
|
|
87
|
+
.map((entry) => createPortal(_jsx(RenderedDocBlock, { language: language, comment: entry.comment, parsed: entry.parsed, linkable: linkable }), entry.container, entry.id)) }));
|
|
84
88
|
}
|
|
@@ -24,19 +24,13 @@ export function rootsOf(items) {
|
|
|
24
24
|
shown: 0
|
|
25
25
|
}));
|
|
26
26
|
}
|
|
27
|
-
function compareItems(left, right) {
|
|
28
|
-
return left.name.localeCompare(right.name) || left.uri.localeCompare(right.uri) || left.range.start.line - right.range.start.line;
|
|
29
|
-
}
|
|
30
27
|
/*
|
|
31
|
-
* The children of a node made from the calls a server returned,
|
|
32
|
-
*
|
|
33
|
-
* that was asked about.
|
|
28
|
+
* The children of a node made from the calls a server returned, in the order the server gave them.
|
|
29
|
+
* The call sites of a caller are in its own file; those of a callee are in the file of the node that was asked about.
|
|
34
30
|
*/
|
|
35
31
|
export function childrenOf(direction, parent, calls) {
|
|
36
32
|
const entries = calls.map((call) => ({ item: 'from' in call ? call.from : call.to, sites: call.fromRanges }));
|
|
37
|
-
return entries
|
|
38
|
-
.sort((left, right) => compareItems(left.item, right.item))
|
|
39
|
-
.map((entry, index) => ({
|
|
33
|
+
return entries.map((entry, index) => ({
|
|
40
34
|
id: `${parent.id}.${index}`,
|
|
41
35
|
parent: parent.id,
|
|
42
36
|
level: parent.level + 1,
|
package/dist/call-hierarchy.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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';
|
|
5
6
|
import { isShortcut } from './shortcut-keys.js';
|
|
6
7
|
import { messageOf } from './error-message.js';
|
|
7
|
-
import { realTimers } from './timers.js';
|
|
8
|
+
import { realTimers, revealRow } from './timers.js';
|
|
8
9
|
const TOAST_ID = 'language-call-hierarchy';
|
|
9
10
|
const PREPARE = 'textDocument/prepareCallHierarchy';
|
|
10
11
|
/* The row the panel stands in is as tall as a peek's, plus the segmented control that is a pixel taller than the peek's title bar. */
|
|
@@ -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,16 +224,15 @@ 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
|
-
this.timers.set(() => container.scrollIntoView?.({ block: 'nearest' }), 0);
|
|
230
|
+
revealRow(this.timers, container);
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
patch(id, change) {
|
|
234
234
|
const node = this.nodes.get(id);
|
|
235
|
-
const view = this.language.popups.getState().callHierarchy
|
|
235
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
236
236
|
if (node === undefined || view === null) {
|
|
237
237
|
return;
|
|
238
238
|
}
|
|
@@ -292,7 +292,7 @@ export class CallHierarchyFeature {
|
|
|
292
292
|
this.texts.set(uri, text);
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
|
-
const view = this.language.popups.getState().callHierarchy
|
|
295
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
296
296
|
if (view === null || view.active !== node.id) {
|
|
297
297
|
return;
|
|
298
298
|
}
|
|
@@ -315,7 +315,7 @@ export class CallHierarchyFeature {
|
|
|
315
315
|
return false;
|
|
316
316
|
}
|
|
317
317
|
key(event) {
|
|
318
|
-
const view = this.language.popups.getState().callHierarchy
|
|
318
|
+
const view = this.language.popups.getState().callHierarchy;
|
|
319
319
|
if (view === null || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || event.isComposing) {
|
|
320
320
|
return false;
|
|
321
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;
|