@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/src/diagnostics.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Diagnostic } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { EditorContentChange, EditorPosition, EditorReveal } from '@adecore/editor';
|
|
3
4
|
import type { EditorLanguage } from './editor-language.ts';
|
|
5
|
+
import { sameRange } from './hover-content.ts';
|
|
4
6
|
import { isShortcut } from './shortcut-keys.ts';
|
|
5
7
|
import { comparePositions, markerOf, neighborProblem, problemCountsOf, problemsAt, severityOf, shiftRange, type Problem } from './diagnostics-model.ts';
|
|
6
8
|
|
|
@@ -27,19 +29,19 @@ export class DiagnosticsFeature {
|
|
|
27
29
|
const reports = project.service.onDiagnostics((report) => {
|
|
28
30
|
if (report.uri === uri) {
|
|
29
31
|
this.reports.set(report.source, report.diagnostics);
|
|
30
|
-
this.rebuild(
|
|
32
|
+
this.rebuild();
|
|
31
33
|
}
|
|
32
34
|
});
|
|
33
35
|
const edits = editor.onTextChange((change) => this.shift(change.changes));
|
|
34
36
|
// These keys are the editor's while it has the keyboard; elsewhere the menu's commands answer.
|
|
35
|
-
const keys =
|
|
37
|
+
const keys = language.onKey((event) => {
|
|
36
38
|
const next = isShortcut(this.language.shortcuts.nextProblem, event);
|
|
37
39
|
if (!next && !isShortcut(this.language.shortcuts.previousProblem, event)) {
|
|
38
40
|
return false;
|
|
39
41
|
}
|
|
40
42
|
this.step(next ? 1 : -1);
|
|
41
43
|
return true;
|
|
42
|
-
});
|
|
44
|
+
}, KEY_PRIORITY.diagnostics);
|
|
43
45
|
language.onDispose(() => {
|
|
44
46
|
keys();
|
|
45
47
|
reports.dispose();
|
|
@@ -99,23 +101,37 @@ export class DiagnosticsFeature {
|
|
|
99
101
|
editor.announce(project.i18n.t('editor:language.announce.problem', { severity, message: problem.diagnostic.message }));
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
/* The problems follow the edit as the markers do. Their number does not change, so nobody is told. */
|
|
102
105
|
private shift(changes: readonly EditorContentChange[]): void {
|
|
106
|
+
let moved = false;
|
|
103
107
|
for (const [server, diagnostics] of this.reports) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
let shifted: Diagnostic[] | null = null;
|
|
109
|
+
for (const [index, diagnostic] of diagnostics.entries()) {
|
|
110
|
+
const range = shiftRange(diagnostic.range, changes);
|
|
111
|
+
if (!sameRange(range, diagnostic.range)) {
|
|
112
|
+
shifted ??= [...diagnostics];
|
|
113
|
+
shifted[index] = { ...diagnostic, range };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (shifted !== null) {
|
|
117
|
+
this.reports.set(server, shifted);
|
|
118
|
+
moved = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (moved) {
|
|
122
|
+
this.collect();
|
|
108
123
|
}
|
|
109
|
-
this.rebuild(false);
|
|
110
124
|
}
|
|
111
125
|
|
|
112
|
-
private rebuild(
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
this.language.editor.setMarkers(this.list.map((problem) => markerOf(problem.diagnostic)));
|
|
116
|
-
}
|
|
126
|
+
private rebuild(): void {
|
|
127
|
+
this.collect();
|
|
128
|
+
this.language.editor.setMarkers(this.list.map((problem) => markerOf(problem.diagnostic)));
|
|
117
129
|
for (const listener of [...this.listeners]) {
|
|
118
130
|
listener();
|
|
119
131
|
}
|
|
120
132
|
}
|
|
133
|
+
|
|
134
|
+
private collect(): void {
|
|
135
|
+
this.list = [...this.reports].flatMap(([server, diagnostics]) => diagnostics.map((diagnostic): Problem => ({ diagnostic, server })));
|
|
136
|
+
}
|
|
121
137
|
}
|
package/src/diff-overlays.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useSyncExternalStore } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { Button } from '@adecore/ui';
|
|
4
|
+
import { Button, Tooltip } from '@adecore/ui';
|
|
5
5
|
import { formatNumber } from '@adecore/ui/format';
|
|
6
6
|
import type { Editor, EditorEngine } from '@adecore/editor';
|
|
7
7
|
import type { DiffDisplay } from '@adecore/merge';
|
|
@@ -38,13 +38,17 @@ export function DiffOverlays({
|
|
|
38
38
|
const text = t('diff.hidden', { count: formatNumber(Math.max(row.oldCount, row.newCount)) });
|
|
39
39
|
return createPortal(
|
|
40
40
|
<div className="adecore-editor-diff-gap">
|
|
41
|
-
{row.unfoldable ? (
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
{row.unfoldable && blocked?.has(row.id) === true ? (
|
|
42
|
+
<Tooltip label={t('diff.contextLimit')}>
|
|
43
|
+
{/* A disabled button gets no pointer events, so the hint hangs on a wrapper. */}
|
|
44
|
+
<span className="inline-flex">
|
|
45
|
+
<Button size="xs" disabled>
|
|
46
|
+
{text}
|
|
47
|
+
</Button>
|
|
48
|
+
</span>
|
|
49
|
+
</Tooltip>
|
|
50
|
+
) : row.unfoldable ? (
|
|
51
|
+
<Button size="xs" onClick={() => unfold(row.id)}>
|
|
48
52
|
{text}
|
|
49
53
|
</Button>
|
|
50
54
|
) : (
|
package/src/editor-language.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { KEYMAP, 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';
|
|
@@ -13,6 +13,7 @@ import { FormatOnTypeFeature } from './format-on-type.ts';
|
|
|
13
13
|
import { HighlightsFeature } from './highlights.ts';
|
|
14
14
|
import { HistoryFeature } from './history.ts';
|
|
15
15
|
import { HoverFeature } from './hover.ts';
|
|
16
|
+
import { KeyDispatcher } from './key-priority.ts';
|
|
16
17
|
import { NavigationFeature, locationRow } from './navigation.ts';
|
|
17
18
|
import { PeekFeature } from './peek.ts';
|
|
18
19
|
import { PickFeature } from './pick.ts';
|
|
@@ -73,6 +74,7 @@ export class EditorLanguage {
|
|
|
73
74
|
readonly formatOnType: FormatOnTypeFeature;
|
|
74
75
|
readonly renderedDocs: RenderedDocsFeature;
|
|
75
76
|
private readonly disposers: Array<() => void> = [];
|
|
77
|
+
private readonly keys = new KeyDispatcher();
|
|
76
78
|
private disposed = false;
|
|
77
79
|
|
|
78
80
|
constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers: Timers = realTimers, options: EditorLanguageOptions = {}) {
|
|
@@ -83,6 +85,7 @@ export class EditorLanguage {
|
|
|
83
85
|
this.project = project;
|
|
84
86
|
this.languageId = languageId;
|
|
85
87
|
this.document = project.acquire(uri, languageId, editor);
|
|
88
|
+
this.onDispose(editor.onKeyDown((event) => this.keys.dispatch(event)));
|
|
86
89
|
this.pick = new PickFeature(this, timers);
|
|
87
90
|
this.diagnostics = new DiagnosticsFeature(this);
|
|
88
91
|
this.hover = new HoverFeature(this, timers);
|
|
@@ -97,7 +100,7 @@ export class EditorLanguage {
|
|
|
97
100
|
this.codeActions = new CodeActionsFeature(this, timers);
|
|
98
101
|
this.rename = new RenameFeature(this);
|
|
99
102
|
this.navigation = new NavigationFeature(this);
|
|
100
|
-
this.peek = new PeekFeature(this);
|
|
103
|
+
this.peek = new PeekFeature(this, timers);
|
|
101
104
|
this.callHierarchy = new CallHierarchyFeature(this, timers);
|
|
102
105
|
this.symbolPicker = new SymbolPickerFeature(this);
|
|
103
106
|
this.contextMenu = new ContextMenuFeature(this);
|
|
@@ -177,6 +180,14 @@ export class EditorLanguage {
|
|
|
177
180
|
};
|
|
178
181
|
}
|
|
179
182
|
|
|
183
|
+
/*
|
|
184
|
+
* Lets `handler` see the editor's keys before every handler of a lower `priority`, the first to take a key
|
|
185
|
+
* winning. `KEY_PRIORITY` holds the features'; the default, 0, comes after all of them.
|
|
186
|
+
*/
|
|
187
|
+
onKey(handler: EditorKeyHandler, priority = 0): () => void {
|
|
188
|
+
return this.keys.add(handler, priority);
|
|
189
|
+
}
|
|
190
|
+
|
|
180
191
|
/* Runs `dispose` when the editor lets go of its document. */
|
|
181
192
|
onDispose(dispose: () => void): void {
|
|
182
193
|
this.disposers.push(dispose);
|
package/src/format-on-type.ts
CHANGED
|
@@ -26,12 +26,11 @@ function overlaps(edits: readonly TextEdit[]): boolean {
|
|
|
26
26
|
* second caret, a read-only editor or a snippet session, whose tab stops an edit could move. An answer is applied
|
|
27
27
|
* only while the text is exactly what it was asked about, and the caret where it was.
|
|
28
28
|
*
|
|
29
|
-
* The edits join the undo step of the typed character, so one undo takes both back.
|
|
30
|
-
* turns it off for a host that has a setting for it.
|
|
29
|
+
* 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)`.
|
|
31
30
|
*/
|
|
32
31
|
export class FormatOnTypeFeature {
|
|
33
32
|
private readonly language: EditorLanguage;
|
|
34
|
-
private enabled =
|
|
33
|
+
private enabled = false;
|
|
35
34
|
/* Raised by every change of the text, so an answer for an older text is told from the one for the text that is. */
|
|
36
35
|
private revision = 0;
|
|
37
36
|
private controller = new AbortController();
|
package/src/highlights.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DocumentHighlight } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { EditorHighlight, EditorHighlightKind, EditorPosition, EditorRange } from '@adecore/editor';
|
|
3
4
|
import { comparePositions, rangeHolds } from './diagnostics-model.ts';
|
|
4
5
|
import { isIdentifierCharacter } from './completion-model.ts';
|
|
@@ -70,13 +71,13 @@ export class HighlightsFeature {
|
|
|
70
71
|
this.current = [];
|
|
71
72
|
this.refresher.later();
|
|
72
73
|
}),
|
|
73
|
-
|
|
74
|
+
language.onKey((event) => {
|
|
74
75
|
const next = isShortcut(this.language.shortcuts.nextHighlight, event);
|
|
75
76
|
if (!next && !isShortcut(this.language.shortcuts.previousHighlight, event)) {
|
|
76
77
|
return false;
|
|
77
78
|
}
|
|
78
79
|
return this.step(next ? 1 : -1);
|
|
79
|
-
})
|
|
80
|
+
}, KEY_PRIORITY.highlights)
|
|
80
81
|
];
|
|
81
82
|
language.onDispose(() => {
|
|
82
83
|
for (const off of offs) {
|
package/src/history.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EditorLanguage } from './editor-language.ts';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { Place } from './navigation-history.ts';
|
|
3
4
|
import { isShortcut } from './shortcut-keys.ts';
|
|
4
5
|
|
|
@@ -8,7 +9,7 @@ export class HistoryFeature {
|
|
|
8
9
|
|
|
9
10
|
constructor(language: EditorLanguage) {
|
|
10
11
|
this.language = language;
|
|
11
|
-
const off = language.
|
|
12
|
+
const off = language.onKey((event) => {
|
|
12
13
|
if (isShortcut(this.language.shortcuts.historyBack, event)) {
|
|
13
14
|
this.back();
|
|
14
15
|
return true;
|
|
@@ -22,7 +23,7 @@ export class HistoryFeature {
|
|
|
22
23
|
return true;
|
|
23
24
|
}
|
|
24
25
|
return false;
|
|
25
|
-
});
|
|
26
|
+
}, KEY_PRIORITY.history);
|
|
26
27
|
language.onDispose(off);
|
|
27
28
|
}
|
|
28
29
|
|
package/src/holder.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/* One value that an effect sets and a render reads through `useSyncExternalStore`, without a state update inside the effect. */
|
|
2
|
+
export function createHolder<T>(): { get(): T | null; set(value: T | null): void; subscribe(listener: () => void): () => void } {
|
|
2
3
|
let value: T | null = null;
|
|
3
4
|
const listeners = new Set<() => void>();
|
|
4
5
|
return {
|
|
5
6
|
get: () => value,
|
|
6
|
-
set: (next
|
|
7
|
+
set: (next) => {
|
|
7
8
|
value = next;
|
|
8
|
-
for (const listener of listeners) {
|
|
9
|
+
for (const listener of [...listeners]) {
|
|
9
10
|
listener();
|
|
10
11
|
}
|
|
11
12
|
},
|
|
12
|
-
subscribe: (listener
|
|
13
|
+
subscribe: (listener) => {
|
|
13
14
|
listeners.add(listener);
|
|
14
15
|
return () => {
|
|
15
16
|
listeners.delete(listener);
|
package/src/host-types.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { i18n } from 'i18next';
|
|
|
4
4
|
import type { Keymap } from '@adecore/editor/keymap';
|
|
5
5
|
import type { ProjectFiles } from './workspace-edit.ts';
|
|
6
6
|
import type { Place } from './navigation-history.ts';
|
|
7
|
+
import type { ProblemSource } from './project-problems.ts';
|
|
7
8
|
|
|
8
9
|
export interface DiskText {
|
|
9
10
|
text: string;
|
|
@@ -35,6 +36,8 @@ export interface RenameSuggestionsRequest {
|
|
|
35
36
|
uri: string;
|
|
36
37
|
languageId: string;
|
|
37
38
|
name: string;
|
|
39
|
+
/* What the name belongs to, in words (`function`, `class`, `type parameter`), when the outline of the file lists it. */
|
|
40
|
+
kind?: string;
|
|
38
41
|
context: string;
|
|
39
42
|
uses: readonly string[];
|
|
40
43
|
}
|
|
@@ -61,6 +64,12 @@ export interface LanguageHost {
|
|
|
61
64
|
readonly files?: ProjectFiles;
|
|
62
65
|
readonly apple?: boolean;
|
|
63
66
|
readonly keymap?: Keymap;
|
|
67
|
+
/* The height in pixels of the list-and-preview area of the peek and the call hierarchy, so a person's drag outlives the panel; 180 without one. */
|
|
68
|
+
readonly panelBodyHeight?: number;
|
|
69
|
+
/* A person finished resizing a peek or call hierarchy panel; the height is already within what the editor allows. */
|
|
70
|
+
onPanelResize?(height: number): void;
|
|
71
|
+
/* Where `project.problems` reads its reports, such as a machine that also reports files no editor here holds; the service's diagnostics without one. */
|
|
72
|
+
readonly problemSource?: ProblemSource;
|
|
64
73
|
openPlace?(place: Place): void;
|
|
65
74
|
pathOfUri?(uri: string): string | null;
|
|
66
75
|
serverNames?(uri: string): readonly string[];
|
package/src/hover.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EditorHover, EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
3
4
|
import { rangeHolds, type Problem } from './diagnostics-model.ts';
|
|
4
5
|
import { hoverTextOf, isEmptyHover, locationsOf, type HoverText } from './hover-content.ts';
|
|
@@ -14,6 +15,11 @@ const HIDE_DELAY_MS = 250;
|
|
|
14
15
|
const COUNT_DELAY_MS = 500;
|
|
15
16
|
const TOAST_ID = 'language-hover';
|
|
16
17
|
|
|
18
|
+
export interface HoverSettings {
|
|
19
|
+
/* Counts the references of the symbol on a card that stays up, which can be a search over the whole project; on unless false. */
|
|
20
|
+
readonly referenceCount?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
/*
|
|
18
24
|
* The card under a character the pointer rests on: the problems there, and what the language servers
|
|
19
25
|
* say about the symbol. It opens after the pointer has stayed a moment, stays while the pointer is on
|
|
@@ -28,6 +34,7 @@ export class HoverFeature {
|
|
|
28
34
|
private hideTimer: unknown;
|
|
29
35
|
private countTimer: unknown;
|
|
30
36
|
private inCard = false;
|
|
37
|
+
private referenceCount = true;
|
|
31
38
|
/* Counts every request, so an answer that arrives after the pointer moved on is dropped. */
|
|
32
39
|
private request = 0;
|
|
33
40
|
private lookup: AbortController | null = null;
|
|
@@ -62,17 +69,17 @@ export class HoverFeature {
|
|
|
62
69
|
this.hide();
|
|
63
70
|
}
|
|
64
71
|
}),
|
|
65
|
-
|
|
72
|
+
language.onKey((event) => {
|
|
66
73
|
if (isShortcut(this.language.shortcuts.quickInfo, event)) {
|
|
67
74
|
this.quickInfo();
|
|
68
75
|
return true;
|
|
69
76
|
}
|
|
70
|
-
if (event.key === 'Escape' && this.language.popups.getState().hover !== null && this.language.popups.getState().callHierarchy
|
|
77
|
+
if (event.key === 'Escape' && this.language.popups.getState().hover !== null && this.language.popups.getState().callHierarchy === null) {
|
|
71
78
|
this.hide();
|
|
72
79
|
return true;
|
|
73
80
|
}
|
|
74
81
|
return false;
|
|
75
|
-
})
|
|
82
|
+
}, KEY_PRIORITY.hover)
|
|
76
83
|
];
|
|
77
84
|
language.onDispose(() => {
|
|
78
85
|
for (const off of offs) {
|
|
@@ -82,6 +89,13 @@ export class HoverFeature {
|
|
|
82
89
|
});
|
|
83
90
|
}
|
|
84
91
|
|
|
92
|
+
configure(settings: HoverSettings): void {
|
|
93
|
+
this.referenceCount = settings.referenceCount !== false;
|
|
94
|
+
if (!this.referenceCount) {
|
|
95
|
+
this.timers.clear(this.countTimer);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
85
99
|
/* The pointer entered or left the card, which is where a link in it is clicked. */
|
|
86
100
|
holdCard(inside: boolean): void {
|
|
87
101
|
this.inCard = inside;
|
|
@@ -236,7 +250,7 @@ export class HoverFeature {
|
|
|
236
250
|
}
|
|
237
251
|
this.pinned = pinned;
|
|
238
252
|
this.present(position, problems, info, problems[0]?.diagnostic.range ?? range);
|
|
239
|
-
if (info !== null) {
|
|
253
|
+
if (info !== null && this.referenceCount) {
|
|
240
254
|
this.scheduleCount(position, request, controller.signal);
|
|
241
255
|
}
|
|
242
256
|
}
|
|
@@ -270,7 +284,7 @@ export class HoverFeature {
|
|
|
270
284
|
/* How many places use the symbol, which can take a while in a large project and so arrives after the card does. */
|
|
271
285
|
private async count(position: EditorPosition, key: string, request: number, signal: AbortSignal): Promise<void> {
|
|
272
286
|
const { service } = this.language.project;
|
|
273
|
-
if (request !== this.request || !service.supports('textDocument/references', this.language.uri)) {
|
|
287
|
+
if (request !== this.request || !this.referenceCount || !service.supports('textDocument/references', this.language.uri)) {
|
|
274
288
|
return;
|
|
275
289
|
}
|
|
276
290
|
const found = await service.references(this.language.uri, position, false, { signal }).catch(() => null);
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { EditorLanguage, type EditorLanguageOptions } from './editor-language.ts';
|
|
2
|
+
export { KEY_PRIORITY } from './key-priority.ts';
|
|
3
|
+
export { realTimers, type Timers } from './timers.ts';
|
|
2
4
|
export { DEFAULT_RANGE_LINES, DEFAULT_RANGE_CHARACTERS, type SemanticTokensOptions } from './semantic-tokens.ts';
|
|
3
5
|
export { ProjectLanguage, type FileOperationTarget } from './project-language.ts';
|
|
4
6
|
export type { LanguageDocumentHandle } from './project-language.ts';
|
|
@@ -15,6 +17,7 @@ export { EditorView } from './EditorView.tsx';
|
|
|
15
17
|
export { createHolder } from './holder.ts';
|
|
16
18
|
export type { EditorViewProps } from './EditorView.tsx';
|
|
17
19
|
export { LanguagePopups } from './LanguagePopups.tsx';
|
|
20
|
+
export type { LanguagePopupsProps } from './LanguagePopups.tsx';
|
|
18
21
|
export { AnchoredPopup } from './AnchoredPopup.tsx';
|
|
19
22
|
export { HoverCard } from './HoverCard.tsx';
|
|
20
23
|
export { CompletionPopup } from './CompletionPopup.tsx';
|
|
@@ -36,13 +39,14 @@ export type { EditorRendering } from './rendering-context.ts';
|
|
|
36
39
|
export { FindReplace } from './FindReplace.tsx';
|
|
37
40
|
export { ChangeReview } from './ChangeReview.tsx';
|
|
38
41
|
export type { ChangeReviewProps } from './ChangeReview.tsx';
|
|
39
|
-
export { ProjectProblems, countsOf, compareRows } from './project-problems.ts';
|
|
40
|
-
export type { ProblemFile, ProblemRow } from './project-problems.ts';
|
|
42
|
+
export { ProjectProblems, serviceProblems, countsOf, compareRows } from './project-problems.ts';
|
|
43
|
+
export type { ProblemFile, ProblemRow, ProblemReport, ProblemSource } from './project-problems.ts';
|
|
41
44
|
export { applyWorkspaceEdit, supportedResourceOperations } from './workspace-edit.ts';
|
|
42
45
|
export type { ProjectFiles, StagedFile, WorkspaceEditHost } from './workspace-edit.ts';
|
|
43
46
|
export { useProblemCounts } from './use-problem-counts.ts';
|
|
44
47
|
export type { ProblemCounts } from './diagnostics.ts';
|
|
45
48
|
export type { BlameSource, CodeVisionSettings } from './code-vision.ts';
|
|
49
|
+
export type { HoverSettings } from './hover.ts';
|
|
46
50
|
export type { Problem } from './diagnostics-model.ts';
|
|
47
51
|
export type {
|
|
48
52
|
PopupState,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { EditorKeyHandler } from '@adecore/editor';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Where each feature of an editor sees a key through `EditorLanguage.onKey`, the highest first. A hundred
|
|
5
|
+
* apart, so a handler of the app can stand between two features; 0, the default, comes after all of them.
|
|
6
|
+
*/
|
|
7
|
+
export const KEY_PRIORITY = {
|
|
8
|
+
pick: 1500,
|
|
9
|
+
diagnostics: 1400,
|
|
10
|
+
hover: 1300,
|
|
11
|
+
completion: 1200,
|
|
12
|
+
snippets: 1100,
|
|
13
|
+
signature: 1000,
|
|
14
|
+
highlights: 900,
|
|
15
|
+
codeActions: 800,
|
|
16
|
+
rename: 700,
|
|
17
|
+
navigation: 600,
|
|
18
|
+
peek: 500,
|
|
19
|
+
callHierarchy: 400,
|
|
20
|
+
symbolPicker: 300,
|
|
21
|
+
history: 200,
|
|
22
|
+
renderedDocs: 100
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
interface Entry {
|
|
26
|
+
readonly handler: EditorKeyHandler;
|
|
27
|
+
readonly priority: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Hands a key to its handlers by priority until one takes it; of two of the same priority the one added first. */
|
|
31
|
+
export class KeyDispatcher {
|
|
32
|
+
private readonly entries: Entry[] = [];
|
|
33
|
+
|
|
34
|
+
add(handler: EditorKeyHandler, priority: number): () => void {
|
|
35
|
+
const entry = { handler, priority };
|
|
36
|
+
const index = this.entries.findIndex((other) => other.priority < priority);
|
|
37
|
+
this.entries.splice(index === -1 ? this.entries.length : index, 0, entry);
|
|
38
|
+
return () => {
|
|
39
|
+
const at = this.entries.indexOf(entry);
|
|
40
|
+
if (at !== -1) {
|
|
41
|
+
this.entries.splice(at, 1);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
dispatch(event: KeyboardEvent): boolean {
|
|
47
|
+
return [...this.entries].some((entry) => entry.handler(event));
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/locales/en.json
CHANGED
|
@@ -82,6 +82,9 @@
|
|
|
82
82
|
"symbol": "Matches"
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
|
+
"panel": {
|
|
86
|
+
"resize": "Resize panel"
|
|
87
|
+
},
|
|
85
88
|
"peek": {
|
|
86
89
|
"unavailable": "No language server finds references in this file",
|
|
87
90
|
"none": "No references found",
|
|
@@ -175,7 +178,8 @@
|
|
|
175
178
|
"details": "Details",
|
|
176
179
|
"none": "No suggestions",
|
|
177
180
|
"transformBusy": "Snippet transforms are busy; try again",
|
|
178
|
-
"transformFailed": "Snippet transform could not be applied"
|
|
181
|
+
"transformFailed": "Snippet transform could not be applied",
|
|
182
|
+
"failed": "Could not insert the suggestion: {{message}}"
|
|
179
183
|
},
|
|
180
184
|
"history": {
|
|
181
185
|
"recent": "Recent locations"
|
package/src/locales/nl.json
CHANGED
|
@@ -82,6 +82,9 @@
|
|
|
82
82
|
"symbol": "Overeenkomsten"
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
|
+
"panel": {
|
|
86
|
+
"resize": "Paneel vergroten of verkleinen"
|
|
87
|
+
},
|
|
85
88
|
"peek": {
|
|
86
89
|
"unavailable": "Geen language server vindt referenties in dit bestand",
|
|
87
90
|
"none": "Geen referenties gevonden",
|
|
@@ -175,7 +178,8 @@
|
|
|
175
178
|
"details": "Details",
|
|
176
179
|
"none": "Geen suggesties",
|
|
177
180
|
"transformBusy": "Snippettransformaties zijn bezig; probeer het opnieuw",
|
|
178
|
-
"transformFailed": "Snippettransformatie kon niet worden toegepast"
|
|
181
|
+
"transformFailed": "Snippettransformatie kon niet worden toegepast",
|
|
182
|
+
"failed": "De suggestie kon niet worden ingevoegd: {{message}}"
|
|
179
183
|
},
|
|
180
184
|
"history": {
|
|
181
185
|
"recent": "Recente plekken"
|
package/src/models.ts
CHANGED
|
@@ -37,6 +37,7 @@ export {
|
|
|
37
37
|
markerOf,
|
|
38
38
|
comparePositions,
|
|
39
39
|
shiftPosition,
|
|
40
|
+
endOfInsertion,
|
|
40
41
|
shiftRange,
|
|
41
42
|
rangeHolds,
|
|
42
43
|
problemsAt,
|
|
@@ -159,7 +160,6 @@ export {
|
|
|
159
160
|
type InlineAnswer,
|
|
160
161
|
parseAnswer,
|
|
161
162
|
fitReplacement,
|
|
162
|
-
endOfInsertion,
|
|
163
163
|
type DiffSegment,
|
|
164
164
|
diffSegments,
|
|
165
165
|
emphasisOf,
|
package/src/navigation.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StaleResultError, fileUriToPath, type LanguageService, type Location, type LocationLink, type NavigationResult, type Range } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { EditorPosition } from '@adecore/editor';
|
|
3
4
|
import { basenameOf } from './paths.ts';
|
|
4
5
|
import type { EditorLanguage } from './editor-language.ts';
|
|
@@ -76,14 +77,14 @@ export class NavigationFeature {
|
|
|
76
77
|
void this.go('definition', click.position);
|
|
77
78
|
return true;
|
|
78
79
|
}),
|
|
79
|
-
|
|
80
|
+
language.onKey((event) => {
|
|
80
81
|
const found = SHORTCUT_KINDS.find(([, name]) => isShortcut(this.language.shortcuts[name], event));
|
|
81
82
|
if (found === undefined) {
|
|
82
83
|
return false;
|
|
83
84
|
}
|
|
84
85
|
void this.go(found[0]);
|
|
85
86
|
return true;
|
|
86
|
-
})
|
|
87
|
+
}, KEY_PRIORITY.navigation)
|
|
87
88
|
];
|
|
88
89
|
language.onDispose(() => {
|
|
89
90
|
for (const off of offs) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* The height of the list-and-preview area of a peek or a call hierarchy, which is what a person drags; the bands and the title bar around it stay. */
|
|
2
|
+
export const PANEL_BODY_HEIGHT = 180;
|
|
3
|
+
/* Four rows of 22px, so a list never shrinks to nothing. */
|
|
4
|
+
export const PANEL_BODY_MIN = 88;
|
|
5
|
+
/* What the bands and the title bar take of the panel, which the share of the editor has to leave room for. */
|
|
6
|
+
const PANEL_CHROME = 63;
|
|
7
|
+
/* The share of the editor's height a panel may take, so the line it belongs to stays in view. */
|
|
8
|
+
const PANEL_MAX_SHARE = 0.8;
|
|
9
|
+
const PANEL_FALLBACK_AVAILABLE = 800;
|
|
10
|
+
export const PANEL_KEY_STEP = 22;
|
|
11
|
+
|
|
12
|
+
/* The tallest body for an editor that is `available` pixels high; never below the minimum, so a tiny editor still gets a usable panel. */
|
|
13
|
+
export function panelBodyMax(available: number): number {
|
|
14
|
+
return Math.max(PANEL_BODY_MIN, Math.floor(available * PANEL_MAX_SHARE) - PANEL_CHROME);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function clampPanelBody(height: number, available: number = PANEL_FALLBACK_AVAILABLE): number {
|
|
18
|
+
if (!Number.isFinite(height)) {
|
|
19
|
+
return PANEL_BODY_HEIGHT;
|
|
20
|
+
}
|
|
21
|
+
return Math.min(panelBodyMax(available), Math.max(PANEL_BODY_MIN, Math.round(height)));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* The height of the editor's scrolling area around a panel's row; the fallback stands in where nothing is laid out. */
|
|
25
|
+
export function availableHeightOf(container: HTMLElement | null): number {
|
|
26
|
+
const height = container?.closest<HTMLElement>('.se-viewport')?.clientHeight ?? 0;
|
|
27
|
+
return height > 0 ? height : PANEL_FALLBACK_AVAILABLE;
|
|
28
|
+
}
|
package/src/peek.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StaleResultError, type Location, type Range } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { EditorPosition } from '@adecore/editor';
|
|
3
4
|
import type { EditorLanguage } from './editor-language.ts';
|
|
4
5
|
import { uniquePlaces } from './navigation.ts';
|
|
@@ -6,6 +7,7 @@ import { PEEK_READ_FILES, definitionSnippetOf, peekFilesOf, snippetOf, type Peek
|
|
|
6
7
|
import type { PeekView } from './popups.ts';
|
|
7
8
|
import { isShortcut } from './shortcut-keys.ts';
|
|
8
9
|
import { messageOf } from './error-message.ts';
|
|
10
|
+
import { realTimers, revealRow, type Timers } from './timers.ts';
|
|
9
11
|
|
|
10
12
|
const TOAST_ID = 'language-peek';
|
|
11
13
|
/* The block as the panel draws it: a hatched band, the header, eight lines of code and a hatched band, until the editor has measured it. */
|
|
@@ -29,6 +31,7 @@ function placeKey(location: Location): string {
|
|
|
29
31
|
*/
|
|
30
32
|
export class PeekFeature {
|
|
31
33
|
private readonly language: EditorLanguage;
|
|
34
|
+
private readonly timers: Timers;
|
|
32
35
|
private kind: PeekKind = 'references';
|
|
33
36
|
/* The whole declaration of each place of a definition peek, where the server gave it. */
|
|
34
37
|
private targets = new Map<string, Range>();
|
|
@@ -38,11 +41,12 @@ export class PeekFeature {
|
|
|
38
41
|
private line = 0;
|
|
39
42
|
private token = 0;
|
|
40
43
|
|
|
41
|
-
constructor(language: EditorLanguage) {
|
|
44
|
+
constructor(language: EditorLanguage, timers: Timers = realTimers) {
|
|
42
45
|
this.language = language;
|
|
46
|
+
this.timers = timers;
|
|
43
47
|
const { editor } = language;
|
|
44
48
|
const offs = [
|
|
45
|
-
|
|
49
|
+
language.onKey((event) => {
|
|
46
50
|
if (isShortcut(this.language.shortcuts.peekReferences, event)) {
|
|
47
51
|
void this.open();
|
|
48
52
|
return true;
|
|
@@ -52,7 +56,7 @@ export class PeekFeature {
|
|
|
52
56
|
return true;
|
|
53
57
|
}
|
|
54
58
|
return this.key(event);
|
|
55
|
-
}),
|
|
59
|
+
}, KEY_PRIORITY.peek),
|
|
56
60
|
editor.onTextChange(() => this.close())
|
|
57
61
|
];
|
|
58
62
|
language.onDispose(() => {
|
|
@@ -201,6 +205,7 @@ export class PeekFeature {
|
|
|
201
205
|
const view = this.language.popups.getState().peek;
|
|
202
206
|
if (view !== null && view.container !== container) {
|
|
203
207
|
this.language.popups.setState({ peek: { ...view, container } });
|
|
208
|
+
revealRow(this.timers, container);
|
|
204
209
|
}
|
|
205
210
|
}
|
|
206
211
|
|
package/src/pick.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EditorPosition } from '@adecore/editor';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
3
4
|
import type { PickGroup, PickPreview } from './popups.ts';
|
|
4
5
|
import { realTimers, type Timers } from './timers.ts';
|
|
@@ -45,7 +46,7 @@ export class PickFeature {
|
|
|
45
46
|
this.timers = timers;
|
|
46
47
|
const { editor } = language;
|
|
47
48
|
const offs = [
|
|
48
|
-
|
|
49
|
+
language.onKey((event) => this.key(event), KEY_PRIORITY.pick),
|
|
49
50
|
editor.onTextChange(() => this.close()),
|
|
50
51
|
editor.onCaret(() => this.close()),
|
|
51
52
|
editor.onBlur(() => this.close())
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FocusEvent, MouseEvent } from 'react';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The handlers for a popup list whose rows the keyboard never enters: the text it belongs to keeps the
|
|
5
|
+
* focus, and the active row is only `aria-activedescendant` or a highlight. Chromium reports that row as
|
|
6
|
+
* the focused element, so a tool that focuses it before it types would otherwise pull the keys out of the
|
|
7
|
+
* text. A press on the list moves no focus, and a row that gets it anyway hands it straight back.
|
|
8
|
+
*/
|
|
9
|
+
export function keepFocusOutOfList(focusBack: () => void): {
|
|
10
|
+
onMouseDown(event: MouseEvent): void;
|
|
11
|
+
onFocus(event: FocusEvent): void;
|
|
12
|
+
} {
|
|
13
|
+
return {
|
|
14
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
15
|
+
onFocus: () => focusBack()
|
|
16
|
+
};
|
|
17
|
+
}
|
package/src/popups.ts
CHANGED
|
@@ -203,8 +203,7 @@ export interface PopupState {
|
|
|
203
203
|
readonly pick: PickView | null;
|
|
204
204
|
readonly rename: RenameView | null;
|
|
205
205
|
readonly peek: PeekView | null;
|
|
206
|
-
|
|
207
|
-
readonly callHierarchy?: CallHierarchyView | null;
|
|
206
|
+
readonly callHierarchy: CallHierarchyView | null;
|
|
208
207
|
readonly symbols: SymbolsView | null;
|
|
209
208
|
readonly menu: MenuView | null;
|
|
210
209
|
readonly authors: AuthorsView | null;
|