diff --git a/src/features/project-tree/operations.test.ts b/src/features/project-tree/operations.test.ts index 46cdc104f..cc076859e 100644 --- a/src/features/project-tree/operations.test.ts +++ b/src/features/project-tree/operations.test.ts @@ -7,6 +7,7 @@ import { importExternal, paste, refreshOsClipboard, + revealablePath, selectionAfterDelete, transfer, topLevelPaths, @@ -452,3 +453,43 @@ describe("project clipboard ownership", () => { expect(canPaste()).toBe(true); }); }); + +describe("revealablePath", () => { + const platform = usePlatform.getState(); + afterEach(() => { + usePlatform.setState(platform); + useRemote.setState({ session: null }); + }); + + it("translates distro paths to the \\\\wsl.localhost share for a WSL session on Windows", () => { + usePlatform.setState({ os: "windows" }); + useRemote.setState({ + session: { + target: { kind: "wsl", distro: "Ubuntu-24.04" }, + root: "/p", + host: "Ubuntu", + os: "linux", + commands: [], + }, + }); + expect(revealablePath("/home/me/app/src/main.rs")).toBe( + "\\\\wsl.localhost\\Ubuntu-24.04\\home\\me\\app\\src\\main.rs", + ); + expect(revealablePath("/")).toBe("\\\\wsl.localhost\\Ubuntu-24.04\\"); + }); + + it("leaves paths alone for local projects, SSH sessions and non-Windows desktops", () => { + usePlatform.setState({ os: "windows" }); + useRemote.setState({ + session: { target: { kind: "ssh", config: "host" }, root: "/p", host: "host", os: "linux", commands: [] }, + }); + expect(revealablePath("/home/me/app")).toBe("/home/me/app"); + useRemote.setState({ session: null }); + expect(revealablePath("C:\\work\\app")).toBe("C:\\work\\app"); + usePlatform.setState({ os: "macos" }); + useRemote.setState({ + session: { target: { kind: "wsl", distro: "Ubuntu" }, root: "/p", host: "Ubuntu", os: "linux", commands: [] }, + }); + expect(revealablePath("/home/me/app")).toBe("/home/me/app"); + }); +}); diff --git a/src/features/project-tree/operations.ts b/src/features/project-tree/operations.ts index 410421af0..8b1dee9a3 100644 --- a/src/features/project-tree/operations.ts +++ b/src/features/project-tree/operations.ts @@ -39,7 +39,7 @@ import { useProjectTree, } from "./store"; import { tx } from "@/platform/i18n"; -import { hostOs, isRemoteWindow } from "@/platform/remote"; +import { hostOs, isRemoteWindow, useRemote } from "@/platform/remote"; import { usePlatform } from "@/platform/platformInfo"; /** Nodes that may be renamed, moved or deleted (not roots, groups or library sources). */ @@ -821,14 +821,29 @@ export async function copyPaths(kind: PathKind, nodes: ProjectNode[] = selectedN } } +/** + * The path the local OS's file manager can open: in a WSL session the node + * paths are inside the distro (`/home/…`), which `revealItemInDir` reads as + * local paths — translate them to `\\wsl.localhost\\…` UNC shares. + */ +export function revealablePath(path: string): string { + const session = useRemote.getState().session; + if (session?.target.kind !== "wsl" || usePlatform.getState().os !== "windows") return path; + if (!path.startsWith("/")) return path; + return `\\\\wsl.localhost\\${session.target.distro}${path.replaceAll("/", "\\")}`; +} + export async function revealInFinder(nodes: ProjectNode[] = selectedNodes()) { - const paths = nodes.map((n) => n.path); + const paths = nodes.map((n) => revealablePath(n.path)); if (!paths.length) return; try { const { revealItemInDir } = await import("@tauri-apps/plugin-opener"); await revealItemInDir(paths.length === 1 ? paths[0]! : paths); } catch (e) { - fail("Cannot reveal in Finder", e); + // Same names as contextMenu.revealLabel (kept local: contextMenu imports + // this module, so importing it back would be a cycle). + const desktop = usePlatform.getState().os; + fail(`Cannot reveal in ${desktop === "windows" ? "Explorer" : desktop === "linux" ? "Files" : "Finder"}`, e); } }