From 19c9fb3118ff82bca356b9fca26e69f7299ab628 Mon Sep 17 00:00:00 2001 From: ddoemonn <109994179+ddoemonn@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:43:00 +0300 Subject: [PATCH] Allow multiple Markdown preview tabs (#32859) Closes #32791 https://github.com/user-attachments/assets/8cb90e3d-ef7b-407f-b78b-7ba4ff6d8df2 Release Notes: - Allowed multiple Markdown preview tabs --- .../markdown_preview/src/markdown_preview.rs | 5 +- .../src/markdown_preview_view.rs | 76 +++++++++++++++++-- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/crates/markdown_preview/src/markdown_preview.rs b/crates/markdown_preview/src/markdown_preview.rs index de3554286b..fad6355d8a 100644 --- a/crates/markdown_preview/src/markdown_preview.rs +++ b/crates/markdown_preview/src/markdown_preview.rs @@ -6,7 +6,10 @@ pub mod markdown_parser; pub mod markdown_preview_view; pub mod markdown_renderer; -actions!(markdown, [OpenPreview, OpenPreviewToTheSide]); +actions!( + markdown, + [OpenPreview, OpenPreviewToTheSide, OpenFollowingPreview] +); pub fn init(cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, window, cx| { diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index c9c32e216a..40c1783482 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -20,7 +20,7 @@ use workspace::{Pane, Workspace}; use crate::OpenPreviewToTheSide; use crate::markdown_elements::ParsedMarkdownElement; use crate::{ - OpenPreview, + OpenFollowingPreview, OpenPreview, markdown_elements::ParsedMarkdown, markdown_parser::parse_markdown, markdown_renderer::{RenderContext, render_markdown_block}, @@ -39,6 +39,7 @@ pub struct MarkdownPreviewView { tab_content_text: Option, language_registry: Arc, parsing_markdown_task: Option>>, + mode: MarkdownPreviewMode, } #[derive(Clone, Copy, Debug, PartialEq)] @@ -58,9 +59,11 @@ impl MarkdownPreviewView { pub fn register(workspace: &mut Workspace, _window: &mut Window, _cx: &mut Context) { workspace.register_action(move |workspace, _: &OpenPreview, window, cx| { if let Some(editor) = Self::resolve_active_item_as_markdown_editor(workspace, cx) { - let view = Self::create_markdown_view(workspace, editor, window, cx); + let view = Self::create_markdown_view(workspace, editor.clone(), window, cx); workspace.active_pane().update(cx, |pane, cx| { - if let Some(existing_view_idx) = Self::find_existing_preview_item_idx(pane) { + if let Some(existing_view_idx) = + Self::find_existing_independent_preview_item_idx(pane, &editor, cx) + { pane.activate_item(existing_view_idx, true, true, window, cx); } else { pane.add_item(Box::new(view.clone()), true, true, None, window, cx) @@ -84,7 +87,9 @@ impl MarkdownPreviewView { ) }); pane.update(cx, |pane, cx| { - if let Some(existing_view_idx) = Self::find_existing_preview_item_idx(pane) { + if let Some(existing_view_idx) = + Self::find_existing_independent_preview_item_idx(pane, &editor, cx) + { pane.activate_item(existing_view_idx, true, true, window, cx); } else { pane.add_item(Box::new(view.clone()), false, false, None, window, cx) @@ -94,11 +99,49 @@ impl MarkdownPreviewView { cx.notify(); } }); + + workspace.register_action(move |workspace, _: &OpenFollowingPreview, window, cx| { + if let Some(editor) = Self::resolve_active_item_as_markdown_editor(workspace, cx) { + // Check if there's already a following preview + let existing_follow_view_idx = { + let active_pane = workspace.active_pane().read(cx); + active_pane + .items_of_type::() + .find(|view| view.read(cx).mode == MarkdownPreviewMode::Follow) + .and_then(|view| active_pane.index_for_item(&view)) + }; + + if let Some(existing_follow_view_idx) = existing_follow_view_idx { + workspace.active_pane().update(cx, |pane, cx| { + pane.activate_item(existing_follow_view_idx, true, true, window, cx); + }); + } else { + let view = + Self::create_following_markdown_view(workspace, editor.clone(), window, cx); + workspace.active_pane().update(cx, |pane, cx| { + pane.add_item(Box::new(view.clone()), true, true, None, window, cx) + }); + } + cx.notify(); + } + }); } - fn find_existing_preview_item_idx(pane: &Pane) -> Option { + fn find_existing_independent_preview_item_idx( + pane: &Pane, + editor: &Entity, + cx: &App, + ) -> Option { pane.items_of_type::() - .nth(0) + .find(|view| { + let view_read = view.read(cx); + // Only look for independent (Default mode) previews, not Follow previews + view_read.mode == MarkdownPreviewMode::Default + && view_read + .active_editor + .as_ref() + .is_some_and(|active_editor| active_editor.editor == *editor) + }) .and_then(|view| pane.index_for_item(&view)) } @@ -122,6 +165,25 @@ impl MarkdownPreviewView { editor: Entity, window: &mut Window, cx: &mut Context, + ) -> Entity { + let language_registry = workspace.project().read(cx).languages().clone(); + let workspace_handle = workspace.weak_handle(); + MarkdownPreviewView::new( + MarkdownPreviewMode::Default, + editor, + workspace_handle, + language_registry, + None, + window, + cx, + ) + } + + fn create_following_markdown_view( + workspace: &mut Workspace, + editor: Entity, + window: &mut Window, + cx: &mut Context, ) -> Entity { let language_registry = workspace.project().read(cx).languages().clone(); let workspace_handle = workspace.weak_handle(); @@ -266,6 +328,7 @@ impl MarkdownPreviewView { language_registry, parsing_markdown_task: None, image_cache: RetainAllImageCache::new(cx), + mode, }; this.set_editor(active_editor, window, cx); @@ -343,6 +406,7 @@ impl MarkdownPreviewView { ); let tab_content = editor.read(cx).tab_content_text(0, cx); + if self.tab_content_text.is_none() { self.tab_content_text = Some(format!("Preview {}", tab_content).into()); }