From a57267fa32236f5129c40bb8168c4af0be3261bf Mon Sep 17 00:00:00 2001 From: Jaw <53564781+jaw52@users.noreply.github.com> Date: Tue, 3 Nov 2026 20:30:07 +0800 Subject: [PATCH] fix(project-tree): reveal files of linked workspace projects (#915) (#1035) In a workspace the tree's root is the workspace directory, and the linked projects are separate top-level roots outside it. Select Opened File (and other reveals) only looked inside that root and contributed folders, so a file of a linked project was never expanded, selected or scrolled to. Reveal now resolves the innermost linked project containing the path first; the Project Files pane also counts linked projects as shown. Co-authored-by: Claude Opus 5.5 --- src/features/project-tree/store.test.ts | 11 +++++++++++ src/features/project-tree/store.ts | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/features/project-tree/store.test.ts b/src/features/project-tree/store.test.ts index 8d60d4d89..78a42dec1 100644 --- a/src/features/project-tree/store.test.ts +++ b/src/features/project-tree/store.test.ts @@ -146,6 +146,17 @@ describe("reveal and expand", () => { expect(visibleInfo(currentModel(), s.expanded).visible.has("/p/docs/guide.md")).toBe(true); }); + it("reveals files of linked workspace projects outside the workspace directory", async () => { + fs = new MemoryFs({ "/ws/.openidea/workspace.json": "x", "/a/src/app.ts": "x", "/b/lib/util.ts": "x" }, "/"); + setProjectFsForTests(fs, "/ws"); + initProjectTree("/ws", "ws", ["/a", "/b"]); + expect(revealPath("/b/lib/util.ts")).toBe(true); + await vi.waitFor(() => expect(childLabels("/b/lib")).toEqual(["util.ts"])); + const s = useProjectTree.getState(); + expect(s.revealRequest?.path).toBe("/b/lib/util.ts"); + expect(visibleInfo(currentModel(), s.expanded).visible.has("/b/lib/util.ts")).toBe(true); + }); + it("expands everything below a folder", async () => { await expandAll(); const s = useProjectTree.getState(); diff --git a/src/features/project-tree/store.ts b/src/features/project-tree/store.ts index cca259ea9..993971856 100644 --- a/src/features/project-tree/store.ts +++ b/src/features/project-tree/store.ts @@ -548,7 +548,7 @@ let revealSeq = 0; export function revealPath(path: string, focus = false) { const { expanded } = get(); const project = get().root; - const root = project && isSameOrInside(path, project) ? project : project ? extraRootOf(path) : null; + const root = project ? (memberRootOf(path) ?? (isSameOrInside(path, project) ? project : extraRootOf(path))) : null; if (!root) return false; // Library folders sit below group nodes (External Libraries › SDK), keyed in their own namespace. const model = currentModel(); @@ -568,6 +568,16 @@ export function revealPath(path: string, focus = false) { return true; } +/** + * The linked workspace project containing `path`, if any (the innermost one). A + * workspace's root is its own directory; its projects are separate top-level roots. + */ +function memberRootOf(path: string): string | null { + let found: string | null = null; + for (const m of get().members ?? []) if (isSameOrInside(path, m) && (!found || m.length > found.length)) found = m; + return found; +} + /** The contributed folder (scratches, a library root…) containing `path`, if any. */ export function extraRootOf(path: string): string | null { for (const r of extraFolders(projectViewRoots(useProjectViewRoots.getState().roots))) @@ -583,7 +593,7 @@ export function modeShows(path: string): boolean { case "project": return true; case "projectFiles": - return isSameOrInside(path, root); + return memberRootOf(path) !== null || (!get().members && isSameOrInside(path, root)); case "scratches": return projectViewRoots(useProjectViewRoots.getState().roots).some( (r) => r.scope === "scratches" && r.path !== undefined && isSameOrInside(path, r.path),