pane: Add "Reveal in Finder" to tab context menu (#51615)

Matt Van Horn , Matt Van Horn , and Claude Opus 4.6 (1M context) created

The tab context menu has "Copy Path", "Open in Terminal", and "Reveal In
Project Panel" but no way to reveal the file in the system file manager.
This action already exists in three other context menus (editor
right-click, project panel, outline panel) but was missing from tab
right-click.

## Changes

Adds a platform-specific entry to the tab context menu:
- **macOS:** "Reveal in Finder"
- **Windows:** "Reveal in File Explorer"
- **Linux:** "Reveal in File Manager"

Placed after "Copy Relative Path" and before "Pin Tab". Gated behind
`is_local` (including WSL with host interop) to match the project
panel's behavior. Uses the existing `project.reveal_path()`
infrastructure, which handles platform-specific file manager invocation
and WSL path conversion.

## Prior art

Every major editor has this in the tab context menu:
- VS Code: "Reveal in Finder" (macOS) / "Reveal in File Explorer"
(Windows)
- JetBrains IDEs: Right-click tab -> "Open in" -> "Finder"
- Sublime Text: Right-click tab -> "Reveal in Finder"

Zed already has this in the editor body right-click menu (`Cmd-K R`),
project panel (`Alt-Cmd-R`), and outline panel. The tab context menu was
the only place it was missing.

This contribution was developed with AI assistance (Claude Code).

Release Notes:

- Added "Reveal in Finder" to the tab context menu

## Screenshot

![Reveal in Finder in tab context
menu](https://github.com/mvanhorn/zed/releases/download/untagged-02ded227b30e3bcce8db/IMG_8537.png)

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Change summary

crates/editor/src/actions.rs              |  2 -
crates/editor/src/editor.rs               |  1 
crates/editor/src/mouse_context_menu.rs   |  8 -----
crates/outline_panel/src/outline_panel.rs |  8 -----
crates/project_panel/src/project_panel.rs |  8 -----
crates/ui/src/utils.rs                    | 11 +++++++++
crates/workspace/src/pane.rs              | 30 ++++++++++++++++++++++++
crates/zed_actions/src/lib.rs             |  2 +
8 files changed, 46 insertions(+), 24 deletions(-)

Detailed changes

crates/editor/src/actions.rs 🔗

@@ -699,8 +699,6 @@ actions!(
         Rename,
         /// Restarts the language server for the current file.
         RestartLanguageServer,
-        /// Reveals the current file in the system file manager.
-        RevealInFileManager,
         /// Reverses the order of selected lines.
         ReverseLines,
         /// Reloads the file from disk.

crates/editor/src/editor.rs 🔗

@@ -221,6 +221,7 @@ use workspace::{
     notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
     searchable::SearchEvent,
 };
+pub use zed_actions::editor::RevealInFileManager;
 use zed_actions::editor::{MoveDown, MoveUp};
 
 use crate::{

crates/editor/src/mouse_context_menu.rs 🔗

@@ -286,13 +286,7 @@ pub fn deploy_context_menu(
                 .separator()
                 .action_disabled_when(
                     !has_reveal_target,
-                    if cfg!(target_os = "macos") {
-                        "Reveal in Finder"
-                    } else if cfg!(target_os = "windows") {
-                        "Reveal in File Explorer"
-                    } else {
-                        "Reveal in File Manager"
-                    },
+                    ui::utils::reveal_in_file_manager_label(false),
                     Box::new(RevealInFileManager),
                 )
                 .when(is_markdown, |builder| {

crates/outline_panel/src/outline_panel.rs 🔗

@@ -1490,13 +1490,7 @@ impl OutlinePanel {
         let context_menu = ContextMenu::build(window, cx, |menu, _, _| {
             menu.context(self.focus_handle.clone())
                 .action(
-                    if cfg!(target_os = "macos") {
-                        "Reveal in Finder"
-                    } else if cfg!(target_os = "windows") {
-                        "Reveal in File Explorer"
-                    } else {
-                        "Reveal in File Manager"
-                    },
+                    ui::utils::reveal_in_file_manager_label(false),
                     Box::new(RevealInFileManager),
                 )
                 .action("Open in Terminal", Box::new(OpenInTerminal))

crates/project_panel/src/project_panel.rs 🔗

@@ -1201,13 +1201,7 @@ impl ProjectPanel {
                             .separator()
                             .when(is_local, |menu| {
                                 menu.action(
-                                    if cfg!(target_os = "macos") && !is_remote {
-                                        "Reveal in Finder"
-                                    } else if cfg!(target_os = "windows") && !is_remote {
-                                        "Reveal in File Explorer"
-                                    } else {
-                                        "Reveal in File Manager"
-                                    },
+                                    ui::utils::reveal_in_file_manager_label(is_remote),
                                     Box::new(RevealInFileManager),
                                 )
                             })

crates/ui/src/utils.rs 🔗

@@ -23,3 +23,14 @@ pub use with_rem_size::*;
 pub fn is_light(cx: &mut App) -> bool {
     cx.theme().appearance.is_light()
 }
+
+/// Returns the platform-appropriate label for the "reveal in file manager" action.
+pub fn reveal_in_file_manager_label(is_remote: bool) -> &'static str {
+    if cfg!(target_os = "macos") && !is_remote {
+        "Reveal in Finder"
+    } else if cfg!(target_os = "windows") && !is_remote {
+        "Reveal in File Explorer"
+    } else {
+        "Reveal in File Manager"
+    }
+}

crates/workspace/src/pane.rs 🔗

@@ -3192,6 +3192,7 @@ impl Pane {
                             });
 
                             let entry_abs_path = pane.read(cx).entry_abs_path(entry, cx);
+                            let reveal_path = entry_abs_path.clone();
                             let parent_abs_path = entry_abs_path
                                 .as_deref()
                                 .and_then(|abs_path| Some(abs_path.parent()?.to_path_buf()));
@@ -3201,6 +3202,15 @@ impl Pane {
 
                             let visible_in_project_panel = relative_path.is_some()
                                 && worktree.is_some_and(|worktree| worktree.read(cx).is_visible());
+                            let is_local = pane.read(cx).project.upgrade().is_some_and(|project| {
+                                let project = project.read(cx);
+                                project.is_local() || project.is_via_wsl_with_host_interop(cx)
+                            });
+                            let is_remote = pane
+                                .read(cx)
+                                .project
+                                .upgrade()
+                                .is_some_and(|project| project.read(cx).is_remote());
 
                             let entry_id = entry.to_proto();
 
@@ -3233,8 +3243,26 @@ impl Pane {
                                         }),
                                     )
                                 })
+                                .when(is_local, |menu| {
+                                    menu.when_some(reveal_path, |menu, reveal_path| {
+                                        menu.separator().entry(
+                                            ui::utils::reveal_in_file_manager_label(is_remote),
+                                            Some(Box::new(
+                                                zed_actions::editor::RevealInFileManager,
+                                            )),
+                                            window.handler_for(&pane, move |pane, _, cx| {
+                                                if let Some(project) = pane.project.upgrade() {
+                                                    project.update(cx, |project, cx| {
+                                                        project.reveal_path(&reveal_path, cx);
+                                                    });
+                                                } else {
+                                                    cx.reveal_path(&reveal_path);
+                                                }
+                                            }),
+                                        )
+                                    })
+                                })
                                 .map(pin_tab_entries)
-                                .separator()
                                 .when(visible_in_project_panel, |menu| {
                                     menu.entry(
                                         "Reveal In Project Panel",

crates/zed_actions/src/lib.rs 🔗

@@ -197,6 +197,8 @@ pub mod editor {
             MoveUp,
             /// Moves cursor down.
             MoveDown,
+            /// Reveals the current file in the system file manager.
+            RevealInFileManager,
         ]
     );
 }