From fa12d84b9bafbd0a002602ace992ff9fbaa2c097 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sat, 8 Aug 2026 07:22:41 +0800 Subject: [PATCH] fix(reveal): stop offering Finder a path that is not on this machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The file tree and the Session panel both list whatever host the workspace spawns on, and every other action they offer goes through that host. Only "Reveal in Finder" does not: `cx.reveal_path` talks to the local file manager, so in a remote workspace it was handed a path that lives on the other machine. Finder either does nothing at all — a menu item that silently no-ops — or, if a local path happens to collide, opens the wrong directory. Both are now hidden unless the paths really are local: | | Gate | |---|---| | File tree context menu | `spawn_host(cx).is_local()`, the same predicate `guard_local_spawn` uses | | Session panel button | `view.local_cwd().is_some()` — a helper that already existed for exactly this (`remote_context().is_none() && host_id.is_local()`) and that this call site was not using | Copy Path stays in both places: a remote path is still worth copying. Checked on screen that the local file tree keeps the item and its separators. The remote branch is reasoned from the predicate, not exercised — it needs a real SSH host. --- src/ui/file_tree.rs | 32 +++++++++++++++++++------------- src/ui/right_panel.rs | 42 +++++++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/ui/file_tree.rs b/src/ui/file_tree.rs index d96a3e33..d91d6ed1 100644 --- a/src/ui/file_tree.rs +++ b/src/ui/file_tree.rs @@ -1445,6 +1445,7 @@ impl Tty7App { let path = path.clone(); let is_root = row.is_root; let show_hidden = self.file_tree.show_hidden; + let paths_are_local = self.spawn_host(cx).is_local(); move |menu, _window, cx| { let danger = cx.theme().danger; Self::tree_row_context_menu( @@ -1453,6 +1454,7 @@ impl Tty7App { is_dir, is_root, show_hidden, + paths_are_local, danger, &app, ) @@ -1489,6 +1491,12 @@ impl Tty7App { is_dir: bool, is_root: bool, show_hidden: bool, + // The tree lists whatever host the workspace spawns on. Everything else + // in this menu goes through that host; the file manager only knows this + // machine, so over SSH the item would hand Finder a path that is not + // here — silently opening nothing, or the wrong thing if a local path + // happens to collide. + paths_are_local: bool, danger: gpui::Hsla, app: &gpui::WeakEntity, ) -> PopupMenu { @@ -1588,19 +1596,16 @@ impl Tty7App { ); } - menu = menu - .separator() - .item( - PopupMenuItem::new(t(L10nKey::FileTreeContextCopyPath)).on_click({ - let p = p.clone(); - move |_, _window, cx| { - cx.write_to_clipboard(gpui::ClipboardItem::new_string( - p.display().to_string(), - )); - } - }), - ) - .item( + menu = menu.separator().item( + PopupMenuItem::new(t(L10nKey::FileTreeContextCopyPath)).on_click({ + let p = p.clone(); + move |_, _window, cx| { + cx.write_to_clipboard(gpui::ClipboardItem::new_string(p.display().to_string())); + } + }), + ); + if paths_are_local { + menu = menu.item( PopupMenuItem::new(crate::ui::right_panel::reveal_label()).on_click({ let p = p.clone(); move |_, _window, cx| { @@ -1608,6 +1613,7 @@ impl Tty7App { } }), ); + } menu = menu.separator().item(dotfiles_menu_item(show_hidden, app)); diff --git a/src/ui/right_panel.rs b/src/ui/right_panel.rs index 488908b4..94105b00 100644 --- a/src/ui/right_panel.rs +++ b/src/ui/right_panel.rs @@ -357,7 +357,7 @@ impl Tty7App { fn render_panel_info(&mut self, window: &mut Window, cx: &mut Context) -> AnyElement { let title = self.panel_title(t(L10nKey::PanelInfoTitle), None, None, window, cx); let mut rows: Vec<(&'static str, String)> = Vec::new(); - let mut cwd_for_actions: Option = None; + let mut cwd_for_actions: Option<(PathBuf, bool)> = None; let mut pane_id: Option = None; let mut forwards_pane: Option = None; @@ -371,7 +371,9 @@ impl Tty7App { .or_else(|| view.cwd()) { rows.push((t(L10nKey::PanelCwd), compact_path(&cwd))); - cwd_for_actions = Some(cwd); + // Copy Path is right either way; Reveal only means anything + // when the path is on the machine the file manager can see. + cwd_for_actions = Some((cwd, view.local_cwd().is_some())); } let shell = match view.shell_spec().map(|s| s.program.clone()) { Some(program) => crate::core::shells::default_shell_name(Some(&program)), @@ -454,8 +456,8 @@ impl Tty7App { let inner = v_flex() .child(self.panel_subtitle(t(L10nKey::PanelSessionSubtitle), false, None, cx)) .child(list) - .when_some(cwd_for_actions, |this, cwd| { - this.child(self.cwd_actions(cwd, cx)) + .when_some(cwd_for_actions, |this, (cwd, local)| { + this.child(self.cwd_actions(cwd, local, cx)) }) .children(self.procs_section(pane_id, cx)) .children(self.ports_section(pane_id, cx)) @@ -464,27 +466,29 @@ impl Tty7App { self.panel_scroll(inner, title) } - fn cwd_actions(&self, cwd: PathBuf, cx: &mut Context) -> AnyElement { + fn cwd_actions(&self, cwd: PathBuf, local: bool, cx: &mut Context) -> AnyElement { let reveal_label = reveal_label(); h_flex() .gap(px(2.)) .px(px(tile_trailing_inset_sm())) .pt(px(6.)) - .child( - crate::ui::tab_strip::chrome_tile_sized( - Button::new("panel-info-reveal").icon(Icon::new(IconName::FolderOpen)), - TILE_SIZE_SM, - TILE_GLYPH_SM, - false, - cx, + .when(local, |this| { + this.child( + crate::ui::tab_strip::chrome_tile_sized( + Button::new("panel-info-reveal").icon(Icon::new(IconName::FolderOpen)), + TILE_SIZE_SM, + TILE_GLYPH_SM, + false, + cx, + ) + .rounded_md() + .tooltip(reveal_label) + .on_click({ + let cwd = cwd.clone(); + move |_, _window, cx| cx.reveal_path(&cwd) + }), ) - .rounded_md() - .tooltip(reveal_label) - .on_click({ - let cwd = cwd.clone(); - move |_, _window, cx| cx.reveal_path(&cwd) - }), - ) + }) .child( crate::ui::tab_strip::chrome_tile_sized( Button::new("panel-info-copy-path").icon(Icon::new(IconName::Copy)),