@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/project-language.ts
CHANGED
|
@@ -3,10 +3,11 @@ import i18next, { type i18n } from 'i18next';
|
|
|
3
3
|
import { fileUriToPath, pathToFileUri, type ApplyWorkspaceEditResult, type ContentChange, type LanguageService, type WorkspaceEdit } from '@adecore/lsp';
|
|
4
4
|
import type { Editor, EditorPosition } from '@adecore/editor';
|
|
5
5
|
import { NavigationHistory, type Place } from './navigation-history.ts';
|
|
6
|
-
import { ProjectProblems } from './project-problems.ts';
|
|
6
|
+
import { ProjectProblems, serviceProblems } from './project-problems.ts';
|
|
7
7
|
import { applyWorkspaceEdit, type ProjectFiles } from './workspace-edit.ts';
|
|
8
8
|
import type { LanguageHost } from './host-types.ts';
|
|
9
9
|
import { relativeTo } from './paths.ts';
|
|
10
|
+
import { PANEL_BODY_HEIGHT } from './panel-size.ts';
|
|
10
11
|
|
|
11
12
|
export interface LanguageDocumentHandle {
|
|
12
13
|
readonly uri: string;
|
|
@@ -98,6 +99,9 @@ export class ProjectLanguage {
|
|
|
98
99
|
readonly history = new NavigationHistory();
|
|
99
100
|
private readonly holders = new Map<string, Holder>();
|
|
100
101
|
private readonly files: ProjectFiles | null;
|
|
102
|
+
/* The body height of the peek and call hierarchy panels, shared by the editors of the project. */
|
|
103
|
+
panelBodyHeight: number;
|
|
104
|
+
private readonly panelListeners = new Set<(height: number) => void>();
|
|
101
105
|
private pendingCaret: Place | null = null;
|
|
102
106
|
private lastPlace: Place | null = null;
|
|
103
107
|
private disposed = false;
|
|
@@ -108,7 +112,23 @@ export class ProjectLanguage {
|
|
|
108
112
|
this.i18n = host.i18n ?? i18next;
|
|
109
113
|
this.folder = host.folder ?? '';
|
|
110
114
|
this.files = host.files ?? null;
|
|
111
|
-
this.
|
|
115
|
+
this.panelBodyHeight = host.panelBodyHeight ?? PANEL_BODY_HEIGHT;
|
|
116
|
+
this.problems = new ProjectProblems(host.problemSource ?? serviceProblems(service, (uri) => this.pathOfLocation(uri) ?? uri));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Open panels of other editors of the project follow a resize. */
|
|
120
|
+
onPanelBodyChange(listener: (height: number) => void): () => void {
|
|
121
|
+
this.panelListeners.add(listener);
|
|
122
|
+
return () => this.panelListeners.delete(listener);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
resizePanelBody(height: number): void {
|
|
126
|
+
if (height === this.panelBodyHeight) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.panelBodyHeight = height;
|
|
130
|
+
this.panelListeners.forEach((listener) => listener(height));
|
|
131
|
+
this.host.onPanelResize?.(height);
|
|
112
132
|
}
|
|
113
133
|
|
|
114
134
|
pathOfLocation(uri: string): string | null {
|
|
@@ -234,9 +254,10 @@ export class ProjectLanguage {
|
|
|
234
254
|
void holder.sync.ready
|
|
235
255
|
.catch(() => undefined)
|
|
236
256
|
.then(() => {
|
|
237
|
-
if (
|
|
238
|
-
return
|
|
257
|
+
if (this.holders.has(uri)) {
|
|
258
|
+
return undefined;
|
|
239
259
|
}
|
|
260
|
+
return this.service.closeDocument(uri);
|
|
240
261
|
})
|
|
241
262
|
.catch(() => undefined);
|
|
242
263
|
} else if (holder.sync.editor === editor) {
|
package/src/project-problems.ts
CHANGED
|
@@ -24,24 +24,51 @@ export function compareRows(left: ProblemRow, right: ProblemRow): number {
|
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/* One report of a file's problems; `path` is a stored path, as `ProblemFile.path` is. */
|
|
28
|
+
export interface ProblemReport {
|
|
29
|
+
readonly path: string;
|
|
30
|
+
readonly server: string;
|
|
31
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Hands every report to `listener` until the function it returns runs. */
|
|
35
|
+
export type ProblemSource = (listener: (report: ProblemReport) => void) => () => void;
|
|
36
|
+
|
|
37
|
+
/* The diagnostics a service reports, by the stored path `pathOf` gives each URI. */
|
|
38
|
+
export function serviceProblems(service: LanguageService, pathOf: (uri: string) => string = (uri) => uri): ProblemSource {
|
|
39
|
+
return (listener) => {
|
|
40
|
+
const subscription = service.onDiagnostics((report) => listener({ path: pathOf(report.uri), server: report.source, diagnostics: report.diagnostics }));
|
|
41
|
+
return () => subscription.dispose();
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
27
45
|
/*
|
|
28
46
|
* Each report replaces the previous diagnostics from that source for the file. Files with no
|
|
29
47
|
* remaining problems drop out. Hints remain editor annotations rather than listed problems.
|
|
30
48
|
*/
|
|
31
49
|
export class ProjectProblems {
|
|
32
50
|
private readonly reports = new Map<string, Map<string, readonly Diagnostic[]>>();
|
|
51
|
+
private readonly files = new Map<string, ProblemFile>();
|
|
33
52
|
private readonly listeners = new Set<() => void>();
|
|
34
53
|
private readonly stop: () => void;
|
|
35
|
-
|
|
54
|
+
/* The paths of `files` in order; null once a file came or went. */
|
|
55
|
+
private order: string[] | null = [];
|
|
56
|
+
/* Made on first read after a report, so a project nobody lists does no sorting. */
|
|
57
|
+
private snapshot: readonly ProblemFile[] | null = [];
|
|
36
58
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
/* A service stands for `serviceProblems(service, pathOf)`. */
|
|
60
|
+
constructor(source: ProblemSource | LanguageService, pathOf?: (uri: string) => string) {
|
|
61
|
+
const listen = typeof source === 'function' ? source : serviceProblems(source, pathOf);
|
|
62
|
+
this.stop = listen((report) => this.report(report.path, report.server, report.diagnostics));
|
|
40
63
|
}
|
|
41
64
|
|
|
42
65
|
report(path: string, server: string, diagnostics: readonly Diagnostic[]): void {
|
|
43
66
|
const kept = diagnostics.filter((diagnostic) => severityOf(diagnostic) !== 'hint');
|
|
44
67
|
const servers = this.reports.get(path) ?? new Map<string, readonly Diagnostic[]>();
|
|
68
|
+
if (kept.length === 0 && !servers.has(server)) {
|
|
69
|
+
// A clean file reported clean again, as servers do after every edit.
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
45
72
|
if (kept.length === 0) {
|
|
46
73
|
servers.delete(server);
|
|
47
74
|
} else {
|
|
@@ -49,22 +76,30 @@ export class ProjectProblems {
|
|
|
49
76
|
}
|
|
50
77
|
if (servers.size === 0) {
|
|
51
78
|
this.reports.delete(path);
|
|
79
|
+
this.files.delete(path);
|
|
80
|
+
this.order = null;
|
|
52
81
|
} else {
|
|
82
|
+
if (!this.files.has(path)) {
|
|
83
|
+
this.order = null;
|
|
84
|
+
}
|
|
53
85
|
this.reports.set(path, servers);
|
|
86
|
+
const rows = [...servers].flatMap(([name, list]) => list.map((diagnostic): ProblemRow => ({ diagnostic, server: name }))).sort(compareRows);
|
|
87
|
+
this.files.set(path, { path, rows });
|
|
54
88
|
}
|
|
55
|
-
this.snapshot =
|
|
56
|
-
.map(([file, byServer]): ProblemFile => ({
|
|
57
|
-
path: file,
|
|
58
|
-
rows: [...byServer].flatMap(([name, list]) => list.map((diagnostic): ProblemRow => ({ diagnostic, server: name }))).sort(compareRows)
|
|
59
|
-
}))
|
|
60
|
-
.sort((left, right) => left.path.localeCompare(right.path));
|
|
89
|
+
this.snapshot = null;
|
|
61
90
|
for (const listener of [...this.listeners]) {
|
|
62
91
|
listener();
|
|
63
92
|
}
|
|
64
93
|
}
|
|
65
94
|
|
|
66
95
|
/* The same array until the next report, which is what a store a component reads must hand out. */
|
|
67
|
-
getSnapshot = (): readonly ProblemFile[] =>
|
|
96
|
+
getSnapshot = (): readonly ProblemFile[] => {
|
|
97
|
+
if (this.snapshot === null) {
|
|
98
|
+
this.order ??= [...this.files.keys()].sort((left, right) => left.localeCompare(right));
|
|
99
|
+
this.snapshot = this.order.map((path) => this.files.get(path)!);
|
|
100
|
+
}
|
|
101
|
+
return this.snapshot;
|
|
102
|
+
};
|
|
68
103
|
|
|
69
104
|
subscribe = (listener: () => void): (() => void) => {
|
|
70
105
|
this.listeners.add(listener);
|
package/src/proposal-model.ts
CHANGED
|
@@ -139,15 +139,6 @@ export function fitReplacement(selected: string, replacement: string): string {
|
|
|
139
139
|
return breaks === '\n' ? fitted : fitted.replace(/\n/g, breaks);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
/* Where the caret stands after `text` was typed at `start`. */
|
|
143
|
-
export function endOfInsertion(start: EditorPosition, text: string): EditorPosition {
|
|
144
|
-
const lines = text.split('\n');
|
|
145
|
-
if (lines.length === 1) {
|
|
146
|
-
return { line: start.line, character: start.character + text.length };
|
|
147
|
-
}
|
|
148
|
-
return { line: start.line + lines.length - 1, character: lines.at(-1)!.length };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
142
|
/* One stretch of the proposal as the card draws it. */
|
|
152
143
|
export interface DiffSegment {
|
|
153
144
|
readonly kind: 'same' | 'added' | 'removed';
|
package/src/rename-model.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
|
-
import type { Location, PrepareRenameResult, TextEdit, WorkspaceEdit } from '@adecore/lsp';
|
|
2
|
+
import type { DocumentSymbol, DocumentSymbolResult, Location, PrepareRenameResult, TextEdit, WorkspaceEdit } from '@adecore/lsp';
|
|
3
3
|
import { isIdentifierCharacter } from './completion-model.ts';
|
|
4
|
+
import { rangeHolds } from './diagnostics-model.ts';
|
|
4
5
|
import { textEntriesOf } from './workspace-edit.ts';
|
|
5
6
|
|
|
6
7
|
/* The identifier the caret is in or touches; null where there is none. */
|
|
@@ -59,6 +60,64 @@ export function occurrencesOf(locations: readonly Location[], uri: string): Occu
|
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
/* LSP's SymbolKind in words, indexed by its number. */
|
|
64
|
+
const SYMBOL_KINDS = [
|
|
65
|
+
'',
|
|
66
|
+
'file',
|
|
67
|
+
'module',
|
|
68
|
+
'namespace',
|
|
69
|
+
'package',
|
|
70
|
+
'class',
|
|
71
|
+
'method',
|
|
72
|
+
'property',
|
|
73
|
+
'field',
|
|
74
|
+
'constructor',
|
|
75
|
+
'enum',
|
|
76
|
+
'interface',
|
|
77
|
+
'function',
|
|
78
|
+
'variable',
|
|
79
|
+
'constant',
|
|
80
|
+
'string',
|
|
81
|
+
'number',
|
|
82
|
+
'boolean',
|
|
83
|
+
'array',
|
|
84
|
+
'object',
|
|
85
|
+
'key',
|
|
86
|
+
'null',
|
|
87
|
+
'enum member',
|
|
88
|
+
'struct',
|
|
89
|
+
'event',
|
|
90
|
+
'operator',
|
|
91
|
+
'type parameter'
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* The kind of the symbol a rename names, in words, from the outline of the open file: the symbol whose name
|
|
96
|
+
* holds one of the places of the name there. Undefined when the outline does not list it, as with most locals.
|
|
97
|
+
*/
|
|
98
|
+
export function renameKindOf(result: DocumentSymbolResult, name: string, places: readonly EditorRange[]): string | undefined {
|
|
99
|
+
if (result === null || result.length === 0) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const named = (range: EditorRange) => places.some((place) => rangeHolds(range, place.start));
|
|
103
|
+
if ('range' in result[0]!) {
|
|
104
|
+
let found: DocumentSymbol | undefined;
|
|
105
|
+
const walk = (symbols: readonly DocumentSymbol[]): void => {
|
|
106
|
+
for (const symbol of symbols) {
|
|
107
|
+
if (named(symbol.selectionRange)) {
|
|
108
|
+
found = symbol;
|
|
109
|
+
}
|
|
110
|
+
walk(symbol.children ?? []);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
walk(result as DocumentSymbol[]);
|
|
114
|
+
return found === undefined ? undefined : SYMBOL_KINDS[found.kind];
|
|
115
|
+
}
|
|
116
|
+
// A flat list only gives the whole span of a symbol, so the name has to match as well.
|
|
117
|
+
const found = result.find((symbol) => 'location' in symbol && symbol.name === name && named(symbol.location.range));
|
|
118
|
+
return found === undefined ? undefined : SYMBOL_KINDS[found.kind];
|
|
119
|
+
}
|
|
120
|
+
|
|
62
121
|
/* One line a rename changes: the line as it is and as it will be. */
|
|
63
122
|
export interface RenameRow {
|
|
64
123
|
/* One-based. */
|
package/src/rename.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { StaleResultError, type Location, type PrepareRenameResult, type WorkspaceEdit } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { EditorRange } from '@adecore/editor';
|
|
3
4
|
import type { EditorLanguage } from './editor-language.ts';
|
|
4
5
|
import { lineTextOf } from './line-text.ts';
|
|
5
6
|
import type { RenameView } from './popups.ts';
|
|
6
|
-
import { occurrencesOf, renamePreviewOf, renameTargetOf, wordRangeAt, type RenameTarget } from './rename-model.ts';
|
|
7
|
+
import { occurrencesOf, renameKindOf, renamePreviewOf, renameTargetOf, wordRangeAt, type RenameTarget } from './rename-model.ts';
|
|
7
8
|
import { isShortcut } from './shortcut-keys.ts';
|
|
8
9
|
import { resourceOperationsOf } from './workspace-edit.ts';
|
|
9
10
|
import { messageOf } from './error-message.ts';
|
|
@@ -37,13 +38,13 @@ export class RenameFeature {
|
|
|
37
38
|
this.language = language;
|
|
38
39
|
const { editor } = language;
|
|
39
40
|
const offs = [
|
|
40
|
-
|
|
41
|
+
language.onKey((event) => {
|
|
41
42
|
if (!isShortcut(this.language.shortcuts.rename, event)) {
|
|
42
43
|
return false;
|
|
43
44
|
}
|
|
44
45
|
void this.start();
|
|
45
46
|
return true;
|
|
46
|
-
}),
|
|
47
|
+
}, KEY_PRIORITY.rename),
|
|
47
48
|
editor.onTextChange(() => this.cancel())
|
|
48
49
|
];
|
|
49
50
|
language.onDispose(() => {
|
|
@@ -218,8 +219,9 @@ export class RenameFeature {
|
|
|
218
219
|
start: { line: Math.max(0, line - CONTEXT_LINES), character: 0 },
|
|
219
220
|
end: { line: line + CONTEXT_LINES, character: Number.MAX_SAFE_INTEGER }
|
|
220
221
|
});
|
|
221
|
-
const uses = await this.linesOf(locations ?? []);
|
|
222
|
-
const
|
|
222
|
+
const [uses, kind] = await Promise.all([this.linesOf(locations ?? []), this.kindOf(session, locations ?? [])]);
|
|
223
|
+
const request = { uri, languageId, name: session.original, ...(kind === undefined ? {} : { kind }), context, uses };
|
|
224
|
+
const names = await suggestNames(request, session.names.signal);
|
|
223
225
|
if (this.session === session && names.length > 0) {
|
|
224
226
|
this.publish({ suggestions: names });
|
|
225
227
|
}
|
|
@@ -228,6 +230,20 @@ export class RenameFeature {
|
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
232
|
|
|
233
|
+
/* 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. */
|
|
234
|
+
private async kindOf(session: Session, locations: readonly Location[]): Promise<string | undefined> {
|
|
235
|
+
const { project, uri } = this.language;
|
|
236
|
+
if (!project.service.supports('textDocument/documentSymbol', uri)) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const symbols = await project.service.documentSymbols(uri, { signal: session.names.signal, parallel: true });
|
|
241
|
+
return renameKindOf(symbols, session.original, [session.target.range, ...occurrencesOf(locations, uri).inFile]);
|
|
242
|
+
} catch {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
231
247
|
/* The text of the first lines that name the symbol, in the order the servers listed them, reading at most a few other files. */
|
|
232
248
|
private async linesOf(locations: readonly Location[]): Promise<string[]> {
|
|
233
249
|
const { project } = this.language;
|
package/src/rendered-docs.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { findDocComments, type DocComment, type EditorPosition, type EditorRange, type EditorReplacement, type EditorTrackedRange } from '@adecore/editor';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
3
4
|
import { parseDocComment, type ParsedDocComment } from './doc-comment-model.ts';
|
|
4
5
|
import { isShortcut } from './shortcut-keys.ts';
|
|
@@ -16,6 +17,8 @@ const MAX_PARSED = 200;
|
|
|
16
17
|
export interface RenderedDocsSettings {
|
|
17
18
|
/* Draw every documentation comment as formatted text, as the Reader mode of the documentation does. Off by default. */
|
|
18
19
|
readonly renderDocComments: boolean;
|
|
20
|
+
/* Off draws no buttons, ignores the shortcut and shows every comment as source; on unless false. */
|
|
21
|
+
readonly enabled?: boolean;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/* A documentation comment the editor drew a row for, and the element of that row. */
|
|
@@ -69,6 +72,7 @@ export class RenderedDocsFeature {
|
|
|
69
72
|
private readonly language: EditorLanguage;
|
|
70
73
|
private readonly timers: Timers;
|
|
71
74
|
private settings: RenderedDocsSettings = { renderDocComments: false };
|
|
75
|
+
private enabled = true;
|
|
72
76
|
private comments: DocComment[] = [];
|
|
73
77
|
private overrides: Override[] = [];
|
|
74
78
|
private readonly containers = new Map<string, HTMLElement>();
|
|
@@ -111,9 +115,9 @@ export class RenderedDocsFeature {
|
|
|
111
115
|
this.toggleAt(line);
|
|
112
116
|
}
|
|
113
117
|
}),
|
|
114
|
-
|
|
118
|
+
language.onKey((event) => {
|
|
115
119
|
return isShortcut(this.language.shortcuts.toggleRenderedDocs, event) && this.toggle();
|
|
116
|
-
}),
|
|
120
|
+
}, KEY_PRIORITY.renderedDocs),
|
|
117
121
|
editor.onViewChange(() => {
|
|
118
122
|
this.sweep();
|
|
119
123
|
this.releaseRevealed();
|
|
@@ -140,11 +144,16 @@ export class RenderedDocsFeature {
|
|
|
140
144
|
|
|
141
145
|
/* Whether every documentation comment is drawn as text until a person switches it. Changing it puts every comment back under it. */
|
|
142
146
|
configure(settings: RenderedDocsSettings): void {
|
|
143
|
-
|
|
147
|
+
const enabled = settings.enabled !== false;
|
|
148
|
+
if (settings.renderDocComments === this.settings.renderDocComments && enabled === this.enabled) {
|
|
144
149
|
return;
|
|
145
150
|
}
|
|
151
|
+
const sameRendering = settings.renderDocComments === this.settings.renderDocComments;
|
|
146
152
|
this.settings = settings;
|
|
147
|
-
this.
|
|
153
|
+
this.enabled = enabled;
|
|
154
|
+
if (!sameRendering) {
|
|
155
|
+
this.clearOverrides();
|
|
156
|
+
}
|
|
148
157
|
this.publish();
|
|
149
158
|
}
|
|
150
159
|
|
|
@@ -162,7 +171,7 @@ export class RenderedDocsFeature {
|
|
|
162
171
|
toggle(): boolean {
|
|
163
172
|
this.settle();
|
|
164
173
|
const comment = this.at(this.language.editor.getCaret().line);
|
|
165
|
-
if (comment === undefined || !this.canRender(comment)) {
|
|
174
|
+
if (!this.enabled || comment === undefined || !this.canRender(comment)) {
|
|
166
175
|
return false;
|
|
167
176
|
}
|
|
168
177
|
this.setRendered(comment, !this.wantsRendered(comment));
|
|
@@ -173,7 +182,7 @@ export class RenderedDocsFeature {
|
|
|
173
182
|
toggleAt(startLine: number): boolean {
|
|
174
183
|
this.settle();
|
|
175
184
|
const comment = this.comments.find((candidate) => candidate.startLine === startLine);
|
|
176
|
-
if (comment === undefined || !this.canRender(comment)) {
|
|
185
|
+
if (!this.enabled || comment === undefined || !this.canRender(comment)) {
|
|
177
186
|
return false;
|
|
178
187
|
}
|
|
179
188
|
this.setRendered(comment, !this.wantsRendered(comment));
|
|
@@ -225,7 +234,7 @@ export class RenderedDocsFeature {
|
|
|
225
234
|
}
|
|
226
235
|
|
|
227
236
|
private wantsRendered(comment: DocComment): boolean {
|
|
228
|
-
if (!this.canRender(comment)) {
|
|
237
|
+
if (!this.enabled || !this.canRender(comment)) {
|
|
229
238
|
return false;
|
|
230
239
|
}
|
|
231
240
|
const start = startOf(comment);
|
|
@@ -375,7 +384,7 @@ export class RenderedDocsFeature {
|
|
|
375
384
|
this.bump();
|
|
376
385
|
}
|
|
377
386
|
this.markerLines.clear();
|
|
378
|
-
const markers = this.comments.map((comment) => {
|
|
387
|
+
const markers = (this.enabled ? this.comments : []).map((comment) => {
|
|
379
388
|
const id = `docs:${comment.startLine}`;
|
|
380
389
|
this.markerLines.set(id, comment.startLine);
|
|
381
390
|
const on = this.wantsRendered(comment);
|
package/src/signature.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SignatureHelp, SignatureHelpContext } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import type { EditorPosition, EditorTextChange } from '@adecore/editor';
|
|
3
4
|
import type { EditorLanguage } from './editor-language.ts';
|
|
4
5
|
import { signatureViewOf } from './signature-model.ts';
|
|
@@ -30,8 +31,8 @@ export class SignatureFeature {
|
|
|
30
31
|
editor.onTextChange((change) => this.edited(change)),
|
|
31
32
|
editor.onCaret(() => this.retrigger()),
|
|
32
33
|
editor.onBlur(() => this.close()),
|
|
33
|
-
|
|
34
|
-
if (event.key === 'Escape' && this.isOpen && this.language.popups.getState().callHierarchy
|
|
34
|
+
language.onKey((event) => {
|
|
35
|
+
if (event.key === 'Escape' && this.isOpen && this.language.popups.getState().callHierarchy === null) {
|
|
35
36
|
this.close();
|
|
36
37
|
return true;
|
|
37
38
|
}
|
|
@@ -43,7 +44,7 @@ export class SignatureFeature {
|
|
|
43
44
|
return this.cycle(event.key === 'ArrowDown' ? 1 : -1);
|
|
44
45
|
}
|
|
45
46
|
return false;
|
|
46
|
-
})
|
|
47
|
+
}, KEY_PRIORITY.signature)
|
|
47
48
|
];
|
|
48
49
|
language.onDispose(() => {
|
|
49
50
|
for (const off of offs) {
|
package/src/snippet-session.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { SnippetFieldTransforms, type FieldTransformTarget } from './snippet-fie
|
|
|
3
3
|
import { SnippetTransformError } from './snippet-transform-client.ts';
|
|
4
4
|
import { acquireSnippetTransforms, type SnippetTransformLease } from './snippet-transform-queue.ts';
|
|
5
5
|
import { realTimers, type Timers } from './timers.ts';
|
|
6
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
6
7
|
import { MAX_TRANSFORM_CHARACTERS, type SnippetTransformJob } from './snippet-transform-protocol.ts';
|
|
7
8
|
import type { EditorContentChange, EditorPosition, EditorRange, EditorTextChange } from '@adecore/editor';
|
|
8
9
|
import { comparePositions, rangeHolds, shiftPosition } from './diagnostics-model.ts';
|
|
@@ -79,7 +80,7 @@ export interface SnippetInsertion {
|
|
|
79
80
|
readonly fields?: readonly SnippetField[];
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
type SnippetHost = Pick<EditorLanguage, 'editor' | 'onDispose'> & Partial<Pick<EditorLanguage, 'popups' | 'pick' | 'project'>>;
|
|
83
|
+
type SnippetHost = Pick<EditorLanguage, 'editor' | 'onDispose'> & Partial<Pick<EditorLanguage, 'popups' | 'pick' | 'project' | 'onKey'>>;
|
|
83
84
|
const MAX_FRAMES = 8;
|
|
84
85
|
const MAX_RANGES = 1000;
|
|
85
86
|
|
|
@@ -185,7 +186,12 @@ export class SnippetFeature {
|
|
|
185
186
|
this.transforms = language.project === undefined ? null : acquireSnippetTransforms(language.project, timers);
|
|
186
187
|
const { editor } = language;
|
|
187
188
|
this.fieldTransforms = new SnippetFieldTransforms(editor, this.transforms, (targets, values) => this.applyFieldTransforms(targets, values));
|
|
188
|
-
const
|
|
189
|
+
const key = (event: KeyboardEvent): boolean => this.key(event);
|
|
190
|
+
const offs = [
|
|
191
|
+
language.onKey === undefined ? editor.onKeyDown(key) : language.onKey(key, KEY_PRIORITY.snippets),
|
|
192
|
+
editor.onTextChange((change) => this.edited(change)),
|
|
193
|
+
editor.onCaret(() => this.moved())
|
|
194
|
+
];
|
|
189
195
|
language.onDispose(() => {
|
|
190
196
|
this.transforms?.dispose();
|
|
191
197
|
this.end();
|
|
@@ -492,7 +498,7 @@ export class SnippetFeature {
|
|
|
492
498
|
const popups = this.language.popups?.getState();
|
|
493
499
|
if (
|
|
494
500
|
popups !== undefined &&
|
|
495
|
-
[popups.completion, popups.signature, popups.hover, popups.pick, popups.peek, popups.callHierarchy
|
|
501
|
+
[popups.completion, popups.signature, popups.hover, popups.pick, popups.peek, popups.callHierarchy].some((popup) => popup !== null)
|
|
496
502
|
) {
|
|
497
503
|
return false;
|
|
498
504
|
}
|
package/src/symbol-picker.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StaleResultError, type Location } from '@adecore/lsp';
|
|
2
|
+
import { KEY_PRIORITY } from './key-priority.ts';
|
|
2
3
|
import { basenameOf } from './paths.ts';
|
|
3
4
|
import type { EditorLanguage } from './editor-language.ts';
|
|
4
5
|
import { entriesOf, workspaceEntriesOf, type SymbolEntry, type WorkspaceEntry } from './symbol-picker-model.ts';
|
|
@@ -17,13 +18,13 @@ export class SymbolPickerFeature {
|
|
|
17
18
|
|
|
18
19
|
constructor(language: EditorLanguage) {
|
|
19
20
|
this.language = language;
|
|
20
|
-
const off = language.
|
|
21
|
+
const off = language.onKey((event) => {
|
|
21
22
|
if (!isShortcut(this.language.shortcuts.goToSymbol, event)) {
|
|
22
23
|
return false;
|
|
23
24
|
}
|
|
24
25
|
void this.open();
|
|
25
26
|
return true;
|
|
26
|
-
});
|
|
27
|
+
}, KEY_PRIORITY.symbolPicker);
|
|
27
28
|
language.onDispose(() => {
|
|
28
29
|
off();
|
|
29
30
|
this.close();
|
package/src/timers.ts
CHANGED
|
@@ -43,3 +43,8 @@ export class ManualTimers implements Timers {
|
|
|
43
43
|
return this.pending.size;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
/* 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. */
|
|
48
|
+
export function revealRow(timers: Timers, container: HTMLElement): void {
|
|
49
|
+
timers.set(() => container.scrollIntoView?.({ block: 'nearest' }), 0);
|
|
50
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import { clampPanelBody } from './panel-size.ts';
|
|
4
|
+
|
|
5
|
+
/* The body height of a panel, from the project that remembers it for the next one; `commit` hands it to the host. */
|
|
6
|
+
export function usePanelBody(language: EditorLanguage): { height: number; set(height: number): void; commit(height: number): void } {
|
|
7
|
+
const { project } = language;
|
|
8
|
+
const [height, set] = useState(() => clampPanelBody(project.panelBodyHeight));
|
|
9
|
+
|
|
10
|
+
useEffect(() => project.onPanelBodyChange((value) => set(clampPanelBody(value))), [project]);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
height,
|
|
14
|
+
set,
|
|
15
|
+
commit: (value) => project.resizePanelBody(value)
|
|
16
|
+
};
|
|
17
|
+
}
|
package/dist/relative-time.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function relativeTime(at: number, now: number): string;
|
package/dist/relative-time.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { formatAgo, formatDay, formatDayWithYear } from '@adecore/ui/format';
|
|
2
|
-
export function relativeTime(at, now) {
|
|
3
|
-
const seconds = Math.max(0, now - at);
|
|
4
|
-
if (seconds < 7 * 24 * 60 * 60) {
|
|
5
|
-
return formatAgo(seconds * 1000);
|
|
6
|
-
}
|
|
7
|
-
const date = new Date(at * 1000);
|
|
8
|
-
return date.getFullYear() === new Date(now * 1000).getFullYear() ? formatDay(date) : formatDayWithYear(date);
|
|
9
|
-
}
|
package/src/relative-time.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { formatAgo, formatDay, formatDayWithYear } from '@adecore/ui/format';
|
|
2
|
-
|
|
3
|
-
export function relativeTime(at: number, now: number): string {
|
|
4
|
-
const seconds = Math.max(0, now - at);
|
|
5
|
-
if (seconds < 7 * 24 * 60 * 60) {
|
|
6
|
-
return formatAgo(seconds * 1000);
|
|
7
|
-
}
|
|
8
|
-
const date = new Date(at * 1000);
|
|
9
|
-
return date.getFullYear() === new Date(now * 1000).getFullYear() ? formatDay(date) : formatDayWithYear(date);
|
|
10
|
-
}
|