@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/project-language.js
CHANGED
|
@@ -2,9 +2,10 @@ import { disposeSnippetTransforms } from './snippet-transform-queue.js';
|
|
|
2
2
|
import i18next, {} from 'i18next';
|
|
3
3
|
import { fileUriToPath, pathToFileUri } from '@adecore/lsp';
|
|
4
4
|
import { NavigationHistory } from './navigation-history.js';
|
|
5
|
-
import { ProjectProblems } from './project-problems.js';
|
|
5
|
+
import { ProjectProblems, serviceProblems } from './project-problems.js';
|
|
6
6
|
import { applyWorkspaceEdit } from './workspace-edit.js';
|
|
7
7
|
import { relativeTo } from './paths.js';
|
|
8
|
+
import { PANEL_BODY_HEIGHT } from './panel-size.js';
|
|
8
9
|
/* Sends one editor's edits to the document, holding them until the document is open. */
|
|
9
10
|
class DocumentSync {
|
|
10
11
|
ready;
|
|
@@ -71,6 +72,9 @@ export class ProjectLanguage {
|
|
|
71
72
|
history = new NavigationHistory();
|
|
72
73
|
holders = new Map();
|
|
73
74
|
files;
|
|
75
|
+
/* The body height of the peek and call hierarchy panels, shared by the editors of the project. */
|
|
76
|
+
panelBodyHeight;
|
|
77
|
+
panelListeners = new Set();
|
|
74
78
|
pendingCaret = null;
|
|
75
79
|
lastPlace = null;
|
|
76
80
|
disposed = false;
|
|
@@ -80,7 +84,21 @@ export class ProjectLanguage {
|
|
|
80
84
|
this.i18n = host.i18n ?? i18next;
|
|
81
85
|
this.folder = host.folder ?? '';
|
|
82
86
|
this.files = host.files ?? null;
|
|
83
|
-
this.
|
|
87
|
+
this.panelBodyHeight = host.panelBodyHeight ?? PANEL_BODY_HEIGHT;
|
|
88
|
+
this.problems = new ProjectProblems(host.problemSource ?? serviceProblems(service, (uri) => this.pathOfLocation(uri) ?? uri));
|
|
89
|
+
}
|
|
90
|
+
/* Open panels of other editors of the project follow a resize. */
|
|
91
|
+
onPanelBodyChange(listener) {
|
|
92
|
+
this.panelListeners.add(listener);
|
|
93
|
+
return () => this.panelListeners.delete(listener);
|
|
94
|
+
}
|
|
95
|
+
resizePanelBody(height) {
|
|
96
|
+
if (height === this.panelBodyHeight) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.panelBodyHeight = height;
|
|
100
|
+
this.panelListeners.forEach((listener) => listener(height));
|
|
101
|
+
this.host.onPanelResize?.(height);
|
|
84
102
|
}
|
|
85
103
|
pathOfLocation(uri) {
|
|
86
104
|
if (this.host.pathOfUri !== undefined) {
|
|
@@ -193,9 +211,10 @@ export class ProjectLanguage {
|
|
|
193
211
|
void holder.sync.ready
|
|
194
212
|
.catch(() => undefined)
|
|
195
213
|
.then(() => {
|
|
196
|
-
if (
|
|
197
|
-
return
|
|
214
|
+
if (this.holders.has(uri)) {
|
|
215
|
+
return undefined;
|
|
198
216
|
}
|
|
217
|
+
return this.service.closeDocument(uri);
|
|
199
218
|
})
|
|
200
219
|
.catch(() => undefined);
|
|
201
220
|
}
|
|
@@ -9,12 +9,21 @@ export interface ProblemFile {
|
|
|
9
9
|
readonly rows: readonly ProblemRow[];
|
|
10
10
|
}
|
|
11
11
|
export declare function compareRows(left: ProblemRow, right: ProblemRow): number;
|
|
12
|
+
export interface ProblemReport {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly server: string;
|
|
15
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
16
|
+
}
|
|
17
|
+
export type ProblemSource = (listener: (report: ProblemReport) => void) => () => void;
|
|
18
|
+
export declare function serviceProblems(service: LanguageService, pathOf?: (uri: string) => string): ProblemSource;
|
|
12
19
|
export declare class ProjectProblems {
|
|
13
20
|
private readonly reports;
|
|
21
|
+
private readonly files;
|
|
14
22
|
private readonly listeners;
|
|
15
23
|
private readonly stop;
|
|
24
|
+
private order;
|
|
16
25
|
private snapshot;
|
|
17
|
-
constructor(
|
|
26
|
+
constructor(source: ProblemSource | LanguageService, pathOf?: (uri: string) => string);
|
|
18
27
|
report(path: string, server: string, diagnostics: readonly Diagnostic[]): void;
|
|
19
28
|
getSnapshot: () => readonly ProblemFile[];
|
|
20
29
|
subscribe: (listener: () => void) => (() => void);
|
package/dist/project-problems.js
CHANGED
|
@@ -5,22 +5,38 @@ export function compareRows(left, right) {
|
|
|
5
5
|
return (SEVERITY_ORDER[severityOf(left.diagnostic)] - SEVERITY_ORDER[severityOf(right.diagnostic)] ||
|
|
6
6
|
comparePositions(left.diagnostic.range.start, right.diagnostic.range.start));
|
|
7
7
|
}
|
|
8
|
+
/* The diagnostics a service reports, by the stored path `pathOf` gives each URI. */
|
|
9
|
+
export function serviceProblems(service, pathOf = (uri) => uri) {
|
|
10
|
+
return (listener) => {
|
|
11
|
+
const subscription = service.onDiagnostics((report) => listener({ path: pathOf(report.uri), server: report.source, diagnostics: report.diagnostics }));
|
|
12
|
+
return () => subscription.dispose();
|
|
13
|
+
};
|
|
14
|
+
}
|
|
8
15
|
/*
|
|
9
16
|
* Each report replaces the previous diagnostics from that source for the file. Files with no
|
|
10
17
|
* remaining problems drop out. Hints remain editor annotations rather than listed problems.
|
|
11
18
|
*/
|
|
12
19
|
export class ProjectProblems {
|
|
13
20
|
reports = new Map();
|
|
21
|
+
files = new Map();
|
|
14
22
|
listeners = new Set();
|
|
15
23
|
stop;
|
|
24
|
+
/* The paths of `files` in order; null once a file came or went. */
|
|
25
|
+
order = [];
|
|
26
|
+
/* Made on first read after a report, so a project nobody lists does no sorting. */
|
|
16
27
|
snapshot = [];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
/* A service stands for `serviceProblems(service, pathOf)`. */
|
|
29
|
+
constructor(source, pathOf) {
|
|
30
|
+
const listen = typeof source === 'function' ? source : serviceProblems(source, pathOf);
|
|
31
|
+
this.stop = listen((report) => this.report(report.path, report.server, report.diagnostics));
|
|
20
32
|
}
|
|
21
33
|
report(path, server, diagnostics) {
|
|
22
34
|
const kept = diagnostics.filter((diagnostic) => severityOf(diagnostic) !== 'hint');
|
|
23
35
|
const servers = this.reports.get(path) ?? new Map();
|
|
36
|
+
if (kept.length === 0 && !servers.has(server)) {
|
|
37
|
+
// A clean file reported clean again, as servers do after every edit.
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
24
40
|
if (kept.length === 0) {
|
|
25
41
|
servers.delete(server);
|
|
26
42
|
}
|
|
@@ -29,22 +45,30 @@ export class ProjectProblems {
|
|
|
29
45
|
}
|
|
30
46
|
if (servers.size === 0) {
|
|
31
47
|
this.reports.delete(path);
|
|
48
|
+
this.files.delete(path);
|
|
49
|
+
this.order = null;
|
|
32
50
|
}
|
|
33
51
|
else {
|
|
52
|
+
if (!this.files.has(path)) {
|
|
53
|
+
this.order = null;
|
|
54
|
+
}
|
|
34
55
|
this.reports.set(path, servers);
|
|
56
|
+
const rows = [...servers].flatMap(([name, list]) => list.map((diagnostic) => ({ diagnostic, server: name }))).sort(compareRows);
|
|
57
|
+
this.files.set(path, { path, rows });
|
|
35
58
|
}
|
|
36
|
-
this.snapshot =
|
|
37
|
-
.map(([file, byServer]) => ({
|
|
38
|
-
path: file,
|
|
39
|
-
rows: [...byServer].flatMap(([name, list]) => list.map((diagnostic) => ({ diagnostic, server: name }))).sort(compareRows)
|
|
40
|
-
}))
|
|
41
|
-
.sort((left, right) => left.path.localeCompare(right.path));
|
|
59
|
+
this.snapshot = null;
|
|
42
60
|
for (const listener of [...this.listeners]) {
|
|
43
61
|
listener();
|
|
44
62
|
}
|
|
45
63
|
}
|
|
46
64
|
/* The same array until the next report, which is what a store a component reads must hand out. */
|
|
47
|
-
getSnapshot = () =>
|
|
65
|
+
getSnapshot = () => {
|
|
66
|
+
if (this.snapshot === null) {
|
|
67
|
+
this.order ??= [...this.files.keys()].sort((left, right) => left.localeCompare(right));
|
|
68
|
+
this.snapshot = this.order.map((path) => this.files.get(path));
|
|
69
|
+
}
|
|
70
|
+
return this.snapshot;
|
|
71
|
+
};
|
|
48
72
|
subscribe = (listener) => {
|
|
49
73
|
this.listeners.add(listener);
|
|
50
74
|
return () => {
|
package/dist/proposal-model.d.ts
CHANGED
|
@@ -21,7 +21,6 @@ export interface InlineAnswer {
|
|
|
21
21
|
}
|
|
22
22
|
export declare function parseAnswer(text: string): InlineAnswer;
|
|
23
23
|
export declare function fitReplacement(selected: string, replacement: string): string;
|
|
24
|
-
export declare function endOfInsertion(start: EditorPosition, text: string): EditorPosition;
|
|
25
24
|
export interface DiffSegment {
|
|
26
25
|
readonly kind: 'same' | 'added' | 'removed';
|
|
27
26
|
readonly text: string;
|
package/dist/proposal-model.js
CHANGED
|
@@ -94,14 +94,6 @@ export function fitReplacement(selected, replacement) {
|
|
|
94
94
|
}
|
|
95
95
|
return breaks === '\n' ? fitted : fitted.replace(/\n/g, breaks);
|
|
96
96
|
}
|
|
97
|
-
/* Where the caret stands after `text` was typed at `start`. */
|
|
98
|
-
export function endOfInsertion(start, text) {
|
|
99
|
-
const lines = text.split('\n');
|
|
100
|
-
if (lines.length === 1) {
|
|
101
|
-
return { line: start.line, character: start.character + text.length };
|
|
102
|
-
}
|
|
103
|
-
return { line: start.line + lines.length - 1, character: lines.at(-1).length };
|
|
104
|
-
}
|
|
105
97
|
function linesOf(text) {
|
|
106
98
|
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
107
99
|
return lines.length > 1 && lines.at(-1) === '' ? lines.slice(0, -1) : lines;
|
package/dist/rename-model.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
|
-
import type { Location, PrepareRenameResult, TextEdit, WorkspaceEdit } from '@adecore/lsp';
|
|
2
|
+
import type { DocumentSymbolResult, Location, PrepareRenameResult, TextEdit, WorkspaceEdit } from '@adecore/lsp';
|
|
3
3
|
export declare function wordRangeAt(lineText: string, position: EditorPosition): EditorRange | null;
|
|
4
4
|
export interface RenameTarget {
|
|
5
5
|
readonly range: EditorRange;
|
|
@@ -12,6 +12,7 @@ export interface Occurrences {
|
|
|
12
12
|
readonly files: number;
|
|
13
13
|
}
|
|
14
14
|
export declare function occurrencesOf(locations: readonly Location[], uri: string): Occurrences;
|
|
15
|
+
export declare function renameKindOf(result: DocumentSymbolResult, name: string, places: readonly EditorRange[]): string | undefined;
|
|
15
16
|
export interface RenameRow {
|
|
16
17
|
readonly line: number;
|
|
17
18
|
readonly before: string;
|
package/dist/rename-model.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isIdentifierCharacter } from './completion-model.js';
|
|
2
|
+
import { rangeHolds } from './diagnostics-model.js';
|
|
2
3
|
import { textEntriesOf } from './workspace-edit.js';
|
|
3
4
|
/* The identifier the caret is in or touches; null where there is none. */
|
|
4
5
|
export function wordRangeAt(lineText, position) {
|
|
@@ -36,6 +37,62 @@ export function occurrencesOf(locations, uri) {
|
|
|
36
37
|
files: new Set(locations.map((location) => location.uri)).size
|
|
37
38
|
};
|
|
38
39
|
}
|
|
40
|
+
/* LSP's SymbolKind in words, indexed by its number. */
|
|
41
|
+
const SYMBOL_KINDS = [
|
|
42
|
+
'',
|
|
43
|
+
'file',
|
|
44
|
+
'module',
|
|
45
|
+
'namespace',
|
|
46
|
+
'package',
|
|
47
|
+
'class',
|
|
48
|
+
'method',
|
|
49
|
+
'property',
|
|
50
|
+
'field',
|
|
51
|
+
'constructor',
|
|
52
|
+
'enum',
|
|
53
|
+
'interface',
|
|
54
|
+
'function',
|
|
55
|
+
'variable',
|
|
56
|
+
'constant',
|
|
57
|
+
'string',
|
|
58
|
+
'number',
|
|
59
|
+
'boolean',
|
|
60
|
+
'array',
|
|
61
|
+
'object',
|
|
62
|
+
'key',
|
|
63
|
+
'null',
|
|
64
|
+
'enum member',
|
|
65
|
+
'struct',
|
|
66
|
+
'event',
|
|
67
|
+
'operator',
|
|
68
|
+
'type parameter'
|
|
69
|
+
];
|
|
70
|
+
/*
|
|
71
|
+
* The kind of the symbol a rename names, in words, from the outline of the open file: the symbol whose name
|
|
72
|
+
* holds one of the places of the name there. Undefined when the outline does not list it, as with most locals.
|
|
73
|
+
*/
|
|
74
|
+
export function renameKindOf(result, name, places) {
|
|
75
|
+
if (result === null || result.length === 0) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const named = (range) => places.some((place) => rangeHolds(range, place.start));
|
|
79
|
+
if ('range' in result[0]) {
|
|
80
|
+
let found;
|
|
81
|
+
const walk = (symbols) => {
|
|
82
|
+
for (const symbol of symbols) {
|
|
83
|
+
if (named(symbol.selectionRange)) {
|
|
84
|
+
found = symbol;
|
|
85
|
+
}
|
|
86
|
+
walk(symbol.children ?? []);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
walk(result);
|
|
90
|
+
return found === undefined ? undefined : SYMBOL_KINDS[found.kind];
|
|
91
|
+
}
|
|
92
|
+
// A flat list only gives the whole span of a symbol, so the name has to match as well.
|
|
93
|
+
const found = result.find((symbol) => 'location' in symbol && symbol.name === name && named(symbol.location.range));
|
|
94
|
+
return found === undefined ? undefined : SYMBOL_KINDS[found.kind];
|
|
95
|
+
}
|
|
39
96
|
function lineStarts(text) {
|
|
40
97
|
const starts = [0];
|
|
41
98
|
for (let index = 0; index < text.length; index++) {
|
package/dist/rename.d.ts
CHANGED
package/dist/rename.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StaleResultError } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
2
3
|
import { lineTextOf } from './line-text.js';
|
|
3
|
-
import { occurrencesOf, renamePreviewOf, renameTargetOf, wordRangeAt } from './rename-model.js';
|
|
4
|
+
import { occurrencesOf, renameKindOf, renamePreviewOf, renameTargetOf, wordRangeAt } from './rename-model.js';
|
|
4
5
|
import { isShortcut } from './shortcut-keys.js';
|
|
5
6
|
import { resourceOperationsOf } from './workspace-edit.js';
|
|
6
7
|
import { messageOf } from './error-message.js';
|
|
@@ -22,13 +23,13 @@ export class RenameFeature {
|
|
|
22
23
|
this.language = language;
|
|
23
24
|
const { editor } = language;
|
|
24
25
|
const offs = [
|
|
25
|
-
|
|
26
|
+
language.onKey((event) => {
|
|
26
27
|
if (!isShortcut(this.language.shortcuts.rename, event)) {
|
|
27
28
|
return false;
|
|
28
29
|
}
|
|
29
30
|
void this.start();
|
|
30
31
|
return true;
|
|
31
|
-
}),
|
|
32
|
+
}, KEY_PRIORITY.rename),
|
|
32
33
|
editor.onTextChange(() => this.cancel())
|
|
33
34
|
];
|
|
34
35
|
language.onDispose(() => {
|
|
@@ -192,8 +193,9 @@ export class RenameFeature {
|
|
|
192
193
|
start: { line: Math.max(0, line - CONTEXT_LINES), character: 0 },
|
|
193
194
|
end: { line: line + CONTEXT_LINES, character: Number.MAX_SAFE_INTEGER }
|
|
194
195
|
});
|
|
195
|
-
const uses = await this.linesOf(locations ?? []);
|
|
196
|
-
const
|
|
196
|
+
const [uses, kind] = await Promise.all([this.linesOf(locations ?? []), this.kindOf(session, locations ?? [])]);
|
|
197
|
+
const request = { uri, languageId, name: session.original, ...(kind === undefined ? {} : { kind }), context, uses };
|
|
198
|
+
const names = await suggestNames(request, session.names.signal);
|
|
197
199
|
if (this.session === session && names.length > 0) {
|
|
198
200
|
this.publish({ suggestions: names });
|
|
199
201
|
}
|
|
@@ -202,6 +204,20 @@ export class RenameFeature {
|
|
|
202
204
|
// Suggestions do not prevent a manual rename.
|
|
203
205
|
}
|
|
204
206
|
}
|
|
207
|
+
/* So a suggester knows a function from a variable; a rename on a use finds the kind through the declaration's place in this file. */
|
|
208
|
+
async kindOf(session, locations) {
|
|
209
|
+
const { project, uri } = this.language;
|
|
210
|
+
if (!project.service.supports('textDocument/documentSymbol', uri)) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const symbols = await project.service.documentSymbols(uri, { signal: session.names.signal, parallel: true });
|
|
215
|
+
return renameKindOf(symbols, session.original, [session.target.range, ...occurrencesOf(locations, uri).inFile]);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
205
221
|
/* The text of the first lines that name the symbol, in the order the servers listed them, reading at most a few other files. */
|
|
206
222
|
async linesOf(locations) {
|
|
207
223
|
const { project } = this.language;
|
package/dist/rendered-docs.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type ParsedDocComment } from './doc-comment-model.ts';
|
|
|
4
4
|
import { type Timers } from './timers.ts';
|
|
5
5
|
export interface RenderedDocsSettings {
|
|
6
6
|
readonly renderDocComments: boolean;
|
|
7
|
+
readonly enabled?: boolean;
|
|
7
8
|
}
|
|
8
9
|
export interface RenderedDocEntry {
|
|
9
10
|
readonly id: string;
|
|
@@ -15,6 +16,7 @@ export declare class RenderedDocsFeature {
|
|
|
15
16
|
private readonly language;
|
|
16
17
|
private readonly timers;
|
|
17
18
|
private settings;
|
|
19
|
+
private enabled;
|
|
18
20
|
private comments;
|
|
19
21
|
private overrides;
|
|
20
22
|
private readonly containers;
|
package/dist/rendered-docs.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { findDocComments } from '@adecore/editor';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
2
3
|
import { parseDocComment } from './doc-comment-model.js';
|
|
3
4
|
import { isShortcut } from './shortcut-keys.js';
|
|
4
5
|
import { realTimers } from './timers.js';
|
|
@@ -44,6 +45,7 @@ export class RenderedDocsFeature {
|
|
|
44
45
|
language;
|
|
45
46
|
timers;
|
|
46
47
|
settings = { renderDocComments: false };
|
|
48
|
+
enabled = true;
|
|
47
49
|
comments = [];
|
|
48
50
|
overrides = [];
|
|
49
51
|
containers = new Map();
|
|
@@ -85,9 +87,9 @@ export class RenderedDocsFeature {
|
|
|
85
87
|
this.toggleAt(line);
|
|
86
88
|
}
|
|
87
89
|
}),
|
|
88
|
-
|
|
90
|
+
language.onKey((event) => {
|
|
89
91
|
return isShortcut(this.language.shortcuts.toggleRenderedDocs, event) && this.toggle();
|
|
90
|
-
}),
|
|
92
|
+
}, KEY_PRIORITY.renderedDocs),
|
|
91
93
|
editor.onViewChange(() => {
|
|
92
94
|
this.sweep();
|
|
93
95
|
this.releaseRevealed();
|
|
@@ -113,11 +115,16 @@ export class RenderedDocsFeature {
|
|
|
113
115
|
}
|
|
114
116
|
/* Whether every documentation comment is drawn as text until a person switches it. Changing it puts every comment back under it. */
|
|
115
117
|
configure(settings) {
|
|
116
|
-
|
|
118
|
+
const enabled = settings.enabled !== false;
|
|
119
|
+
if (settings.renderDocComments === this.settings.renderDocComments && enabled === this.enabled) {
|
|
117
120
|
return;
|
|
118
121
|
}
|
|
122
|
+
const sameRendering = settings.renderDocComments === this.settings.renderDocComments;
|
|
119
123
|
this.settings = settings;
|
|
120
|
-
this.
|
|
124
|
+
this.enabled = enabled;
|
|
125
|
+
if (!sameRendering) {
|
|
126
|
+
this.clearOverrides();
|
|
127
|
+
}
|
|
121
128
|
this.publish();
|
|
122
129
|
}
|
|
123
130
|
/* The documentation comments of the document, from the last time it settled. */
|
|
@@ -132,7 +139,7 @@ export class RenderedDocsFeature {
|
|
|
132
139
|
toggle() {
|
|
133
140
|
this.settle();
|
|
134
141
|
const comment = this.at(this.language.editor.getCaret().line);
|
|
135
|
-
if (comment === undefined || !this.canRender(comment)) {
|
|
142
|
+
if (!this.enabled || comment === undefined || !this.canRender(comment)) {
|
|
136
143
|
return false;
|
|
137
144
|
}
|
|
138
145
|
this.setRendered(comment, !this.wantsRendered(comment));
|
|
@@ -142,7 +149,7 @@ export class RenderedDocsFeature {
|
|
|
142
149
|
toggleAt(startLine) {
|
|
143
150
|
this.settle();
|
|
144
151
|
const comment = this.comments.find((candidate) => candidate.startLine === startLine);
|
|
145
|
-
if (comment === undefined || !this.canRender(comment)) {
|
|
152
|
+
if (!this.enabled || comment === undefined || !this.canRender(comment)) {
|
|
146
153
|
return false;
|
|
147
154
|
}
|
|
148
155
|
this.setRendered(comment, !this.wantsRendered(comment));
|
|
@@ -186,7 +193,7 @@ export class RenderedDocsFeature {
|
|
|
186
193
|
return comment.startLine > 0 || comment.endLine < this.language.editor.positionAt(Number.MAX_SAFE_INTEGER).line;
|
|
187
194
|
}
|
|
188
195
|
wantsRendered(comment) {
|
|
189
|
-
if (!this.canRender(comment)) {
|
|
196
|
+
if (!this.enabled || !this.canRender(comment)) {
|
|
190
197
|
return false;
|
|
191
198
|
}
|
|
192
199
|
const start = startOf(comment);
|
|
@@ -325,7 +332,7 @@ export class RenderedDocsFeature {
|
|
|
325
332
|
this.bump();
|
|
326
333
|
}
|
|
327
334
|
this.markerLines.clear();
|
|
328
|
-
const markers = this.comments.map((comment) => {
|
|
335
|
+
const markers = (this.enabled ? this.comments : []).map((comment) => {
|
|
329
336
|
const id = `docs:${comment.startLine}`;
|
|
330
337
|
this.markerLines.set(id, comment.startLine);
|
|
331
338
|
const on = this.wantsRendered(comment);
|
package/dist/signature.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
1
2
|
import { signatureViewOf } from './signature-model.js';
|
|
2
3
|
import { isShortcut } from './shortcut-keys.js';
|
|
3
4
|
import { realTimers } from './timers.js';
|
|
@@ -24,8 +25,8 @@ export class SignatureFeature {
|
|
|
24
25
|
editor.onTextChange((change) => this.edited(change)),
|
|
25
26
|
editor.onCaret(() => this.retrigger()),
|
|
26
27
|
editor.onBlur(() => this.close()),
|
|
27
|
-
|
|
28
|
-
if (event.key === 'Escape' && this.isOpen && this.language.popups.getState().callHierarchy
|
|
28
|
+
language.onKey((event) => {
|
|
29
|
+
if (event.key === 'Escape' && this.isOpen && this.language.popups.getState().callHierarchy === null) {
|
|
29
30
|
this.close();
|
|
30
31
|
return true;
|
|
31
32
|
}
|
|
@@ -37,7 +38,7 @@ export class SignatureFeature {
|
|
|
37
38
|
return this.cycle(event.key === 'ArrowDown' ? 1 : -1);
|
|
38
39
|
}
|
|
39
40
|
return false;
|
|
40
|
-
})
|
|
41
|
+
}, KEY_PRIORITY.signature)
|
|
41
42
|
];
|
|
42
43
|
language.onDispose(() => {
|
|
43
44
|
for (const off of offs) {
|
|
@@ -10,7 +10,7 @@ export interface SnippetInsertion {
|
|
|
10
10
|
readonly stops: readonly SnippetStop[];
|
|
11
11
|
readonly fields?: readonly SnippetField[];
|
|
12
12
|
}
|
|
13
|
-
type SnippetHost = Pick<EditorLanguage, 'editor' | 'onDispose'> & Partial<Pick<EditorLanguage, 'popups' | 'pick' | 'project'>>;
|
|
13
|
+
type SnippetHost = Pick<EditorLanguage, 'editor' | 'onDispose'> & Partial<Pick<EditorLanguage, 'popups' | 'pick' | 'project' | 'onKey'>>;
|
|
14
14
|
export declare class SnippetFeature {
|
|
15
15
|
private readonly language;
|
|
16
16
|
private frames;
|
package/dist/snippet-session.js
CHANGED
|
@@ -3,6 +3,7 @@ import { SnippetFieldTransforms } from './snippet-field-transforms.js';
|
|
|
3
3
|
import { SnippetTransformError } from './snippet-transform-client.js';
|
|
4
4
|
import { acquireSnippetTransforms } from './snippet-transform-queue.js';
|
|
5
5
|
import { realTimers } from './timers.js';
|
|
6
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
6
7
|
import { MAX_TRANSFORM_CHARACTERS } from './snippet-transform-protocol.js';
|
|
7
8
|
import { comparePositions, rangeHolds, shiftPosition } from './diagnostics-model.js';
|
|
8
9
|
export function positionInInsertion(start, text, offset) {
|
|
@@ -128,7 +129,12 @@ export class SnippetFeature {
|
|
|
128
129
|
this.transforms = language.project === undefined ? null : acquireSnippetTransforms(language.project, timers);
|
|
129
130
|
const { editor } = language;
|
|
130
131
|
this.fieldTransforms = new SnippetFieldTransforms(editor, this.transforms, (targets, values) => this.applyFieldTransforms(targets, values));
|
|
131
|
-
const
|
|
132
|
+
const key = (event) => this.key(event);
|
|
133
|
+
const offs = [
|
|
134
|
+
language.onKey === undefined ? editor.onKeyDown(key) : language.onKey(key, KEY_PRIORITY.snippets),
|
|
135
|
+
editor.onTextChange((change) => this.edited(change)),
|
|
136
|
+
editor.onCaret(() => this.moved())
|
|
137
|
+
];
|
|
132
138
|
language.onDispose(() => {
|
|
133
139
|
this.transforms?.dispose();
|
|
134
140
|
this.end();
|
|
@@ -395,7 +401,7 @@ export class SnippetFeature {
|
|
|
395
401
|
if (event.key === 'Escape') {
|
|
396
402
|
const popups = this.language.popups?.getState();
|
|
397
403
|
if (popups !== undefined &&
|
|
398
|
-
[popups.completion, popups.signature, popups.hover, popups.pick, popups.peek, popups.callHierarchy
|
|
404
|
+
[popups.completion, popups.signature, popups.hover, popups.pick, popups.peek, popups.callHierarchy].some((popup) => popup !== null)) {
|
|
399
405
|
return false;
|
|
400
406
|
}
|
|
401
407
|
const active = this.isActive;
|
package/dist/symbol-picker.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StaleResultError } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.js';
|
|
2
3
|
import { basenameOf } from './paths.js';
|
|
3
4
|
import { entriesOf, workspaceEntriesOf } from './symbol-picker-model.js';
|
|
4
5
|
import { isShortcut } from './shortcut-keys.js';
|
|
@@ -13,13 +14,13 @@ export class SymbolPickerFeature {
|
|
|
13
14
|
language;
|
|
14
15
|
constructor(language) {
|
|
15
16
|
this.language = language;
|
|
16
|
-
const off = language.
|
|
17
|
+
const off = language.onKey((event) => {
|
|
17
18
|
if (!isShortcut(this.language.shortcuts.goToSymbol, event)) {
|
|
18
19
|
return false;
|
|
19
20
|
}
|
|
20
21
|
void this.open();
|
|
21
22
|
return true;
|
|
22
|
-
});
|
|
23
|
+
}, KEY_PRIORITY.symbolPicker);
|
|
23
24
|
language.onDispose(() => {
|
|
24
25
|
off();
|
|
25
26
|
this.close();
|
package/dist/timers.d.ts
CHANGED
package/dist/timers.js
CHANGED
|
@@ -32,3 +32,7 @@ export class ManualTimers {
|
|
|
32
32
|
return this.pending.size;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/* Scrolls a row the editor opened under the caret line into view, which may sit at the bottom of the view with the row below the fold. The element is in the page a turn later. */
|
|
36
|
+
export function revealRow(timers, container) {
|
|
37
|
+
timers.set(() => container.scrollIntoView?.({ block: 'nearest' }), 0);
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { clampPanelBody } from './panel-size.js';
|
|
3
|
+
/* The body height of a panel, from the project that remembers it for the next one; `commit` hands it to the host. */
|
|
4
|
+
export function usePanelBody(language) {
|
|
5
|
+
const { project } = language;
|
|
6
|
+
const [height, set] = useState(() => clampPanelBody(project.panelBodyHeight));
|
|
7
|
+
useEffect(() => project.onPanelBodyChange((value) => set(clampPanelBody(value))), [project]);
|
|
8
|
+
return {
|
|
9
|
+
height,
|
|
10
|
+
set,
|
|
11
|
+
commit: (value) => project.resizePanelBody(value)
|
|
12
|
+
};
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/editor-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Language-enabled editors, feature coordination and React popups over injected language services.",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"test": "bun test --conditions=source"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@adecore/editor": "0.
|
|
66
|
-
"@adecore/lsp": "0.
|
|
67
|
-
"@adecore/merge": "0.
|
|
65
|
+
"@adecore/editor": "0.24.0",
|
|
66
|
+
"@adecore/lsp": "0.24.0",
|
|
67
|
+
"@adecore/merge": "0.24.0",
|
|
68
68
|
"lucide-react": "^1.47.0",
|
|
69
69
|
"react-markdown": "^10.1.0",
|
|
70
70
|
"remark-breaks": "^4.0.0",
|
|
@@ -72,14 +72,14 @@
|
|
|
72
72
|
"zustand": "^5.0.15"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"@adecore/ui": "^0.
|
|
75
|
+
"@adecore/ui": "^0.24.0",
|
|
76
76
|
"i18next": "^26.4.2",
|
|
77
77
|
"react": "^19.3.0",
|
|
78
78
|
"react-dom": "^19.3.0",
|
|
79
79
|
"react-i18next": "^17.0.15"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
|
-
"@adecore/ui": "
|
|
82
|
+
"@adecore/ui": "0.24.0",
|
|
83
83
|
"@types/bun": "^1.4.2",
|
|
84
84
|
"@types/react": "^19.3.0",
|
|
85
85
|
"@types/react-dom": "^19.3.0",
|
package/src/AnchoredPopup.tsx
CHANGED
|
@@ -7,6 +7,8 @@ export interface AnchoredPopupProps {
|
|
|
7
7
|
/* The character the popup belongs to, in the page's pixels. */
|
|
8
8
|
rect: EditorRect;
|
|
9
9
|
className?: string;
|
|
10
|
+
/* What the card does with content past its room: `scroll` it, or `clip` it for content that scrolls on its own. `scroll` without one. */
|
|
11
|
+
overflow?: 'scroll' | 'clip';
|
|
10
12
|
children: ReactNode;
|
|
11
13
|
placement?: PlaceOptions;
|
|
12
14
|
onPointerEnter?(): void;
|
|
@@ -17,7 +19,7 @@ export interface AnchoredPopupProps {
|
|
|
17
19
|
* A card next to a character of the editor, portaled to the page: inside a scaled canvas node it would be
|
|
18
20
|
* drawn at that scale and clipped by the node. Placed from the character's screen rectangle, so it holds at any zoom.
|
|
19
21
|
*/
|
|
20
|
-
export function AnchoredPopup({ rect, className, children, placement, onPointerEnter, onPointerLeave }: AnchoredPopupProps) {
|
|
22
|
+
export function AnchoredPopup({ rect, className, overflow = 'scroll', children, placement, onPointerEnter, onPointerLeave }: AnchoredPopupProps) {
|
|
21
23
|
const element = useRef<HTMLDivElement>(null);
|
|
22
24
|
|
|
23
25
|
// Every render, since the character moves when the editor scrolls and the card's own size follows its content.
|
|
@@ -38,7 +40,7 @@ export function AnchoredPopup({ rect, className, children, placement, onPointerE
|
|
|
38
40
|
return createPortal(
|
|
39
41
|
<div
|
|
40
42
|
ref={element}
|
|
41
|
-
className={`fixed top-0 left-0 z-(--z-popup) overflow-y-auto rounded-lg border border-border bg-surface-raised text-text shadow-(--float-shadow) ${className ?? ''}`}
|
|
43
|
+
className={`fixed top-0 left-0 z-(--z-popup) ${overflow === 'clip' ? 'overflow-hidden' : 'overflow-y-auto'} rounded-lg border border-border bg-surface-raised text-text shadow-(--float-shadow) ${className ?? ''}`}
|
|
42
44
|
style={{ visibility: 'hidden' }}
|
|
43
45
|
onPointerEnter={onPointerEnter}
|
|
44
46
|
onPointerLeave={onPointerLeave}
|