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
This commit is contained in:
ddoemonn 2025-06-25 19:43:00 +03:00 committed by GitHub
parent b0bab0bf9a
commit 19c9fb3118
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 74 additions and 7 deletions

View file

@ -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| {

View file

@ -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<SharedString>,
language_registry: Arc<LanguageRegistry>,
parsing_markdown_task: Option<Task<Result<()>>>,
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>) {
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::<MarkdownPreviewView>()
.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<usize> {
fn find_existing_independent_preview_item_idx(
pane: &Pane,
editor: &Entity<Editor>,
cx: &App,
) -> Option<usize> {
pane.items_of_type::<MarkdownPreviewView>()
.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<Editor>,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<MarkdownPreviewView> {
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<Editor>,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<MarkdownPreviewView> {
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());
}