mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-11 02:31:38 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cfe01122b |
No files matched your search
@@ -23,6 +23,7 @@ import {
|
||||
hierarchyTitle,
|
||||
hierarchyTarget,
|
||||
loadChildren,
|
||||
prefetchLevels,
|
||||
refreshHierarchy,
|
||||
noneFoundLabel,
|
||||
setDirection,
|
||||
@@ -222,6 +223,10 @@ export function HierarchyView() {
|
||||
getChildren={getChildren}
|
||||
isExpandable={isExpandable}
|
||||
loadChildren={(n) => (n.kind === "item" ? loadChildren(n.node) : undefined)}
|
||||
onExpandedChange={(_expanded, n, open) => {
|
||||
const shown = open ? children.get(n.id) : undefined;
|
||||
if (shown) prefetchLevels(shown);
|
||||
}}
|
||||
defaultExpanded={[ROOT_ID]}
|
||||
handleRef={tree}
|
||||
isOccurrence={() => true}
|
||||
|
||||
@@ -55,6 +55,20 @@ const callHierarchy = vi.fn((): { items: HierarchyItem[]; incomplete: References
|
||||
incomplete: null,
|
||||
}));
|
||||
const cancelRequest = vi.fn(async () => undefined);
|
||||
// Subtypes by the line of the type asked about: the caret's type → Long (30), Striped64 (40); Striped64 → LongAdder (50).
|
||||
const typeItem = (name: string, line: number): HierarchyItem => ({
|
||||
...item(name),
|
||||
kind: "class",
|
||||
path: "/p/Types.java",
|
||||
selectionRange: range(line, 0, line, name.length),
|
||||
});
|
||||
const typeHierarchy = vi.fn((pos: { line: number; col: number }): HierarchyItem[] =>
|
||||
pos.line === 40
|
||||
? [typeItem("LongAdder", 50)]
|
||||
: pos.line < 30
|
||||
? [typeItem("Long", 30), typeItem("Striped64", 40)]
|
||||
: [],
|
||||
);
|
||||
// The positions a hierarchy request is answered at; anything else is "no symbol".
|
||||
let servedAt = (pos: { line: number; col: number }) => pos.line === 2 && pos.col === 7;
|
||||
|
||||
@@ -67,6 +81,7 @@ vi.mock("@/platform/ipc", async (orig) => {
|
||||
...real.commands,
|
||||
docOutline: async () => OUTLINE,
|
||||
cancelRequest,
|
||||
lspTypeHierarchy: async (_doc: number, pos: { line: number; col: number }) => typeHierarchy(pos),
|
||||
lspCallHierarchy: async (_request: number, _doc: number, pos: { line: number; col: number }) => {
|
||||
// The server prepares a hierarchy only on the method name.
|
||||
if (!servedAt(pos)) throw new real.IpcError({ kind: "notFound", message: "No symbol" });
|
||||
@@ -169,6 +184,28 @@ describe("hierarchy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("type hierarchy", () => {
|
||||
it("loads each shown type's level ahead, so a type without subtypes shows no expand arrow", async () => {
|
||||
servedAt = () => true;
|
||||
try {
|
||||
useHierarchy.setState({ mode: "type", direction: "outgoing" });
|
||||
await showHierarchy("type");
|
||||
const [long, striped] = useHierarchy.getState().children.get(ROOT_ID) ?? [];
|
||||
await vi.waitFor(() => {
|
||||
const { children } = useHierarchy.getState();
|
||||
expect(children.get(long!.id)).toEqual([]);
|
||||
expect(children.get(striped!.id)?.map((n) => n.item.name)).toEqual(["LongAdder"]);
|
||||
});
|
||||
const { children } = useHierarchy.getState();
|
||||
// Only the shown levels: LongAdder's subtypes wait until Striped64 is expanded.
|
||||
expect(children.has(children.get(striped!.id)![0]!.id)).toBe(false);
|
||||
} finally {
|
||||
servedAt = (pos) => pos.line === 2 && pos.col === 7;
|
||||
useHierarchy.setState({ mode: "call", direction: "incoming" });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("closing the hierarchy tab", () => {
|
||||
it("clears the tree and hides the tool window", async () => {
|
||||
const { closeHierarchy, useHierarchy } = await import("./hierarchy");
|
||||
|
||||
@@ -177,6 +177,7 @@ async function loadRoot(quiet = false): Promise<RootResult> {
|
||||
request(s.mode, doc, root.line, root.col, s.direction),
|
||||
);
|
||||
setChildren(generation, ROOT_ID, [rootKey(root)], items, incomplete && incompleteNote(incomplete, root.name));
|
||||
prefetchLevels(useHierarchy.getState().children.get(ROOT_ID) ?? []);
|
||||
return "loaded";
|
||||
} catch (e) {
|
||||
if (useHierarchy.getState().generation !== generation) return "stale";
|
||||
@@ -220,7 +221,8 @@ async function enclosingRoot(c: CaretContext, mode: HierarchyMode): Promise<Hier
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export async function loadChildren(node: HierarchyNode) {
|
||||
/** Loads `node`'s level. `ahead`: a look-ahead load (prefetchLevels), which reports nothing. */
|
||||
export async function loadChildren(node: HierarchyNode, ahead = false) {
|
||||
const { mode, direction, generation } = useHierarchy.getState();
|
||||
const at = node.item.selectionRange.start;
|
||||
try {
|
||||
@@ -234,8 +236,9 @@ export async function loadChildren(node: HierarchyNode) {
|
||||
items,
|
||||
incomplete && incompleteNote(incomplete, node.item.name),
|
||||
);
|
||||
if (!ahead) prefetchLevels(useHierarchy.getState().children.get(node.id) ?? []);
|
||||
} catch (e) {
|
||||
if (useHierarchy.getState().generation !== generation) return;
|
||||
if (ahead || useHierarchy.getState().generation !== generation) return;
|
||||
useHierarchy.setState((st) => ({
|
||||
failed: new Set(st.failed).add(node.id),
|
||||
children: new Map(st.children).set(node.id, []),
|
||||
@@ -244,6 +247,40 @@ export async function loadChildren(node: HierarchyNode) {
|
||||
}
|
||||
}
|
||||
|
||||
const AHEAD_CONCURRENCY = 4;
|
||||
const ahead: HierarchyNode[] = [];
|
||||
let aheadRunning = 0;
|
||||
let aheadGeneration = -1;
|
||||
|
||||
/**
|
||||
* IDEA's type hierarchy shows no expand arrow on a type without subtypes (or supertypes): the
|
||||
* level below each shown node is loaded ahead, a few requests at a time. Call hierarchies stay
|
||||
* lazy, as in IDEA.
|
||||
*/
|
||||
export function prefetchLevels(nodes: readonly HierarchyNode[]) {
|
||||
const { mode, generation, children } = useHierarchy.getState();
|
||||
if (mode !== "type") return;
|
||||
if (aheadGeneration !== generation) {
|
||||
ahead.length = 0;
|
||||
aheadGeneration = generation;
|
||||
}
|
||||
for (const n of nodes) if (!n.recursion && !children.has(n.id) && !ahead.includes(n)) ahead.push(n);
|
||||
pumpAhead();
|
||||
}
|
||||
|
||||
function pumpAhead() {
|
||||
while (aheadRunning < AHEAD_CONCURRENCY && ahead.length) {
|
||||
const node = ahead.shift()!;
|
||||
const { generation, children } = useHierarchy.getState();
|
||||
if (generation !== aheadGeneration || children.has(node.id)) continue;
|
||||
aheadRunning++;
|
||||
void loadChildren(node, true).finally(() => {
|
||||
aheadRunning--;
|
||||
pumpAhead();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function showHierarchy(mode: HierarchyMode) {
|
||||
const c = caretContext();
|
||||
if (!c) {
|
||||
|
||||
Reference in new issue
Block a user