@adecore/editor-react 0.22.1 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CallHierarchyPanel.d.ts +6 -0
- package/dist/CallHierarchyPanel.js +92 -0
- package/dist/CompletionPopup.js +11 -3
- package/dist/DiffView.d.ts +22 -0
- package/dist/DiffView.js +203 -0
- package/dist/EditorContextMenu.js +1 -0
- package/dist/EditorView.js +18 -1
- package/dist/FindReplace.d.ts +1 -1
- package/dist/FindReplace.js +41 -11
- package/dist/HoverCard.js +2 -2
- package/dist/LanguagePopups.js +4 -1
- package/dist/Markdown.d.ts +2 -1
- package/dist/Markdown.js +8 -4
- package/dist/PanelResizeHandle.d.ts +8 -0
- package/dist/PanelResizeHandle.js +71 -0
- package/dist/PeekPanel.d.ts +3 -0
- package/dist/PeekPanel.js +7 -4
- package/dist/PickPopup.js +15 -2
- package/dist/RenameCard.js +10 -5
- package/dist/RenderedDocs.d.ts +9 -0
- package/dist/RenderedDocs.js +84 -0
- package/dist/SplitDiffView.d.ts +9 -0
- package/dist/SplitDiffView.js +276 -0
- package/dist/SymbolPicker.d.ts +3 -0
- package/dist/SymbolPicker.js +4 -3
- package/dist/call-hierarchy-model.d.ts +52 -0
- package/dist/call-hierarchy-model.js +93 -0
- package/dist/call-hierarchy.d.ts +42 -0
- package/dist/call-hierarchy.js +398 -0
- package/dist/code-actions-model.d.ts +2 -0
- package/dist/code-actions-model.js +12 -10
- package/dist/code-actions.js +3 -1
- package/dist/completion-model.d.ts +14 -4
- package/dist/completion-model.js +40 -33
- package/dist/completion.d.ts +4 -0
- package/dist/completion.js +257 -42
- package/dist/diagnostics.js +8 -6
- package/dist/diff-expansion.d.ts +9 -0
- package/dist/diff-expansion.js +41 -0
- package/dist/diff-overlays.d.ts +15 -0
- package/dist/diff-overlays.js +28 -0
- package/dist/diff-syntax.d.ts +16 -0
- package/dist/diff-syntax.js +49 -0
- package/dist/diff-wordmarks.d.ts +5 -0
- package/dist/diff-wordmarks.js +26 -0
- package/dist/doc-comment-model.d.ts +23 -0
- package/dist/doc-comment-model.js +291 -0
- package/dist/editor-language.d.ts +13 -1
- package/dist/editor-language.js +15 -5
- package/dist/editor-react.css +79 -0
- package/dist/format-on-type.d.ts +14 -0
- package/dist/format-on-type.js +96 -0
- package/dist/host-types.d.ts +19 -0
- package/dist/hover.d.ts +4 -0
- package/dist/hover.js +15 -2
- package/dist/index.d.ts +19 -5
- package/dist/index.js +9 -1
- package/dist/inlay-hints.d.ts +47 -2
- package/dist/inlay-hints.js +353 -16
- package/dist/language-ids.js +4 -2
- package/dist/locales/en.json +117 -3
- package/dist/locales/nl.json +117 -3
- package/dist/models.d.ts +6 -2
- package/dist/models.js +5 -1
- package/dist/panel-size.d.ts +6 -0
- package/dist/panel-size.js +25 -0
- package/dist/patch-decorations.d.ts +27 -0
- package/dist/patch-decorations.js +182 -0
- package/dist/patch-layout.d.ts +40 -0
- package/dist/patch-layout.js +304 -0
- package/dist/peek.d.ts +3 -1
- package/dist/peek.js +6 -1
- package/dist/pick.d.ts +1 -0
- package/dist/pick.js +11 -1
- package/dist/popups.d.ts +17 -0
- package/dist/popups.js +1 -0
- package/dist/project-language.d.ts +12 -0
- package/dist/project-language.js +52 -2
- package/dist/refresher.d.ts +1 -0
- package/dist/refresher.js +5 -0
- package/dist/rename-model.d.ts +1 -1
- package/dist/rename-model.js +3 -7
- package/dist/rename.js +5 -8
- package/dist/rendered-docs.d.ts +64 -0
- package/dist/rendered-docs.js +374 -0
- package/dist/row-host.d.ts +2 -0
- package/dist/row-host.js +11 -3
- package/dist/semantic-delta.d.ts +2 -0
- package/dist/semantic-delta.js +27 -0
- package/dist/semantic-model.js +16 -0
- package/dist/semantic-tokens.d.ts +26 -1
- package/dist/semantic-tokens.js +174 -14
- package/dist/shortcut-keys.d.ts +2 -0
- package/dist/shortcut-keys.js +6 -2
- package/dist/signature.js +1 -1
- package/dist/snippet-field-transforms.d.ts +30 -0
- package/dist/snippet-field-transforms.js +116 -0
- package/dist/snippet-indent.d.ts +2 -0
- package/dist/snippet-indent.js +49 -0
- package/dist/snippet-range-order.d.ts +3 -0
- package/dist/snippet-range-order.js +19 -0
- package/dist/snippet-session.d.ts +42 -6
- package/dist/snippet-session.js +650 -53
- package/dist/snippet-transform-client.d.ts +19 -0
- package/dist/snippet-transform-client.js +66 -0
- package/dist/snippet-transform-format.d.ts +15 -0
- package/dist/snippet-transform-format.js +302 -0
- package/dist/snippet-transform-protocol.d.ts +45 -0
- package/dist/snippet-transform-protocol.js +8 -0
- package/dist/snippet-transform-queue.d.ts +32 -0
- package/dist/snippet-transform-queue.js +173 -0
- package/dist/snippet-transform-worker.d.ts +10 -0
- package/dist/snippet-transform-worker.js +41 -0
- package/dist/snippet-variable-names.d.ts +1 -0
- package/dist/snippet-variable-names.js +36 -0
- package/dist/snippet-variables.d.ts +7 -0
- package/dist/snippet-variables.js +66 -0
- package/dist/snippet.d.ts +30 -3
- package/dist/snippet.js +402 -87
- package/dist/split-alignment.d.ts +6 -0
- package/dist/split-alignment.js +38 -0
- package/dist/split-scroll.d.ts +14 -0
- package/dist/split-scroll.js +45 -0
- package/dist/symbols.d.ts +2 -1
- package/dist/symbols.js +39 -7
- package/dist/testing.d.ts +5 -1
- package/dist/testing.js +12 -0
- 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/dist/workspace-edit.d.ts +25 -3
- package/dist/workspace-edit.js +282 -143
- package/package.json +10 -5
- package/src/CallHierarchyPanel.tsx +244 -0
- package/src/CompletionPopup.tsx +12 -1
- package/src/DiffView.tsx +262 -0
- package/src/EditorContextMenu.tsx +1 -0
- package/src/EditorView.tsx +21 -2
- package/src/FindReplace.tsx +51 -13
- package/src/HoverCard.tsx +23 -21
- package/src/LanguagePopups.tsx +5 -0
- package/src/Markdown.tsx +10 -3
- package/src/PanelResizeHandle.tsx +104 -0
- package/src/PeekPanel.tsx +11 -6
- package/src/PickPopup.tsx +18 -2
- package/src/RenameCard.tsx +18 -0
- package/src/RenderedDocs.tsx +155 -0
- package/src/SplitDiffView.tsx +337 -0
- package/src/SymbolPicker.tsx +4 -3
- package/src/call-hierarchy-model.ts +151 -0
- package/src/call-hierarchy.ts +440 -0
- package/src/code-actions-model.ts +17 -11
- package/src/code-actions.ts +3 -1
- package/src/completion-model.ts +61 -35
- package/src/completion.ts +302 -42
- package/src/diagnostics.ts +8 -6
- package/src/diff-expansion.ts +49 -0
- package/src/diff-overlays.tsx +69 -0
- package/src/diff-syntax.ts +67 -0
- package/src/diff-wordmarks.ts +31 -0
- package/src/doc-comment-model.ts +336 -0
- package/src/editor-language.ts +22 -6
- package/src/editor-react.css +79 -0
- package/src/format-on-type.ts +111 -0
- package/src/host-types.ts +23 -0
- package/src/hover.ts +18 -3
- package/src/index.ts +27 -4
- package/src/inlay-hints.ts +394 -18
- package/src/language-ids.ts +4 -2
- package/src/locales/en.json +117 -3
- package/src/locales/nl.json +117 -3
- package/src/models.ts +40 -1
- package/src/panel-size.ts +28 -0
- package/src/patch-decorations.ts +224 -0
- package/src/patch-layout.ts +388 -0
- package/src/peek.ts +6 -1
- package/src/pick.ts +12 -1
- package/src/popups.ts +24 -0
- package/src/project-language.ts +63 -2
- package/src/refresher.ts +6 -0
- package/src/rename-model.ts +4 -8
- package/src/rename.ts +5 -8
- package/src/rendered-docs.ts +430 -0
- package/src/row-host.ts +12 -3
- package/src/semantic-delta.ts +29 -0
- package/src/semantic-model.ts +17 -0
- package/src/semantic-tokens.ts +202 -19
- package/src/shortcut-keys.ts +6 -2
- package/src/signature.ts +1 -1
- package/src/snippet-field-transforms.ts +144 -0
- package/src/snippet-indent.ts +53 -0
- package/src/snippet-range-order.ts +19 -0
- package/src/snippet-session.ts +797 -56
- package/src/snippet-transform-client.ts +84 -0
- package/src/snippet-transform-format.ts +325 -0
- package/src/snippet-transform-protocol.ts +24 -0
- package/src/snippet-transform-queue.ts +199 -0
- package/src/snippet-transform-worker.ts +58 -0
- package/src/snippet-variable-names.ts +36 -0
- package/src/snippet-variables.ts +74 -0
- package/src/snippet.ts +452 -89
- package/src/split-alignment.ts +45 -0
- package/src/split-scroll.ts +54 -0
- package/src/symbols.ts +43 -7
- package/src/testing.ts +25 -0
- package/src/timers.ts +5 -0
- package/src/use-panel-body.ts +17 -0
- package/src/workspace-edit.ts +347 -169
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, Range } from '@adecore/lsp';
|
|
2
|
+
export type CallDirection = 'incoming' | 'outgoing';
|
|
3
|
+
export declare const CALL_LEVEL_LIMIT = 50;
|
|
4
|
+
export type CallLoad = 'idle' | 'loading' | 'loaded' | 'failed';
|
|
5
|
+
export interface CallNode {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly parent: string | null;
|
|
8
|
+
readonly level: number;
|
|
9
|
+
readonly item: CallHierarchyItem;
|
|
10
|
+
readonly sites: readonly Range[];
|
|
11
|
+
readonly siteUri: string | null;
|
|
12
|
+
readonly expanded: boolean;
|
|
13
|
+
readonly load: CallLoad;
|
|
14
|
+
readonly error: string | null;
|
|
15
|
+
readonly children: readonly string[];
|
|
16
|
+
readonly shown: number;
|
|
17
|
+
}
|
|
18
|
+
export type CallRow = {
|
|
19
|
+
readonly kind: 'node';
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly level: number;
|
|
22
|
+
readonly item: CallHierarchyItem;
|
|
23
|
+
readonly expanded: boolean;
|
|
24
|
+
readonly load: CallLoad;
|
|
25
|
+
readonly sites: number;
|
|
26
|
+
readonly leaf: boolean;
|
|
27
|
+
readonly position: number;
|
|
28
|
+
readonly size: number;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'status';
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly parent: string;
|
|
33
|
+
readonly level: number;
|
|
34
|
+
readonly status: 'loading' | 'empty' | 'failed';
|
|
35
|
+
readonly message: string | null;
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: 'more';
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly level: number;
|
|
40
|
+
readonly parent: string;
|
|
41
|
+
readonly hidden: number;
|
|
42
|
+
};
|
|
43
|
+
export declare function statusIdOf(parent: string): string;
|
|
44
|
+
export declare function moreIdOf(parent: string): string;
|
|
45
|
+
export declare function rootsOf(items: readonly CallHierarchyItem[]): CallNode[];
|
|
46
|
+
export declare function childrenOf(direction: CallDirection, parent: CallNode, calls: readonly (CallHierarchyIncomingCall | CallHierarchyOutgoingCall)[]): CallNode[];
|
|
47
|
+
export declare function rowsOf(nodes: ReadonlyMap<string, CallNode>, roots: readonly string[]): CallRow[];
|
|
48
|
+
export declare function stepsOf(rows: readonly CallRow[]): CallRow[];
|
|
49
|
+
export declare function targetOf(node: CallNode, site?: number): {
|
|
50
|
+
uri: string;
|
|
51
|
+
range: Range;
|
|
52
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* The most calls of one level that are drawn at once; the rest wait behind a row that shows more. */
|
|
2
|
+
export const CALL_LEVEL_LIMIT = 50;
|
|
3
|
+
/* The id of the status line under a node. */
|
|
4
|
+
export function statusIdOf(parent) {
|
|
5
|
+
return `${parent}:status`;
|
|
6
|
+
}
|
|
7
|
+
/* The id of the line that shows more calls under a node. */
|
|
8
|
+
export function moreIdOf(parent) {
|
|
9
|
+
return `${parent}:more`;
|
|
10
|
+
}
|
|
11
|
+
/* The tree's roots, each one a symbol the server prepared. */
|
|
12
|
+
export function rootsOf(items) {
|
|
13
|
+
return items.map((item, index) => ({
|
|
14
|
+
id: `r${index}`,
|
|
15
|
+
parent: null,
|
|
16
|
+
level: 1,
|
|
17
|
+
item,
|
|
18
|
+
sites: [],
|
|
19
|
+
siteUri: null,
|
|
20
|
+
expanded: true,
|
|
21
|
+
load: 'idle',
|
|
22
|
+
error: null,
|
|
23
|
+
children: [],
|
|
24
|
+
shown: 0
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
/*
|
|
28
|
+
* The children of a node made from the calls a server returned, in the order the server gave them.
|
|
29
|
+
* The call sites of a caller are in its own file; those of a callee are in the file of the node that was asked about.
|
|
30
|
+
*/
|
|
31
|
+
export function childrenOf(direction, parent, calls) {
|
|
32
|
+
const entries = calls.map((call) => ({ item: 'from' in call ? call.from : call.to, sites: call.fromRanges }));
|
|
33
|
+
return entries.map((entry, index) => ({
|
|
34
|
+
id: `${parent.id}.${index}`,
|
|
35
|
+
parent: parent.id,
|
|
36
|
+
level: parent.level + 1,
|
|
37
|
+
item: entry.item,
|
|
38
|
+
sites: entry.sites,
|
|
39
|
+
siteUri: direction === 'incoming' ? entry.item.uri : parent.item.uri,
|
|
40
|
+
expanded: false,
|
|
41
|
+
load: 'idle',
|
|
42
|
+
error: null,
|
|
43
|
+
children: [],
|
|
44
|
+
shown: 0
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
/* The lines the tree draws: every root, and under an expanded node its status, its calls and the row that shows more. */
|
|
48
|
+
export function rowsOf(nodes, roots) {
|
|
49
|
+
const rows = [];
|
|
50
|
+
const visit = (id, position, size) => {
|
|
51
|
+
const node = nodes.get(id);
|
|
52
|
+
rows.push({
|
|
53
|
+
kind: 'node',
|
|
54
|
+
id,
|
|
55
|
+
level: node.level,
|
|
56
|
+
item: node.item,
|
|
57
|
+
expanded: node.expanded,
|
|
58
|
+
load: node.load,
|
|
59
|
+
sites: node.sites.length,
|
|
60
|
+
leaf: node.load === 'loaded' && node.children.length === 0,
|
|
61
|
+
position,
|
|
62
|
+
size
|
|
63
|
+
});
|
|
64
|
+
if (!node.expanded) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (node.load === 'loading') {
|
|
68
|
+
rows.push({ kind: 'status', id: statusIdOf(id), parent: id, level: node.level + 1, status: 'loading', message: null });
|
|
69
|
+
}
|
|
70
|
+
else if (node.load === 'failed') {
|
|
71
|
+
rows.push({ kind: 'status', id: statusIdOf(id), parent: id, level: node.level + 1, status: 'failed', message: node.error });
|
|
72
|
+
}
|
|
73
|
+
else if (node.load === 'loaded' && node.children.length === 0) {
|
|
74
|
+
rows.push({ kind: 'status', id: statusIdOf(id), parent: id, level: node.level + 1, status: 'empty', message: null });
|
|
75
|
+
}
|
|
76
|
+
const shown = node.children.slice(0, node.shown);
|
|
77
|
+
shown.forEach((child, index) => visit(child, index + 1, node.children.length));
|
|
78
|
+
if (node.children.length > node.shown) {
|
|
79
|
+
rows.push({ kind: 'more', id: moreIdOf(id), level: node.level + 1, parent: id, hidden: node.children.length - node.shown });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
roots.forEach((root, index) => visit(root, index + 1, roots.length));
|
|
83
|
+
return rows;
|
|
84
|
+
}
|
|
85
|
+
/* The rows a key can land on: symbols, and the row that shows more. */
|
|
86
|
+
export function stepsOf(rows) {
|
|
87
|
+
return rows.filter((row) => row.kind !== 'status');
|
|
88
|
+
}
|
|
89
|
+
/* The place a node goes to: its first call site, or the symbol itself for a root. */
|
|
90
|
+
export function targetOf(node, site = 0) {
|
|
91
|
+
const range = node.sites[site];
|
|
92
|
+
return range === undefined || node.siteUri === null ? { uri: node.item.uri, range: node.item.selectionRange } : { uri: node.siteUri, range };
|
|
93
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EditorPosition } from '@adecore/editor';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import { type CallDirection } from './call-hierarchy-model.ts';
|
|
4
|
+
import { type Timers } from './timers.ts';
|
|
5
|
+
export declare const CALL_HIERARCHY_HEIGHT: number;
|
|
6
|
+
export declare class CallHierarchyFeature {
|
|
7
|
+
private readonly language;
|
|
8
|
+
private readonly timers;
|
|
9
|
+
private nodes;
|
|
10
|
+
private roots;
|
|
11
|
+
private prepared;
|
|
12
|
+
private direction;
|
|
13
|
+
private controller;
|
|
14
|
+
private epoch;
|
|
15
|
+
private previewEpoch;
|
|
16
|
+
private texts;
|
|
17
|
+
constructor(language: EditorLanguage, timers?: Timers);
|
|
18
|
+
get isOpen(): boolean;
|
|
19
|
+
get supported(): boolean;
|
|
20
|
+
open(position?: EditorPosition, direction?: CallDirection): Promise<void>;
|
|
21
|
+
close(): void;
|
|
22
|
+
setDirection(direction: CallDirection): void;
|
|
23
|
+
toggle(id: string): void;
|
|
24
|
+
retry(id: string): void;
|
|
25
|
+
showMore(parent: string): void;
|
|
26
|
+
select(id: string, site?: number): void;
|
|
27
|
+
stepSite(direction: 1 | -1): void;
|
|
28
|
+
go(id: string): void;
|
|
29
|
+
private reset;
|
|
30
|
+
private seed;
|
|
31
|
+
private mounted;
|
|
32
|
+
private patch;
|
|
33
|
+
private load;
|
|
34
|
+
private preview;
|
|
35
|
+
private hasAncestor;
|
|
36
|
+
private key;
|
|
37
|
+
private land;
|
|
38
|
+
private expandOrEnter;
|
|
39
|
+
private collapseOrLeave;
|
|
40
|
+
private tell;
|
|
41
|
+
private say;
|
|
42
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { StaleResultError } from '@adecore/lsp';
|
|
2
|
+
import { CALL_LEVEL_LIMIT, childrenOf, moreIdOf, rootsOf, rowsOf, statusIdOf, stepsOf, targetOf } from './call-hierarchy-model.js';
|
|
3
|
+
import { PEEK_HEIGHT } from './peek.js';
|
|
4
|
+
import { snippetOf } from './peek-model.js';
|
|
5
|
+
import { isShortcut } from './shortcut-keys.js';
|
|
6
|
+
import { messageOf } from './error-message.js';
|
|
7
|
+
import { realTimers, revealRow } from './timers.js';
|
|
8
|
+
const TOAST_ID = 'language-call-hierarchy';
|
|
9
|
+
const PREPARE = 'textDocument/prepareCallHierarchy';
|
|
10
|
+
/* The row the panel stands in is as tall as a peek's, plus the segmented control that is a pixel taller than the peek's title bar. */
|
|
11
|
+
export const CALL_HIERARCHY_HEIGHT = PEEK_HEIGHT + 2;
|
|
12
|
+
const OWNER = 'call-hierarchy';
|
|
13
|
+
/*
|
|
14
|
+
* The callers of the symbol at the caret, or the symbols it calls, as a tree between the lines of the file
|
|
15
|
+
* under the line the caret is on, with the code around the call that is active. A node asks the server for its
|
|
16
|
+
* own calls when it is opened, and a level shows its first calls with a row that brings the others.
|
|
17
|
+
* The editor keeps the keyboard: arrows move and open, Enter goes to the call, Escape closes, and the
|
|
18
|
+
* shortcut that opened the tree switches between callers and callees. Typing closes it.
|
|
19
|
+
*/
|
|
20
|
+
export class CallHierarchyFeature {
|
|
21
|
+
language;
|
|
22
|
+
timers;
|
|
23
|
+
nodes = new Map();
|
|
24
|
+
roots = [];
|
|
25
|
+
prepared = [];
|
|
26
|
+
direction = 'incoming';
|
|
27
|
+
controller = new AbortController();
|
|
28
|
+
/* Raised whenever the tree starts over or closes, so an answer for the tree that was never lands in the one that is. */
|
|
29
|
+
epoch = 0;
|
|
30
|
+
previewEpoch = 0;
|
|
31
|
+
texts = new Map();
|
|
32
|
+
constructor(language, timers = realTimers) {
|
|
33
|
+
this.language = language;
|
|
34
|
+
this.timers = timers;
|
|
35
|
+
const { editor } = language;
|
|
36
|
+
const offs = [
|
|
37
|
+
editor.onKeyDown((event) => {
|
|
38
|
+
if (isShortcut(this.language.shortcuts.callHierarchy, event)) {
|
|
39
|
+
if (this.isOpen) {
|
|
40
|
+
this.setDirection(this.direction === 'incoming' ? 'outgoing' : 'incoming');
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
void this.open();
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return this.key(event);
|
|
48
|
+
}),
|
|
49
|
+
editor.onTextChange(() => this.close())
|
|
50
|
+
];
|
|
51
|
+
language.onDispose(() => {
|
|
52
|
+
for (const off of offs) {
|
|
53
|
+
off();
|
|
54
|
+
}
|
|
55
|
+
this.close();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
get isOpen() {
|
|
59
|
+
return (this.language.popups.getState().callHierarchy ?? null) !== null;
|
|
60
|
+
}
|
|
61
|
+
/* Whether a server of this document answers the three requests the tree is made of. */
|
|
62
|
+
get supported() {
|
|
63
|
+
const { project, uri } = this.language;
|
|
64
|
+
const { service } = project;
|
|
65
|
+
return (service.prepareCallHierarchy !== undefined &&
|
|
66
|
+
service.incomingCalls !== undefined &&
|
|
67
|
+
service.outgoingCalls !== undefined &&
|
|
68
|
+
service.supports(PREPARE, uri));
|
|
69
|
+
}
|
|
70
|
+
/* Opens the tree for the symbol at the position, on its callers unless told otherwise. */
|
|
71
|
+
async open(position = this.language.editor.getCaret(), direction = 'incoming') {
|
|
72
|
+
const { editor, project, uri } = this.language;
|
|
73
|
+
if (!this.supported) {
|
|
74
|
+
this.tell(this.say('unavailable'));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.close();
|
|
78
|
+
const epoch = this.epoch;
|
|
79
|
+
let items;
|
|
80
|
+
try {
|
|
81
|
+
items = await project.service.prepareCallHierarchy(uri, position, { signal: this.controller.signal });
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (epoch === this.epoch && !(error instanceof StaleResultError)) {
|
|
85
|
+
this.tell(this.say('failed', { message: messageOf(error) }));
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (epoch !== this.epoch) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (items === null || items.length === 0) {
|
|
93
|
+
this.tell(this.say('none'));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.language.peek.close();
|
|
97
|
+
this.prepared = items;
|
|
98
|
+
this.direction = direction;
|
|
99
|
+
this.seed();
|
|
100
|
+
const active = this.roots[0];
|
|
101
|
+
this.language.popups.setState({
|
|
102
|
+
callHierarchy: { direction, container: null, name: items[0].name, rows: rowsOf(this.nodes, this.roots), active, site: 0, sites: 0, preview: null }
|
|
103
|
+
});
|
|
104
|
+
this.select(active);
|
|
105
|
+
// The editor draws a row once per id, so a tree opened over another needs an id of its own to get a container.
|
|
106
|
+
editor.setWidgets([{ id: `call-hierarchy-${epoch}`, line: position.line, height: CALL_HIERARCHY_HEIGHT, render: (container) => this.mounted(container) }], OWNER);
|
|
107
|
+
editor.focus();
|
|
108
|
+
for (const root of this.roots) {
|
|
109
|
+
void this.load(root);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
close() {
|
|
113
|
+
this.reset();
|
|
114
|
+
if (this.language.popups.getState().callHierarchy == null) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.language.popups.setState({ callHierarchy: null });
|
|
118
|
+
this.language.editor.setWidgets([], OWNER);
|
|
119
|
+
this.language.editor.setInputPopup(null);
|
|
120
|
+
this.language.editor.focus();
|
|
121
|
+
}
|
|
122
|
+
/* Starts the tree over from the same symbols for the other direction. */
|
|
123
|
+
setDirection(direction) {
|
|
124
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
125
|
+
if (view === null || view.direction === direction) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.reset();
|
|
129
|
+
this.direction = direction;
|
|
130
|
+
this.seed();
|
|
131
|
+
const active = this.roots[0];
|
|
132
|
+
this.language.popups.setState({
|
|
133
|
+
callHierarchy: { ...view, direction, rows: rowsOf(this.nodes, this.roots), active, site: 0, sites: 0, preview: null }
|
|
134
|
+
});
|
|
135
|
+
this.select(active);
|
|
136
|
+
for (const root of this.roots) {
|
|
137
|
+
void this.load(root);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/* Opens a node, asking the server for its calls the first time, or folds it away. */
|
|
141
|
+
toggle(id) {
|
|
142
|
+
const node = this.nodes.get(id);
|
|
143
|
+
if (node === undefined) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (node.expanded) {
|
|
147
|
+
this.patch(id, { expanded: false });
|
|
148
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
149
|
+
if (view !== null && this.hasAncestor(view.active, id)) {
|
|
150
|
+
this.select(id);
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.patch(id, { expanded: true });
|
|
155
|
+
if (node.load === 'idle' || node.load === 'failed') {
|
|
156
|
+
void this.load(id);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/* Asks again for the calls of a node whose last try failed. */
|
|
160
|
+
retry(id) {
|
|
161
|
+
if (this.nodes.get(id)?.load === 'failed') {
|
|
162
|
+
void this.load(id);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/* Brings the next stretch of calls of a level. */
|
|
166
|
+
showMore(parent) {
|
|
167
|
+
const node = this.nodes.get(parent);
|
|
168
|
+
if (node === undefined) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.patch(parent, { shown: Math.min(node.children.length, node.shown + CALL_LEVEL_LIMIT) });
|
|
172
|
+
const first = node.children[node.shown];
|
|
173
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
174
|
+
// The row that offered more is gone once everything shows, so the selection moves on to what it brought.
|
|
175
|
+
if (first !== undefined && view?.active === moreIdOf(parent)) {
|
|
176
|
+
this.select(first);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/* Makes a row the active one and shows the code of its call. */
|
|
180
|
+
select(id, site = 0) {
|
|
181
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
182
|
+
const node = this.nodes.get(id);
|
|
183
|
+
if (view === null || !view.rows.some((row) => row.id === id)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const sites = node?.sites.length ?? view.sites;
|
|
187
|
+
const next = node === undefined ? view.site : Math.max(0, Math.min(Math.max(0, sites - 1), site));
|
|
188
|
+
this.language.popups.setState({ callHierarchy: { ...view, active: id, site: next, sites, preview: node === undefined ? view.preview : null } });
|
|
189
|
+
if (node !== undefined) {
|
|
190
|
+
void this.preview(node, next);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/* Steps through the places a call is made from when the active one has more than one. */
|
|
194
|
+
stepSite(direction) {
|
|
195
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
196
|
+
if (view !== null && view.sites > 1) {
|
|
197
|
+
this.select(view.active, (view.site + direction + view.sites) % view.sites);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/* Goes to the call of a row, or to the symbol of a root, and closes the tree. */
|
|
201
|
+
go(id) {
|
|
202
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
203
|
+
const node = this.nodes.get(id);
|
|
204
|
+
if (view === null || node === undefined) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const { uri, range } = targetOf(node, id === view.active ? view.site : 0);
|
|
208
|
+
this.close();
|
|
209
|
+
this.language.goTo({ uri, range });
|
|
210
|
+
}
|
|
211
|
+
reset() {
|
|
212
|
+
this.epoch++;
|
|
213
|
+
this.previewEpoch++;
|
|
214
|
+
this.controller.abort();
|
|
215
|
+
this.controller = new AbortController();
|
|
216
|
+
this.nodes = new Map();
|
|
217
|
+
this.roots = [];
|
|
218
|
+
this.texts = new Map();
|
|
219
|
+
}
|
|
220
|
+
seed() {
|
|
221
|
+
const roots = rootsOf(this.prepared);
|
|
222
|
+
this.roots = roots.map((root) => root.id);
|
|
223
|
+
this.nodes = new Map(roots.map((root) => [root.id, root]));
|
|
224
|
+
}
|
|
225
|
+
mounted(container) {
|
|
226
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
227
|
+
if (view !== null && view.container !== container) {
|
|
228
|
+
this.language.popups.setState({ callHierarchy: { ...view, container } });
|
|
229
|
+
revealRow(this.timers, container);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
patch(id, change) {
|
|
233
|
+
const node = this.nodes.get(id);
|
|
234
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
235
|
+
if (node === undefined || view === null) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.nodes.set(id, { ...node, ...change });
|
|
239
|
+
this.language.popups.setState({ callHierarchy: { ...view, rows: rowsOf(this.nodes, this.roots) } });
|
|
240
|
+
}
|
|
241
|
+
async load(id) {
|
|
242
|
+
const node = this.nodes.get(id);
|
|
243
|
+
if (node === undefined || node.load === 'loading') {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const { service, i18n } = this.language.project;
|
|
247
|
+
const { uri, editor } = this.language;
|
|
248
|
+
const { direction, epoch } = this;
|
|
249
|
+
const options = { signal: this.controller.signal, parallel: true };
|
|
250
|
+
this.patch(id, { load: 'loading', error: null });
|
|
251
|
+
try {
|
|
252
|
+
const calls = direction === 'incoming' ? await service.incomingCalls(uri, node.item, options) : await service.outgoingCalls(uri, node.item, options);
|
|
253
|
+
if (epoch !== this.epoch) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const children = childrenOf(direction, node, calls ?? []);
|
|
257
|
+
for (const child of children) {
|
|
258
|
+
this.nodes.set(child.id, child);
|
|
259
|
+
}
|
|
260
|
+
this.patch(id, { load: 'loaded', children: children.map((child) => child.id), shown: Math.min(children.length, CALL_LEVEL_LIMIT) });
|
|
261
|
+
editor.announce(i18n.t(`editor:language.callHierarchy.announce.${direction}`, { count: children.length, name: node.item.name }));
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
if (epoch !== this.epoch) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (error instanceof StaleResultError) {
|
|
268
|
+
this.close();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
this.patch(id, { load: 'failed', error: messageOf(error) });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async preview(node, site) {
|
|
275
|
+
const { project } = this.language;
|
|
276
|
+
const epoch = ++this.previewEpoch;
|
|
277
|
+
const { uri, range } = targetOf(node, site);
|
|
278
|
+
let text = this.texts.get(uri);
|
|
279
|
+
if (text === undefined) {
|
|
280
|
+
try {
|
|
281
|
+
text = (await project.readText(uri)) ?? undefined;
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// A file that cannot be read shows no code, as one that is no text file does.
|
|
285
|
+
text = undefined;
|
|
286
|
+
}
|
|
287
|
+
if (epoch !== this.previewEpoch) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (text !== undefined) {
|
|
291
|
+
this.texts.set(uri, text);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
295
|
+
if (view === null || view.active !== node.id) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const snippet = text === undefined ? null : snippetOf(text, range.start.line, range);
|
|
299
|
+
this.language.popups.setState({ callHierarchy: { ...view, preview: snippet === null ? null : { ...snippet, uri } } });
|
|
300
|
+
}
|
|
301
|
+
/* Whether a row stands under a node; the `more` and `status` rows belong to the node they are about. */
|
|
302
|
+
hasAncestor(id, ancestor) {
|
|
303
|
+
const owner = [moreIdOf(''), statusIdOf('')]
|
|
304
|
+
.map((suffix) => (id.endsWith(suffix) ? id.slice(0, -suffix.length) : null))
|
|
305
|
+
.find((found) => found !== null);
|
|
306
|
+
if (owner !== undefined && owner !== null) {
|
|
307
|
+
return owner === ancestor || this.hasAncestor(owner, ancestor);
|
|
308
|
+
}
|
|
309
|
+
for (let node = this.nodes.get(id); node !== undefined && node.parent !== null; node = this.nodes.get(node.parent)) {
|
|
310
|
+
if (node.parent === ancestor) {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
key(event) {
|
|
317
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
318
|
+
if (view === null || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || event.isComposing) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
const steps = stepsOf(view.rows);
|
|
322
|
+
const index = steps.findIndex((row) => row.id === view.active);
|
|
323
|
+
const row = steps[index];
|
|
324
|
+
switch (event.key) {
|
|
325
|
+
case 'ArrowDown':
|
|
326
|
+
this.land(steps[Math.min(steps.length - 1, index + 1)]);
|
|
327
|
+
return true;
|
|
328
|
+
case 'ArrowUp':
|
|
329
|
+
this.land(steps[Math.max(0, index - 1)]);
|
|
330
|
+
return true;
|
|
331
|
+
case 'Home':
|
|
332
|
+
this.land(steps[0]);
|
|
333
|
+
return true;
|
|
334
|
+
case 'End':
|
|
335
|
+
this.land(steps.at(-1));
|
|
336
|
+
return true;
|
|
337
|
+
case 'ArrowRight':
|
|
338
|
+
this.expandOrEnter(row, steps[index + 1]);
|
|
339
|
+
return true;
|
|
340
|
+
case 'ArrowLeft':
|
|
341
|
+
this.collapseOrLeave(row);
|
|
342
|
+
return true;
|
|
343
|
+
case 'Enter':
|
|
344
|
+
if (row?.kind === 'more') {
|
|
345
|
+
this.showMore(row.parent);
|
|
346
|
+
}
|
|
347
|
+
else if (row !== undefined) {
|
|
348
|
+
this.go(row.id);
|
|
349
|
+
}
|
|
350
|
+
return true;
|
|
351
|
+
case 'Escape':
|
|
352
|
+
this.close();
|
|
353
|
+
return true;
|
|
354
|
+
default:
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
land(row) {
|
|
359
|
+
if (row !== undefined) {
|
|
360
|
+
this.select(row.id);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/* Right opens a closed node, tries a failed one again, and steps into an open one. */
|
|
364
|
+
expandOrEnter(row, next) {
|
|
365
|
+
if (row?.kind !== 'node') {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (!row.expanded) {
|
|
369
|
+
this.toggle(row.id);
|
|
370
|
+
}
|
|
371
|
+
else if (row.load === 'failed') {
|
|
372
|
+
this.retry(row.id);
|
|
373
|
+
}
|
|
374
|
+
else if (next !== undefined && next.level > row.level) {
|
|
375
|
+
this.land(next);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/* Left closes an open node and steps out of a closed one. */
|
|
379
|
+
collapseOrLeave(row) {
|
|
380
|
+
if (row === undefined) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (row.kind === 'node' && row.expanded && !row.leaf) {
|
|
384
|
+
this.toggle(row.id);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const parent = row.kind === 'node' ? (this.nodes.get(row.id)?.parent ?? null) : row.parent;
|
|
388
|
+
if (parent !== null) {
|
|
389
|
+
this.select(parent);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
tell(title) {
|
|
393
|
+
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title });
|
|
394
|
+
}
|
|
395
|
+
say(key, options) {
|
|
396
|
+
return this.language.project.i18n.t(`editor:language.callHierarchy.${key}`, options);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
@@ -19,5 +19,7 @@ export interface EditPreview {
|
|
|
19
19
|
readonly hiddenLines: number;
|
|
20
20
|
readonly otherFiles: number;
|
|
21
21
|
readonly moves: number;
|
|
22
|
+
readonly creates?: number;
|
|
23
|
+
readonly deletes?: number;
|
|
22
24
|
}
|
|
23
25
|
export declare function previewOf(text: string, edit: WorkspaceEdit, uri: string): EditPreview | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { applyTextEdits } from '@adecore/lsp';
|
|
2
2
|
import { comparePositions } from './diagnostics-model.js';
|
|
3
|
-
import {
|
|
3
|
+
import { resourceOperationsOf, textEntriesOf } from './workspace-edit.js';
|
|
4
4
|
/* The order of the list under the caret: the fixes and the rewrites that are one keystroke away first, the refactorings after them. */
|
|
5
5
|
export const ACTION_GROUPS = ['quickfix', 'rewrite', 'extract', 'inline', 'move', 'refactor', 'source', 'other'];
|
|
6
6
|
/* The order of Refactor This: what changes the structure of the code first, the rewrites after it. */
|
|
@@ -75,15 +75,17 @@ const PREVIEW_LINES = 3;
|
|
|
75
75
|
function linesOf(text) {
|
|
76
76
|
return text.split(/\r\n|\r|\n/);
|
|
77
77
|
}
|
|
78
|
-
/* Null when the edit changes nothing
|
|
78
|
+
/* Null when the edit changes nothing. A file it creates is counted under `creates` and not as another file; moves and deletes are counted apart. */
|
|
79
79
|
export function previewOf(text, edit, uri) {
|
|
80
|
-
const entries =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
const entries = textEntriesOf(edit);
|
|
81
|
+
const operations = resourceOperationsOf(edit);
|
|
82
|
+
const count = (kind) => operations.filter((operation) => operation.kind === kind).length;
|
|
83
|
+
const moves = count('rename');
|
|
84
|
+
const creates = count('create');
|
|
85
|
+
const deletes = count('delete');
|
|
84
86
|
const own = entries.get(uri);
|
|
85
|
-
const
|
|
86
|
-
const otherFiles = [...entries.keys()].filter((key) => key !== uri).length;
|
|
87
|
+
const made = new Set(operations.filter((operation) => operation.kind === 'create').map((operation) => operation.uri));
|
|
88
|
+
const otherFiles = [...entries.keys()].filter((key) => key !== uri && !made.has(key)).length;
|
|
87
89
|
let removed = [];
|
|
88
90
|
let added = [];
|
|
89
91
|
if (own !== undefined) {
|
|
@@ -107,9 +109,9 @@ export function previewOf(text, edit, uri) {
|
|
|
107
109
|
removed = before.slice(head, before.length - tail);
|
|
108
110
|
added = next.slice(head, next.length - tail);
|
|
109
111
|
}
|
|
110
|
-
if (removed.length === 0 && added.length === 0 && otherFiles === 0 && moves === 0) {
|
|
112
|
+
if (removed.length === 0 && added.length === 0 && otherFiles === 0 && moves === 0 && creates === 0 && deletes === 0) {
|
|
111
113
|
return null;
|
|
112
114
|
}
|
|
113
115
|
const hiddenLines = Math.max(0, removed.length - PREVIEW_LINES) + Math.max(0, added.length - PREVIEW_LINES);
|
|
114
|
-
return { removed: removed.slice(0, PREVIEW_LINES), added: added.slice(0, PREVIEW_LINES), hiddenLines, otherFiles, moves };
|
|
116
|
+
return { removed: removed.slice(0, PREVIEW_LINES), added: added.slice(0, PREVIEW_LINES), hiddenLines, otherFiles, moves, creates, deletes };
|
|
115
117
|
}
|
package/dist/code-actions.js
CHANGED
|
@@ -261,7 +261,9 @@ export class CodeActionsFeature {
|
|
|
261
261
|
const notes = [
|
|
262
262
|
...(found.hiddenLines > 0 ? [this.say('moreLines', { count: found.hiddenLines })] : []),
|
|
263
263
|
...(found.otherFiles > 0 ? [this.say('otherFiles', { count: found.otherFiles })] : []),
|
|
264
|
-
...(found.moves > 0 ? [this.say('moves', { count: found.moves })] : [])
|
|
264
|
+
...(found.moves > 0 ? [this.say('moves', { count: found.moves })] : []),
|
|
265
|
+
...((found.creates ?? 0) > 0 ? [this.say('creates', { count: found.creates })] : []),
|
|
266
|
+
...((found.deletes ?? 0) > 0 ? [this.say('deletes', { count: found.deletes })] : [])
|
|
265
267
|
];
|
|
266
268
|
return { removed: found.removed, added: found.added, note: notes.length === 0 ? null : notes.join(' · ') };
|
|
267
269
|
}
|