diff --git a/Cargo.lock b/Cargo.lock index 908069954d..e3d8c00a8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10434,6 +10434,7 @@ dependencies = [ "project", "settings", "tempfile", + "theme", "theme_settings", "ui", "urlencoding", diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index c2d8e45f16..e07592f093 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -113,15 +113,32 @@ impl Default for MarkdownStyle { } } +#[derive(Clone, Copy)] pub enum MarkdownFont { Agent, Editor, + Preview, } impl MarkdownStyle { pub fn themed(font: MarkdownFont, window: &Window, cx: &App) -> Self { - let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); + let syntax = cx.theme().syntax().clone(); + Self::themed_with_overrides(font, colors, &syntax, window, cx) + } + + /// Like [`Self::themed`], but takes explicit [`ThemeColors`] and + /// [`SyntaxTheme`] so callers (e.g. the markdown preview) can render the + /// markdown using a theme other than the active editor theme. + pub fn themed_with_overrides( + font: MarkdownFont, + colors: &theme::ThemeColors, + syntax: &Arc, + window: &Window, + cx: &App, + ) -> Self { + let theme_settings = ThemeSettings::get_global(cx); + let is_preview = matches!(font, MarkdownFont::Preview); let buffer_font_weight = theme_settings.buffer_font.weight; let (buffer_font_size, ui_font_size) = match font { @@ -129,19 +146,25 @@ impl MarkdownStyle { theme_settings.agent_buffer_font_size(cx), theme_settings.agent_ui_font_size(cx), ), - MarkdownFont::Editor => ( + MarkdownFont::Editor | MarkdownFont::Preview => ( theme_settings.buffer_font_size(cx), theme_settings.ui_font_size(cx), ), }; + let body_font_family = if is_preview { + theme_settings.markdown_preview_font_family().clone() + } else { + theme_settings.ui_font.family.clone() + }; + let text_color = colors.text; let mut text_style = window.text_style(); let line_height = buffer_font_size * 1.75; text_style.refine(&TextStyleRefinement { - font_family: Some(theme_settings.ui_font.family.clone()), + font_family: Some(body_font_family), font_fallbacks: theme_settings.ui_font.fallbacks.clone(), font_features: Some(theme_settings.ui_font.features.clone()), font_size: Some(ui_font_size.into()), @@ -152,7 +175,7 @@ impl MarkdownStyle { MarkdownStyle { base_text_style: text_style.clone(), - syntax: cx.theme().syntax().clone(), + syntax: syntax.clone(), selection_background_color: colors.element_selection_background, rule_color: colors.border, block_quote_border_color: colors.border, diff --git a/crates/markdown_preview/Cargo.toml b/crates/markdown_preview/Cargo.toml index 3a07b258c5..bdb90deb19 100644 --- a/crates/markdown_preview/Cargo.toml +++ b/crates/markdown_preview/Cargo.toml @@ -23,6 +23,7 @@ log.workspace = true markdown.workspace = true project.workspace = true settings.workspace = true +theme.workspace = true theme_settings.workspace = true ui.workspace = true urlencoding.workspace = true diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index 2b7379b0b4..456ecdf748 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -20,6 +20,7 @@ use markdown::{ }; use project::search::SearchQuery; use settings::Settings; +use theme::{SystemAppearance, Theme, ThemeRegistry}; use theme_settings::ThemeSettings; use ui::{ContextMenu, WithScrollbar, prelude::*, right_click_menu}; use util::markdown::split_local_url_fragment; @@ -578,8 +579,18 @@ impl MarkdownPreviewView { cx.notify(); } + /// Returns the theme chosen in `markdown_preview_theme`, or `None` if the + /// user hasn't set one or it can't be resolved. + fn resolve_preview_theme(&self, cx: &App) -> Option> { + let theme_settings = ThemeSettings::get_global(cx); + let theme_selection = theme_settings.markdown_preview_theme.as_ref()?; + let theme_name = theme_selection.name(SystemAppearance::global(cx).0); + ThemeRegistry::global(cx).get(&theme_name.0).ok() + } + fn render_markdown_element( &self, + preview_theme: &Option>, window: &mut Window, cx: &mut Context, ) -> MarkdownElement { @@ -596,41 +607,50 @@ impl MarkdownPreviewView { } } - let mut markdown_element = MarkdownElement::new( - self.markdown.clone(), - MarkdownStyle::themed(MarkdownFont::Editor, window, cx), - ) - .code_block_renderer(CodeBlockRenderer::Default { - copy_button_visibility: CopyButtonVisibility::VisibleOnHover, - border: false, - }) - .scroll_handle(self.scroll_handle.clone()) - .show_root_block_markers() - .image_resolver({ - let base_directory = self.base_directory.clone(); - move |dest_url| { - resolve_preview_image( - dest_url, - base_directory.as_deref(), - workspace_directory.as_deref(), - ) - } - }) - .on_url_click({ - let view_handle = cx.entity().downgrade(); - let workspace = self.workspace.clone(); - let base_directory = self.base_directory.clone(); - move |url, window, cx| { - handle_url_click( - url, - &view_handle, - base_directory.clone(), - &workspace, - window, - cx, - ); - } - }); + let markdown_style = if let Some(theme) = preview_theme { + MarkdownStyle::themed_with_overrides( + MarkdownFont::Preview, + theme.colors(), + theme.syntax(), + window, + cx, + ) + } else { + MarkdownStyle::themed(MarkdownFont::Preview, window, cx) + }; + + let mut markdown_element = MarkdownElement::new(self.markdown.clone(), markdown_style) + .code_block_renderer(CodeBlockRenderer::Default { + copy_button_visibility: CopyButtonVisibility::VisibleOnHover, + border: false, + }) + .scroll_handle(self.scroll_handle.clone()) + .show_root_block_markers() + .image_resolver({ + let base_directory = self.base_directory.clone(); + move |dest_url| { + resolve_preview_image( + dest_url, + base_directory.as_deref(), + workspace_directory.as_deref(), + ) + } + }) + .on_url_click({ + let view_handle = cx.entity().downgrade(); + let workspace = self.workspace.clone(); + let base_directory = self.base_directory.clone(); + move |url, window, cx| { + handle_url_click( + url, + &view_handle, + base_directory.clone(), + &workspace, + window, + cx, + ); + } + }); if let Some(active_editor) = active_editor { let editor_for_checkbox = active_editor.clone(); @@ -878,6 +898,11 @@ impl Item for MarkdownPreviewView { impl Render for MarkdownPreviewView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let preview_theme = self.resolve_preview_theme(cx); + let bg_color = preview_theme + .as_ref() + .map(|theme| theme.colors().editor_background) + .unwrap_or_else(|| cx.theme().colors().editor_background); div() .image_cache(self.image_cache.clone()) .id("MarkdownPreview") @@ -892,7 +917,7 @@ impl Render for MarkdownPreviewView { .on_action(cx.listener(MarkdownPreviewView::scroll_to_top)) .on_action(cx.listener(MarkdownPreviewView::scroll_to_bottom)) .size_full() - .bg(cx.theme().colors().editor_background) + .bg(bg_color) .child( div() .id("markdown-preview-scroll-container") @@ -901,7 +926,8 @@ impl Render for MarkdownPreviewView { .track_scroll(&self.scroll_handle) .p_4() .child({ - let markdown_element = self.render_markdown_element(window, cx); + let markdown_element = + self.render_markdown_element(&preview_theme, window, cx); let markdown = self.markdown.clone(); right_click_menu("markdown-preview-context-menu") .trigger(move |_, _, _| markdown_element) diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index e6f79bf70d..31bcaf847b 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -975,6 +975,8 @@ impl VsCodeSettings { buffer_font_features: None, agent_ui_font_size: None, agent_buffer_font_size: None, + markdown_preview_font_family: None, + markdown_preview_theme: None, theme: None, icon_theme: None, ui_density: None, diff --git a/crates/settings_content/src/theme.rs b/crates/settings_content/src/theme.rs index 623e463cfb..6b7fa8fbd6 100644 --- a/crates/settings_content/src/theme.rs +++ b/crates/settings_content/src/theme.rs @@ -149,6 +149,12 @@ pub struct ThemeSettingsContent { pub agent_ui_font_size: Option, /// The font size for user messages in the agent panel. pub agent_buffer_font_size: Option, + /// The name of a font to use for rendering in the markdown preview. + /// Falls back to the UI font if unset. + pub markdown_preview_font_family: Option, + /// The theme to use for the markdown preview. + /// Falls back to the main editor theme if unset. + pub markdown_preview_theme: Option, /// The name of the Zed theme to use. pub theme: Option, /// The name of the icon theme to use. diff --git a/crates/theme_settings/src/settings.rs b/crates/theme_settings/src/settings.rs index 7b8261d27b..92e3ff678f 100644 --- a/crates/theme_settings/src/settings.rs +++ b/crates/theme_settings/src/settings.rs @@ -4,7 +4,8 @@ use crate::schema::{status_colors_refinement, syntax_overrides, theme_colors_ref use crate::{merge_accent_colors, merge_player_colors}; use collections::HashMap; use gpui::{ - App, Context, Font, FontFallbacks, FontStyle, Global, Pixels, Subscription, Window, px, + App, Context, Font, FontFallbacks, FontStyle, Global, Pixels, SharedString, Subscription, + Window, px, }; use refineable::Refineable; use schemars::JsonSchema; @@ -55,6 +56,12 @@ pub struct ThemeSettings { agent_ui_font_size: Option, /// The agent buffer font size. Determines the size of user messages in the agent panel. agent_buffer_font_size: Option, + /// The font family to use for rendering in the markdown preview. + /// Falls back to the UI font family if unset. + markdown_preview_font_family: Option, + /// The theme to use for the markdown preview. + /// Falls back to the main editor theme if unset. + pub markdown_preview_theme: Option, /// The line height for buffers, and the terminal. /// /// Changing this may affect the spacing of some UI elements. @@ -394,6 +401,14 @@ impl ThemeSettings { .unwrap_or_else(|| self.buffer_font_size(cx)) } + /// Returns the font family to use in the markdown preview, + /// falling back to the UI font family when unset. + pub fn markdown_preview_font_family(&self) -> &SharedString { + self.markdown_preview_font_family + .as_ref() + .unwrap_or(&self.ui_font.family) + } + /// Returns the buffer font size, read from the settings. /// /// The real buffer font size is stored in-memory, to support temporary font size changes. @@ -624,6 +639,14 @@ impl settings::Settings for ThemeSettings { buffer_line_height: content.buffer_line_height.unwrap().into(), agent_ui_font_size: content.agent_ui_font_size.map(|s| s.into_gpui()), agent_buffer_font_size: content.agent_buffer_font_size.map(|s| s.into_gpui()), + markdown_preview_font_family: content + .markdown_preview_font_family + .as_ref() + .map(|f| f.0.clone().into()), + markdown_preview_theme: content + .markdown_preview_theme + .clone() + .map(ThemeSelection::from), theme: theme_selection, experimental_theme_overrides: content.experimental_theme_overrides.clone(), theme_overrides: content.theme_overrides.clone(),