Browse Source

feat(desktop): reveal projects in file manager (#35731)

usrnk1 1 month ago
parent
commit
a4c17f08b1

+ 2 - 5
packages/app/src/components/session/session-header.tsx

@@ -24,6 +24,7 @@ import { focusTerminalById } from "@/pages/session/helpers"
 import { useSessionLayout } from "@/pages/session/session-layout"
 import { messageAgentColor } from "@/utils/agent"
 import { decode64 } from "@/utils/base64"
+import { fileManagerApp } from "@/utils/file-manager"
 import { Persist, persisted } from "@/utils/persist"
 import { StatusPopover, StatusPopoverV2 } from "../status-popover"
 import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
@@ -175,11 +176,7 @@ export function SessionHeader() {
     return LINUX_APPS
   })
 
-  const fileManager = createMemo(() => {
-    if (os() === "macos") return { label: "session.header.open.finder", icon: "finder" as const }
-    if (os() === "windows") return { label: "session.header.open.fileExplorer", icon: "file-explorer" as const }
-    return { label: "session.header.open.fileManager", icon: "finder" as const }
-  })
+  const fileManager = createMemo(() => fileManagerApp(os()))
 
   createEffect(() => {
     if (platform.platform !== "desktop") return

+ 3 - 0
packages/app/src/i18n/en.ts

@@ -709,6 +709,9 @@ export const dict = {
   "session.header.open.finder": "Finder",
   "session.header.open.fileExplorer": "File Explorer",
   "session.header.open.fileManager": "File Manager",
+  "session.header.reveal.finder": "Reveal in Finder",
+  "session.header.reveal.fileExplorer": "Reveal in File Explorer",
+  "session.header.reveal.containingFolder": "Open containing folder",
   "session.header.open.app.vscode": "VS Code",
   "session.header.open.app.cursor": "Cursor",
   "session.header.open.app.zed": "Zed",

+ 18 - 0
packages/app/src/pages/home.tsx

@@ -68,6 +68,7 @@ import { preloadMarkdown } from "@opencode-ai/session-ui/markdown-cache"
 import { archiveHomeSession } from "./home-session-archive"
 import { shouldOpenSessionInBackground } from "./home-session-open"
 import { showToast } from "@/utils/toast"
+import { fileManagerApp } from "@/utils/file-manager"
 
 const HOME_SESSION_LIMIT = 64
 const HOME_SESSION_HEADER_STICKY_TOP = 12
@@ -1030,8 +1031,22 @@ function HomeProjectRow(props: {
   language: ReturnType<typeof useLanguage>
 }) {
   const global = useGlobal()
+  const platform = usePlatform()
   const serverUnreachable = () => global.servers.health[ServerConnection.key(props.server)]?.healthy === false
   const [state, setState] = createStore({ menuOpen: false })
+  const canRevealInFileManager = () =>
+    platform.platform === "desktop" && !!platform.openPath && ServerConnection.local(props.server)
+  const fileManagerActionLabel = () =>
+    props.language.t(fileManagerApp(platform.platform === "desktop" ? platform.os ?? "unknown" : "unknown").actionLabel)
+  const revealInFileManager = () => {
+    if (!platform.openPath) return
+    platform.openPath(props.project.worktree).catch((err: unknown) =>
+      showToast({
+        title: props.language.t("common.requestFailed"),
+        description: errorMessage(err, props.language.t("common.requestFailed")),
+      }),
+    )
+  }
   return (
     <div class="group/project relative flex h-7 min-w-0 items-center rounded-[6px]">
       <button
@@ -1073,6 +1088,9 @@ function HomeProjectRow(props: {
               <MenuV2.Item onSelect={() => props.editProject(props.server, props.project)}>
                 {props.language.t("dialog.project.edit.title")}
               </MenuV2.Item>
+              <Show when={canRevealInFileManager()}>
+                <MenuV2.Item onSelect={revealInFileManager}>{fileManagerActionLabel()}</MenuV2.Item>
+              </Show>
               <MenuV2.Item
                 disabled={props.unseenCount === 0}
                 onSelect={() => props.clearNotifications(props.server, props.project)}

+ 24 - 0
packages/app/src/utils/file-manager.ts

@@ -0,0 +1,24 @@
+export type FileManagerOS = "macos" | "windows" | "linux" | "unknown"
+
+export function fileManagerApp(os: FileManagerOS): {
+  label: "session.header.open.finder" | "session.header.open.fileExplorer" | "session.header.open.fileManager"
+  actionLabel:
+    | "session.header.reveal.finder"
+    | "session.header.reveal.fileExplorer"
+    | "session.header.reveal.containingFolder"
+  icon: "finder" | "file-explorer"
+} {
+  if (os === "macos")
+    return { label: "session.header.open.finder", actionLabel: "session.header.reveal.finder", icon: "finder" }
+  if (os === "windows")
+    return {
+      label: "session.header.open.fileExplorer",
+      actionLabel: "session.header.reveal.fileExplorer",
+      icon: "file-explorer",
+    }
+  return {
+    label: "session.header.open.fileManager",
+    actionLabel: "session.header.reveal.containingFolder",
+    icon: "finder",
+  }
+}