Add item for opening Markdown/SVG files in preview tab in right-click menu (#47821)

Danilo Leal created

Following user feedback, this should help making the Markdown Preview
more discoverable.

| Buffer Right-click | Tab Right-click |
|--------|--------|
| <img width="2474" height="1824" alt="Screenshot 2026-01-27 at 10 
16@2x"
src="https://github.com/user-attachments/assets/251149e9-89c6-4d11-aed0-872669939cfb"
/> | <img width="2464" height="1808" alt="Screenshot 2026-01-27 at 10 
16 2@2x"
src="https://github.com/user-attachments/assets/359a221b-2141-45b1-98a9-d9c77b601c0b"
/> |

Release Notes:

- Workspace: Added a menu item in the buffer and tab right-click menu
for opening Markdown and SVG files in the preview tab.

Change summary

Cargo.lock                                      |  2 
crates/editor/src/items.rs                      | 45 +++++++++++++++++++
crates/editor/src/mouse_context_menu.rs         | 27 +++++++++++
crates/markdown_preview/Cargo.toml              |  1 
crates/markdown_preview/src/markdown_preview.rs |  6 -
crates/svg_preview/Cargo.toml                   |  1 
crates/svg_preview/src/svg_preview.rs           |  6 -
crates/zed_actions/src/lib.rs                   | 30 ++++++++++++
8 files changed, 110 insertions(+), 8 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9857,6 +9857,7 @@ dependencies = [
  "urlencoding",
  "util",
  "workspace",
+ "zed_actions",
 ]
 
 [[package]]
@@ -16155,6 +16156,7 @@ dependencies = [
  "multi_buffer",
  "ui",
  "workspace",
+ "zed_actions",
 ]
 
 [[package]]

crates/editor/src/items.rs 🔗

@@ -59,6 +59,9 @@ use workspace::{
     item::{BreadcrumbText, FollowEvent, ProjectItemKind},
     searchable::SearchOptions,
 };
+use zed_actions::preview::{
+    markdown::OpenPreview as OpenMarkdownPreview, svg::OpenPreview as OpenSvgPreview,
+};
 
 pub const MAX_TAB_TITLE_LEN: usize = 24;
 
@@ -1033,6 +1036,48 @@ impl Item for Editor {
         }
     }
 
+    fn tab_extra_context_menu_actions(
+        &self,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Vec<(SharedString, Box<dyn gpui::Action>)> {
+        let mut actions = Vec::new();
+
+        let is_markdown = self
+            .buffer()
+            .read(cx)
+            .as_singleton()
+            .and_then(|buffer| buffer.read(cx).language())
+            .is_some_and(|language| language.name().as_ref() == "Markdown");
+
+        let is_svg = self
+            .buffer()
+            .read(cx)
+            .as_singleton()
+            .and_then(|buffer| buffer.read(cx).file())
+            .is_some_and(|file| {
+                std::path::Path::new(file.file_name(cx))
+                    .extension()
+                    .is_some_and(|ext| ext.eq_ignore_ascii_case("svg"))
+            });
+
+        if is_markdown {
+            actions.push((
+                "Open Markdown Preview".into(),
+                Box::new(OpenMarkdownPreview) as Box<dyn gpui::Action>,
+            ));
+        }
+
+        if is_svg {
+            actions.push((
+                "Open SVG Preview".into(),
+                Box::new(OpenSvgPreview) as Box<dyn gpui::Action>,
+            ));
+        }
+
+        actions
+    }
+
     fn preserve_preview(&self, cx: &App) -> bool {
         self.buffer.read(cx).preserve_preview(cx)
     }

crates/editor/src/mouse_context_menu.rs 🔗

@@ -14,6 +14,9 @@ use std::ops::Range;
 use text::PointUtf16;
 use workspace::OpenInTerminal;
 use zed_actions::agent::AddSelectionToThread;
+use zed_actions::preview::{
+    markdown::OpenPreview as OpenMarkdownPreview, svg::OpenPreview as OpenSvgPreview,
+};
 
 #[derive(Debug)]
 pub enum MenuPosition {
@@ -218,6 +221,24 @@ pub fn deploy_context_menu(
         let run_to_cursor = window.is_action_available(&RunToCursor, cx);
         let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
 
+        let is_markdown = editor
+            .buffer()
+            .read(cx)
+            .as_singleton()
+            .and_then(|buffer| buffer.read(cx).language())
+            .is_some_and(|language| language.name().as_ref() == "Markdown");
+
+        let is_svg = editor
+            .buffer()
+            .read(cx)
+            .as_singleton()
+            .and_then(|buffer| buffer.read(cx).file())
+            .is_some_and(|file| {
+                std::path::Path::new(file.file_name(cx))
+                    .extension()
+                    .is_some_and(|ext| ext.eq_ignore_ascii_case("svg"))
+            });
+
         ui::ContextMenu::build(window, cx, |menu, _window, _cx| {
             let builder = menu
                 .on_blur_subscription(Subscription::new(|| {}))
@@ -272,6 +293,12 @@ pub fn deploy_context_menu(
                     },
                     Box::new(RevealInFileManager),
                 )
+                .when(is_markdown, |builder| {
+                    builder.action("Open Markdown Preview", Box::new(OpenMarkdownPreview))
+                })
+                .when(is_svg, |builder| {
+                    builder.action("Open SVG Preview", Box::new(OpenSvgPreview))
+                })
                 .action_disabled_when(
                     !has_reveal_target,
                     "Open in Terminal",

crates/markdown_preview/Cargo.toml 🔗

@@ -34,6 +34,7 @@ ui.workspace = true
 urlencoding.workspace = true
 util.workspace = true
 workspace.workspace = true
+zed_actions.workspace = true
 
 [dev-dependencies]
 editor = { workspace = true, features = ["test-support"] }

crates/markdown_preview/src/markdown_preview.rs 🔗

@@ -7,6 +7,8 @@ pub mod markdown_parser;
 pub mod markdown_preview_view;
 pub mod markdown_renderer;
 
+pub use zed_actions::preview::markdown::{OpenPreview, OpenPreviewToTheSide};
+
 actions!(
     markdown,
     [
@@ -24,10 +26,6 @@ actions!(
         ScrollUpByItem,
         /// Scrolls down by one markdown element in the markdown preview
         ScrollDownByItem,
-        /// Opens a markdown preview for the current file.
-        OpenPreview,
-        /// Opens a markdown preview in a split pane.
-        OpenPreviewToTheSide,
         /// Opens a following markdown preview that syncs with the editor.
         OpenFollowingPreview
     ]

crates/svg_preview/Cargo.toml 🔗

@@ -18,3 +18,4 @@ gpui.workspace = true
 language.workspace = true
 ui.workspace = true
 workspace.workspace = true
+zed_actions.workspace = true

crates/svg_preview/src/svg_preview.rs 🔗

@@ -3,13 +3,11 @@ use workspace::Workspace;
 
 pub mod svg_preview_view;
 
+pub use zed_actions::preview::svg::{OpenPreview, OpenPreviewToTheSide};
+
 actions!(
     svg,
     [
-        /// Opens an SVG preview for the current file.
-        OpenPreview,
-        /// Opens an SVG preview in a split pane.
-        OpenPreviewToTheSide,
         /// Opens a following SVG preview that syncs with the editor.
         OpenFollowingPreview
     ]

crates/zed_actions/src/lib.rs 🔗

@@ -651,3 +651,33 @@ pub mod wsl_actions {
         pub create_new_window: bool,
     }
 }
+
+pub mod preview {
+    pub mod markdown {
+        use gpui::actions;
+
+        actions!(
+            markdown,
+            [
+                /// Opens a markdown preview for the current file.
+                OpenPreview,
+                /// Opens a markdown preview in a split pane.
+                OpenPreviewToTheSide,
+            ]
+        );
+    }
+
+    pub mod svg {
+        use gpui::actions;
+
+        actions!(
+            svg,
+            [
+                /// Opens an SVG preview for the current file.
+                OpenPreview,
+                /// Opens an SVG preview in a split pane.
+                OpenPreviewToTheSide,
+            ]
+        );
+    }
+}