mirror of
https://github.com/zed-industries/zed.git
synced 2026-07-10 00:13:29 +00:00
project_panel: Add markdown preview context menu item (#57112)
Opening a markdown file from the project panel used to require four steps: - click the file (opens raw editor) - right-click the editor tab - select "Open Markdown Preview" - then close the raw editor tab This PR shortcuts that to a single right-click (or keypress) from the project panel. Right-clicking a markdown file in the project panel now shows an **Open Markdown Preview** option. Selecting it opens the rendered markdown preview directly — no raw editor tab is left behind. The same shortcut used in the editor (Cmd+Shift+V on macOS, Ctrl+Shift+V on Windows/Linux) also works when focus is in the project panel. ## How it works - **Context menu**: `OpenMarkdownPreview` action is registered and surfaced in the right-click menu only for `.md`/`.markdown` files. - **Keybinding**: `cmd-shift-v` / `ctrl-shift-v` bound to `project_panel::OpenMarkdownPreview` in the `ProjectPanel` context, mirroring the existing editor binding. - **Loading**: The file is opened via `open_path_preview` with `focus_item: false, activate: false`, so the raw editor is loaded into the buffer system but never becomes the visible tab. - **Preview construction**: `MarkdownPreviewView::create_markdown_view` is called directly (no `dispatch_action` indirection, which would have been deferred and caused a race). The preview is added to the active pane and the raw editor tab is removed atomically in one synchronous pane update. - **Pre-existing tabs**: Before loading, all panes are checked for an existing item at the file's project path. If the file is already open in raw mode, `remove_item` is skipped — the existing tab is left untouched. Release Notes: - Added "Open Markdown Preview" context menu item to Project Panel markdown entries. --------- Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com> Co-authored-by: Tom Houlé <tom@tomhoule.com>
This commit is contained in:
parent
eb87750323
commit
db30c67ed2
4 changed files with 76 additions and 2 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -14057,6 +14057,7 @@ dependencies = [
|
|||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"markdown_preview",
|
||||
"menu",
|
||||
"notifications",
|
||||
"pretty_assertions",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use ui::utils::WithRemSize;
|
|||
use ui::{ContextMenu, WithScrollbar, prelude::*, right_click_menu};
|
||||
use util::markdown::split_local_url_fragment;
|
||||
use workspace::item::{Item, ItemBufferKind, ItemHandle, SaveOptions, SerializableItem};
|
||||
use workspace::notifications::NotifyResultExt;
|
||||
use workspace::searchable::{
|
||||
Direction, SearchEvent, SearchOptions, SearchToken, SearchableItem, SearchableItemHandle,
|
||||
};
|
||||
|
|
@ -208,7 +209,7 @@ impl MarkdownPreviewView {
|
|||
None
|
||||
}
|
||||
|
||||
fn create_markdown_view(
|
||||
pub fn create_markdown_view(
|
||||
workspace: &mut Workspace,
|
||||
editor: Entity<Editor>,
|
||||
window: &mut Window,
|
||||
|
|
@ -337,6 +338,43 @@ impl MarkdownPreviewView {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_markdown_path(path: impl AsRef<Path>) -> bool {
|
||||
path.as_ref().extension().is_some_and(|ext| {
|
||||
ext.eq_ignore_ascii_case("md") || ext.eq_ignore_ascii_case("markdown")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_for_project_path(
|
||||
project_path: ProjectPath,
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let open_buffer = workspace
|
||||
.project()
|
||||
.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
||||
|
||||
cx.spawn_in(window, async move |workspace, mut cx| {
|
||||
let Some(buffer) = open_buffer
|
||||
.await
|
||||
.notify_workspace_async_err(workspace.clone(), &mut cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let editor = cx.new(|cx| Editor::for_buffer(buffer, Some(project), window, cx));
|
||||
let preview = Self::create_markdown_view(workspace, editor, window, cx);
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.add_item(Box::new(preview), true, true, None, window, cx);
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn is_markdown_file<V>(editor: &Entity<Editor>, cx: &mut Context<V>) -> bool {
|
||||
let buffer = editor.read(cx).buffer().read(cx);
|
||||
if let Some(buffer) = buffer.as_singleton()
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ client.workspace = true
|
|||
worktree.workspace = true
|
||||
workspace.workspace = true
|
||||
language.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
zed_actions.workspace = true
|
||||
telemetry.workspace = true
|
||||
notifications.workspace = true
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use gpui::{
|
|||
point, px, size, transparent_white, uniform_list,
|
||||
};
|
||||
use language::DiagnosticSeverity;
|
||||
use markdown_preview::markdown_preview_view::MarkdownPreviewView;
|
||||
use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use project::{
|
||||
|
|
@ -405,6 +406,8 @@ actions!(
|
|||
Undo,
|
||||
/// Redoes the last undone file operation.
|
||||
Redo,
|
||||
/// Opens a markdown preview for the selected file.
|
||||
OpenMarkdownPreview,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -1091,6 +1094,7 @@ impl ProjectPanel {
|
|||
let is_remote = project.is_remote();
|
||||
let is_collab = project.is_via_collab();
|
||||
let is_local = project.is_local() || project.is_via_wsl_with_host_interop(cx);
|
||||
let is_markdown = !is_dir && MarkdownPreviewView::is_markdown_path(&*entry.path);
|
||||
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
let visible_worktrees_count = project.visible_worktrees(cx).count();
|
||||
|
|
@ -1120,7 +1124,10 @@ impl ProjectPanel {
|
|||
let context_menu = ContextMenu::build(window, cx, |menu, _, cx| {
|
||||
menu.context(self.focus_handle.clone()).map(|menu| {
|
||||
if is_read_only {
|
||||
menu.when(is_dir, |menu| {
|
||||
menu.when(is_markdown, |menu| {
|
||||
menu.action("Open Markdown Preview", Box::new(OpenMarkdownPreview))
|
||||
})
|
||||
.when(is_dir, |menu| {
|
||||
menu.action("Search Inside", Box::new(NewSearchInDirectory))
|
||||
})
|
||||
} else {
|
||||
|
|
@ -1137,6 +1144,9 @@ impl ProjectPanel {
|
|||
menu.action("Open in Default App", Box::new(OpenWithSystem))
|
||||
})
|
||||
.action("Open in Terminal", Box::new(OpenInTerminal))
|
||||
.when(is_markdown, |menu| {
|
||||
menu.action("Open Markdown Preview", Box::new(OpenMarkdownPreview))
|
||||
})
|
||||
.when(is_dir, |menu| {
|
||||
menu.separator()
|
||||
.action("Find in Folder…", Box::new(NewSearchInDirectory))
|
||||
|
|
@ -1685,6 +1695,29 @@ impl ProjectPanel {
|
|||
);
|
||||
}
|
||||
|
||||
fn open_markdown_preview(
|
||||
&mut self,
|
||||
_: &OpenMarkdownPreview,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some((worktree, entry)) = self.selected_entry(cx) else {
|
||||
return;
|
||||
};
|
||||
if !entry.is_file() || !MarkdownPreviewView::is_markdown_path(&*entry.path) {
|
||||
return;
|
||||
}
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: entry.path.clone(),
|
||||
};
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
MarkdownPreviewView::open_for_project_path(project_path, workspace, window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn open_internal(
|
||||
&mut self,
|
||||
allow_preview: bool,
|
||||
|
|
@ -6769,6 +6802,7 @@ impl Render for ProjectPanel {
|
|||
.on_action(cx.listener(Self::open_permanent))
|
||||
.on_action(cx.listener(Self::open_split_vertical))
|
||||
.on_action(cx.listener(Self::open_split_horizontal))
|
||||
.on_action(cx.listener(Self::open_markdown_preview))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::copy_path))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue