From 38e0801393a765a62192d58daa933e54e4ce5717 Mon Sep 17 00:00:00 2001 From: "Willow (GHOST)" Date: Sun, 14 Dec 2025 11:50:22 +0000 Subject: [PATCH] feat: theme can overwrite code block background color --- docs/src/features/themes/definition.md | 18 ++++ src/code/highlighting.rs | 8 +- src/presentation/builder/snippet.rs | 4 +- src/theme/clean.rs | 127 +++++++++++++++++++++++-- src/theme/raw.rs | 72 +++++++++++++- 5 files changed, 217 insertions(+), 12 deletions(-) diff --git a/docs/src/features/themes/definition.md b/docs/src/features/themes/definition.md index 668e892e..553b09e3 100644 --- a/docs/src/features/themes/definition.md +++ b/docs/src/features/themes/definition.md @@ -277,6 +277,24 @@ code: vertical: 1 ``` +#### Background + +By default the code block background comes from theme given theme, but you're able to override it or disable it completely: + +```yaml +code: + # Use the theme's default background color (the default) + background: true + + # Disable the background (transparent) + background: false + + # Use a specific color + background: "898989" +``` + +This is particularly useful when combining presentation themes with code highlighting themes that have matching or conflicting backgrounds. For example, you might want to use a Catppuccin presentation theme with a Catppuccin code highlighting theme, but override the background to avoid having identical colors. + #### Custom highlighting themes Besides the built-in highlighting themes, you can drop any `.tmTheme` theme in the `themes/highlighting` directory under diff --git a/src/code/highlighting.rs b/src/code/highlighting.rs index 0c5bdfa5..616f4274 100644 --- a/src/code/highlighting.rs +++ b/src/code/highlighting.rs @@ -4,7 +4,7 @@ use crate::{ elements::{Line, Text}, text_style::{Color, TextStyle}, }, - theme::CodeBlockStyle, + theme::{CodeBlockStyle, CodeBlockStyleBackground}, }; use flate2::read::ZlibDecoder; use once_cell::sync::Lazy; @@ -228,8 +228,10 @@ pub(crate) struct StyledTokens<'a> { impl<'a> StyledTokens<'a> { pub(crate) fn new(style: Style, tokens: &'a str, block_style: &CodeBlockStyle) -> Self { - let has_background = block_style.background; - let background = has_background.then_some(parse_color(style.background)).flatten(); + let background = match block_style.background { + CodeBlockStyleBackground::Color(color) => color, + CodeBlockStyleBackground::Enabled(enabled) => enabled.then_some(parse_color(style.background)).flatten(), + }; let foreground = parse_color(style.foreground); let mut style = TextStyle::default(); style.colors.background = background; diff --git a/src/presentation/builder/snippet.rs b/src/presentation/builder/snippet.rs index 2574687b..f19f7462 100644 --- a/src/presentation/builder/snippet.rs +++ b/src/presentation/builder/snippet.rs @@ -14,7 +14,7 @@ use crate::{ operation::{AsRenderOperations, RenderAsyncStartPolicy, RenderOperation}, properties::WindowSize, }, - theme::{Alignment, CodeBlockStyle}, + theme::{Alignment, CodeBlockStyle, CodeBlockStyleBackground}, third_party::ThirdPartyRenderRequest, ui::execution::{ RunAcquireTerminalSnippet, RunImageSnippet, SnippetExecutionDisabledOperation, SnippetOutputOperation, @@ -302,7 +302,7 @@ impl PresentationBuilder<'_, '_> { fn code_style(&self, snippet: &Snippet) -> CodeBlockStyle { let mut style = self.theme.code.clone(); if snippet.attributes.no_background { - style.background = false; + style.background = CodeBlockStyleBackground::Enabled(false); } style } diff --git a/src/theme/clean.rs b/src/theme/clean.rs index 64e1fbd4..f7218613 100644 --- a/src/theme/clean.rs +++ b/src/theme/clean.rs @@ -89,7 +89,7 @@ impl PresentationTheme { let default_style = DefaultStyle::new(default_style, &palette)?; Ok(Self { slide_title: SlideTitleStyle::new(slide_title, &palette, options)?, - code: CodeBlockStyle::new(code), + code: CodeBlockStyle::new(code, &palette)?, execution_output: ExecutionOutputBlockStyle::new(execution_output, &palette)?, pty_output: PtyOutputBlockStyle::new(pty_output, &palette)?, inline_code: ModifierStyle::new(inline_code, &palette)?, @@ -569,29 +569,45 @@ impl FooterContent { } } +#[derive(Clone, Debug)] +pub(crate) enum CodeBlockStyleBackground { + Color(Option), + Enabled(bool), +} + +impl Default for CodeBlockStyleBackground { + fn default() -> Self { + CodeBlockStyleBackground::Enabled(true) + } +} + #[derive(Clone, Debug, Default)] pub(crate) struct CodeBlockStyle { pub(crate) alignment: Alignment, pub(crate) padding: PaddingRect, pub(crate) theme_name: String, - pub(crate) background: bool, + pub(crate) background: CodeBlockStyleBackground, pub(crate) line_numbers: bool, } impl CodeBlockStyle { - fn new(raw: &raw::CodeBlockStyle) -> Self { + fn new(raw: &raw::CodeBlockStyle, palette: &ColorPalette) -> Result { let raw::CodeBlockStyle { alignment, padding, theme_name, background, line_numbers } = raw; let padding = PaddingRect { horizontal: padding.horizontal.unwrap_or_default(), vertical: padding.vertical.unwrap_or_default(), }; - Self { + let background = match background.clone().unwrap_or_default() { + raw::CodeBlockStyleBackground::Color(color) => CodeBlockStyleBackground::Color(color.resolve(palette)?), + raw::CodeBlockStyleBackground::Enabled(enabled) => CodeBlockStyleBackground::Enabled(enabled), + }; + Ok(Self { alignment: alignment.clone().unwrap_or_default().into(), padding, theme_name: theme_name.as_deref().unwrap_or(DEFAULT_CODE_HIGHLIGHT_THEME).to_string(), - background: background.unwrap_or(true), + background, line_numbers: line_numbers.unwrap_or_default(), - } + }) } } @@ -708,6 +724,105 @@ impl PtyStandbyStyle { } } +#[cfg(test)] +mod test { + use super::*; + use crate::theme::raw; + + #[test] + fn code_block_style_background_enabled() { + let palette = ColorPalette::default(); + + let raw = raw::CodeBlockStyle { + alignment: None, + padding: Default::default(), + theme_name: Some("test".to_string()), + background: Some(raw::CodeBlockStyleBackground::Enabled(true)), + line_numbers: None, + }; + + let style = CodeBlockStyle::new(&raw, &palette).unwrap(); + assert!(matches!(style.background, CodeBlockStyleBackground::Enabled(true))); + } + + #[test] + fn code_block_style_background_disabled() { + let palette = ColorPalette::default(); + + let raw = raw::CodeBlockStyle { + alignment: None, + padding: Default::default(), + theme_name: None, + background: Some(raw::CodeBlockStyleBackground::Enabled(false)), + line_numbers: None, + }; + + let style = CodeBlockStyle::new(&raw, &palette).unwrap(); + assert!(matches!(style.background, CodeBlockStyleBackground::Enabled(false))); + } + + #[test] + fn code_block_style_background_color() { + let palette = ColorPalette::default(); + + let raw = raw::CodeBlockStyle { + alignment: None, + padding: Default::default(), + theme_name: None, + background: Some(raw::CodeBlockStyleBackground::Color(raw::RawColor::Color(Color::new(46, 52, 64)))), + line_numbers: None, + }; + + let style = CodeBlockStyle::new(&raw, &palette).unwrap(); + assert!( + matches!( + style.background, + CodeBlockStyleBackground::Color(Some(color)) if color == Color::new(46, 52, 64) + ), + "Expected Color variant with specific color Color::new(46, 52, 64)" + ); + } + + #[test] + fn code_block_style_background_palette_color() { + let mut palette = ColorPalette::default(); + palette.colors.insert("surface0".to_string(), Color::new(49, 50, 68)); + + let raw = raw::CodeBlockStyle { + alignment: None, + padding: Default::default(), + theme_name: None, + background: Some(raw::CodeBlockStyleBackground::Color(raw::RawColor::Palette("surface0".to_string()))), + line_numbers: None, + }; + + let style = CodeBlockStyle::new(&raw, &palette).unwrap(); + assert!( + matches!( + style.background, + CodeBlockStyleBackground::Color(Some(color)) if color == Color::new(49, 50, 68) + ), + "Expected Color variant with palette color Color::new(49, 50, 68)" + ); + } + + #[test] + fn code_block_style_background_default() { + let palette = ColorPalette::default(); + + let raw = raw::CodeBlockStyle { + alignment: None, + padding: Default::default(), + theme_name: None, + background: None, + line_numbers: None, + }; + + let style = CodeBlockStyle::new(&raw, &palette).unwrap(); + assert!(matches!(style.background, CodeBlockStyleBackground::Enabled(true))); + } +} + #[derive(Clone, Debug, Default)] pub(crate) struct ModifierStyle { pub(crate) style: TextStyle, diff --git a/src/theme/raw.rs b/src/theme/raw.rs index 224171ea..d0fa0ef1 100644 --- a/src/theme/raw.rs +++ b/src/theme/raw.rs @@ -657,6 +657,19 @@ pub(crate) enum ParseFooterTemplateError { UnsupportedVariable(String), } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub(crate) enum CodeBlockStyleBackground { + Color(RawColor), + Enabled(bool), +} + +impl Default for CodeBlockStyleBackground { + fn default() -> Self { + CodeBlockStyleBackground::Enabled(true) + } +} + /// The style for a piece of code. #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub(crate) struct CodeBlockStyle { @@ -673,7 +686,7 @@ pub(crate) struct CodeBlockStyle { pub(crate) theme_name: Option, /// Whether to use the theme's background color. - pub(crate) background: Option, + pub(crate) background: Option, /// Whether to show line numbers in all code blocks. #[serde(default)] @@ -1075,4 +1088,61 @@ mod test { let RawColor::Palette(name) = color else { panic!("not a palette color") }; assert_eq!(name, expected); } + + #[test] + fn code_block_background_serde() { + let bg_true: CodeBlockStyleBackground = serde_yaml::from_str("true").unwrap(); + assert!(matches!(bg_true, CodeBlockStyleBackground::Enabled(true))); + + let bg_false: CodeBlockStyleBackground = serde_yaml::from_str("false").unwrap(); + assert!(matches!(bg_false, CodeBlockStyleBackground::Enabled(false))); + + let bg_color: CodeBlockStyleBackground = serde_yaml::from_str("\"2e3440\"").unwrap(); + assert!( + matches!(bg_color, CodeBlockStyleBackground::Color(RawColor::Color(_))), + "Expected Color variant with hex color" + ); + + let bg_palette: CodeBlockStyleBackground = serde_yaml::from_str("\"palette:surface0\"").unwrap(); + assert!( + matches!(bg_palette, CodeBlockStyleBackground::Color(RawColor::Palette(ref name)) if name == "surface0"), + "Expected Color variant with palette color 'surface0'" + ); + } + + #[test] + fn code_block_background_default() { + let default = CodeBlockStyleBackground::default(); + assert!(matches!(default, CodeBlockStyleBackground::Enabled(true))); + } + + #[test] + fn code_block_style_with_background() { + let yaml = r#" +theme_name: "base16-mocha.dark" +background: false +"#; + let style: CodeBlockStyle = serde_yaml::from_str(yaml).unwrap(); + assert!(matches!(style.background, Some(CodeBlockStyleBackground::Enabled(false)))); + + let yaml = r##" +theme_name: "Catppuccin Mocha" +background: "2e3440" +"##; + let style: CodeBlockStyle = serde_yaml::from_str(yaml).unwrap(); + assert!( + matches!(style.background, Some(CodeBlockStyleBackground::Color(RawColor::Color(_)))), + "Expected Color variant with hex color" + ); + + let yaml = r##" +theme_name: "Catppuccin Frappe" +background: "palette:surface0" +"##; + let style: CodeBlockStyle = serde_yaml::from_str(yaml).unwrap(); + assert!( + matches!(style.background, Some(CodeBlockStyleBackground::Color(RawColor::Palette(ref name))) if name == "surface0"), + "Expected Palette variant with 'surface0'" + ); + } }