diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml index 5a5e0540..39bdcc3e 100644 --- a/.github/workflows/merge.yaml +++ b/.github/workflows/merge.yaml @@ -8,7 +8,7 @@ name: Merge checks jobs: check: - name: Check + name: Checks runs-on: ubuntu-latest steps: - name: Checkout sources @@ -16,40 +16,25 @@ jobs: - name: Install rust toolchain uses: dtolnay/rust-toolchain@1.78.0 + with: + components: clippy - name: Run cargo check run: cargo check --features sixel - test: - name: Tests - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - - name: Install rust toolchain - uses: dtolnay/rust-toolchain@1.78.0 - - name: Run cargo test run: cargo test - lints: - name: Lints - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 + - name: Run cargo clippy + run: cargo clippy -- -D warnings - - name: Install stable toolchain + - name: Install nightly toolchain uses: dtolnay/rust-toolchain@nightly with: - components: rustfmt, clippy + components: rustfmt - name: Run cargo fmt - run: cargo fmt --all -- --check - - - name: Run cargo clippy - run: cargo clippy -- -D warnings + run: cargo +nightly fmt --all -- --check nix-flake: name: Validate nix flake diff --git a/src/code/snippet.rs b/src/code/snippet.rs index 440f5da4..658d8510 100644 --- a/src/code/snippet.rs +++ b/src/code/snippet.rs @@ -745,13 +745,10 @@ mod test { #[test] fn highlight_line_range() { let attributes = parse_attributes("bash { 1, 2-4,6 , all , 10 - 12 }"); - assert_eq!(attributes.highlight_groups, &[HighlightGroup::new(vec![ - Single(1), - Range(2..5), - Single(6), - All, - Range(10..13) - ])]); + assert_eq!( + attributes.highlight_groups, + &[HighlightGroup::new(vec![Single(1), Range(2..5), Single(6), All, Range(10..13)])] + ); } #[test] diff --git a/src/presentation/builder.rs b/src/presentation/builder.rs index 43d8ad97..e1dbe4d7 100644 --- a/src/presentation/builder.rs +++ b/src/presentation/builder.rs @@ -733,21 +733,25 @@ impl<'a> PresentationBuilder<'a> { } fn push_alert(&mut self, alert_type: AlertType, title: Option, mut lines: Vec) -> BuildResult { - let (default_title, prefix_color) = match alert_type { - AlertType::Note => ("Note", self.theme.alert.colors.types.note), - AlertType::Tip => ("Tip", self.theme.alert.colors.types.tip), - AlertType::Important => ("Important", self.theme.alert.colors.types.important), - AlertType::Warning => ("Warning", self.theme.alert.colors.types.warning), - AlertType::Caution => ("Caution", self.theme.alert.colors.types.caution), + let (prefix_color, default_title, symbol) = match alert_type { + AlertType::Note => self.theme.alert.styles.note.as_parts(), + AlertType::Tip => self.theme.alert.styles.tip.as_parts(), + AlertType::Important => self.theme.alert.styles.important.as_parts(), + AlertType::Warning => self.theme.alert.styles.warning.as_parts(), + AlertType::Caution => self.theme.alert.styles.caution.as_parts(), }; - let prefix_color = prefix_color.or(self.theme.alert.colors.base.foreground); + let prefix_color = prefix_color.or(self.theme.alert.base_colors.foreground); let title = title.unwrap_or_else(|| default_title.to_string()); - let title_colors = Colors { foreground: prefix_color, background: self.theme.alert.colors.base.background }; + let title = match symbol { + Some(symbol) => format!("{symbol} {title}"), + None => title, + }; + let title_colors = Colors { foreground: prefix_color, background: self.theme.alert.base_colors.background }; lines.insert(0, Line::from(Text::from(""))); lines.insert(0, Line::from(Text::new(title, TextStyle::default().colors(title_colors)))); let prefix = self.theme.block_quote.prefix.clone().unwrap_or_default(); - self.push_quoted_text(lines, prefix, self.theme.alert.colors.base, prefix_color) + self.push_quoted_text(lines, prefix, self.theme.alert.base_colors, prefix_color) } fn push_quoted_text( @@ -1681,11 +1685,10 @@ mod test { #[test] fn iterate_list_starting_from_other() { let list = ListIterator::new( - vec![ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered }, ListItem { - depth: 0, - contents: "1".into(), - item_type: ListItemType::Unordered, - }], + vec![ + ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered }, + ListItem { depth: 0, contents: "1".into(), item_type: ListItemType::Unordered }, + ], 3, ); let expected_indexes = [3, 4]; @@ -1772,10 +1775,10 @@ mod test { #[test] fn implicit_slide_ends_with_front_matter() { - let elements = - vec![MarkdownElement::FrontMatter("theme:\n name: light".into()), MarkdownElement::SetexHeading { - text: "hi".into(), - }]; + let elements = vec![ + MarkdownElement::FrontMatter("theme:\n name: light".into()), + MarkdownElement::SetexHeading { text: "hi".into() }, + ]; let options = PresentationBuilderOptions { implicit_slide_ends: true, ..Default::default() }; let slides = build_presentation_with_options(elements, options).into_slides(); assert_eq!(slides.len(), 1); diff --git a/src/theme.rs b/src/theme.rs index 4ed812a4..afb3e709 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,6 +1,6 @@ use crate::markdown::text_style::{Color, Colors, FixedStr, UndefinedPaletteColorError}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fs, io, path::Path}; +use std::{collections::BTreeMap, fmt, fs, io, marker::PhantomData, path::Path}; include!(concat!(env!("OUT_DIR"), "/themes.rs")); @@ -395,7 +395,7 @@ pub(crate) struct BlockQuoteStyle { impl BlockQuoteStyle { fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> { - let Self { colors, alignment: _alignment, prefix: _prefix } = self; + let Self { colors, alignment: _, prefix: _ } = self; colors.resolve_palette_colors(palette)?; Ok(()) } @@ -431,82 +431,183 @@ pub(crate) struct AlertStyle { #[serde(flatten, default)] pub(crate) alignment: Option, + /// The base colors. + #[serde(default)] + pub(crate) base_colors: Colors, + /// The prefix to be added to this block quote. /// /// This allows adding something like a vertical bar before the text. #[serde(default)] pub(crate) prefix: Option, - /// The colors to be used. + /// The style for each alert type. #[serde(default)] - pub(crate) colors: AlertColors, + pub(crate) styles: AlertTypeStyles, } impl AlertStyle { fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> { - let Self { colors, alignment: _alignment, prefix: _prefix } = self; - colors.resolve_palette_colors(palette)?; + let Self { base_colors, styles, alignment: _, prefix: _ } = self; + *base_colors = base_colors.resolve(palette)?; + styles.resolve_palette_colors(palette)?; Ok(()) } } -/// The colors of an alert. +/// The style for each alert type. #[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub(crate) struct AlertColors { - /// The foreground/background colors. - #[serde(flatten)] - pub(crate) base: Colors, +pub(crate) struct AlertTypeStyles { + /// The style for note alert types. + #[serde(default)] + pub(crate) note: AlertTypeStyle, - /// The color of the vertical bar that prefixes each line in the quote. + /// The style for tip alert types. #[serde(default)] - pub(crate) types: AlertTypeColors, + pub(crate) tip: AlertTypeStyle, + + /// The style for important alert types. + #[serde(default)] + pub(crate) important: AlertTypeStyle, + + /// The style for warning alert types. + #[serde(default)] + pub(crate) warning: AlertTypeStyle, + + /// The style for caution alert types. + #[serde(default)] + pub(crate) caution: AlertTypeStyle, } -impl AlertColors { +impl AlertTypeStyles { fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> { - let Self { base, types } = self; - *base = base.resolve(palette)?; - types.resolve_palette_colors(palette)?; + let Self { note, tip, important, warning, caution } = self; + note.resolve_palette_colors(palette)?; + tip.resolve_palette_colors(palette)?; + important.resolve_palette_colors(palette)?; + warning.resolve_palette_colors(palette)?; + caution.resolve_palette_colors(palette)?; Ok(()) } } -/// The colors of each alert type. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub(crate) struct AlertTypeColors { - /// The color for note type alerts. - #[serde(default)] - pub(crate) note: Option, - - /// The color for tip type alerts. +/// The style for an alert type. +#[derive(Deserialize, Serialize)] +pub(crate) struct AlertTypeStyle { + /// The color to be used. #[serde(default)] - pub(crate) tip: Option, + pub(crate) color: Option, - /// The color for important type alerts. - #[serde(default)] - pub(crate) important: Option, + /// The title to be used. + #[serde(default = "S::default_title")] + pub(crate) title: String, - /// The color for warning type alerts. - #[serde(default)] - pub(crate) warning: Option, + /// The symbol to be used. + #[serde(default = "S::default_symbol")] + pub(crate) symbol: Option, - /// The color for caution type alerts. - #[serde(default)] - pub(crate) caution: Option, + #[serde(skip)] + _unused: PhantomData, } -impl AlertTypeColors { +impl AlertTypeStyle { + pub(crate) fn as_parts(&self) -> (&Option, &str, Option<&str>) { + (&self.color, &self.title, self.symbol.as_deref()) + } + fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> { - let Self { note, tip, important, warning, caution } = self; - for c in [note, tip, important, warning, caution] { - if let Some(c) = c.as_mut() { - *c = c.resolve(palette)?; - } + let Self { color, title: _, symbol: _, _unused: _ } = self; + if let Some(color) = color.as_mut() { + *color = color.resolve(palette)?; } Ok(()) } } +impl fmt::Debug for AlertTypeStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AlertTypeStyle") + .field("color", &self.color) + .field("title", &self.title) + .field("symbol", &self.symbol) + .field("_unused", &self._unused) + .finish() + } +} + +impl Clone for AlertTypeStyle { + fn clone(&self) -> Self { + Self { color: self.color, title: self.title.clone(), symbol: self.symbol.clone(), _unused: PhantomData } + } +} + +impl Default for AlertTypeStyle { + fn default() -> Self { + Self { color: None, title: S::default_title(), symbol: S::default_symbol(), _unused: PhantomData } + } +} + +pub(crate) trait AlertTypeProperties { + fn default_title() -> String; + fn default_symbol() -> Option; +} + +pub(crate) struct NoteAlertType; +pub(crate) struct TipAlertType; +pub(crate) struct ImportantAlertType; +pub(crate) struct WarningAlertType; +pub(crate) struct CautionAlertType; + +impl AlertTypeProperties for NoteAlertType { + fn default_title() -> String { + "Note".into() + } + + fn default_symbol() -> Option { + Some("󰋽".into()) + } +} + +impl AlertTypeProperties for TipAlertType { + fn default_title() -> String { + "Tip".into() + } + + fn default_symbol() -> Option { + Some("".into()) + } +} + +impl AlertTypeProperties for ImportantAlertType { + fn default_title() -> String { + "Important".into() + } + + fn default_symbol() -> Option { + Some("".into()) + } +} + +impl AlertTypeProperties for WarningAlertType { + fn default_title() -> String { + "Warning".into() + } + + fn default_symbol() -> Option { + Some("".into()) + } +} + +impl AlertTypeProperties for CautionAlertType { + fn default_title() -> String { + "Caution".into() + } + + fn default_symbol() -> Option { + Some("󰳦".into()) + } +} + /// The style for the presentation introduction slide. #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub(crate) struct IntroSlideStyle { diff --git a/src/ui/execution.rs b/src/ui/execution.rs index cfd85cee..4e791220 100644 --- a/src/ui/execution.rs +++ b/src/ui/execution.rs @@ -473,12 +473,15 @@ impl AsRenderOperations for RunImageSnippet { match state.deref() { RunImageSnippetState::NotStarted | RunImageSnippetState::Running(_) => vec![], RunImageSnippetState::Success(image) => { - vec![RenderOperation::RenderImage(image.clone(), ImageRenderProperties { - z_index: 0, - size: ImageSize::ShrinkIfNeeded, - restore_cursor: false, - background_color: None, - })] + vec![RenderOperation::RenderImage( + image.clone(), + ImageRenderProperties { + z_index: 0, + size: ImageSize::ShrinkIfNeeded, + restore_cursor: false, + background_color: None, + }, + )] } RunImageSnippetState::Failure(lines) => { let mut output = Vec::new(); diff --git a/src/ui/footer.rs b/src/ui/footer.rs index 8214332f..e97c4103 100644 --- a/src/ui/footer.rs +++ b/src/ui/footer.rs @@ -82,10 +82,13 @@ impl AsRenderOperations for FooterGenerator { let columns_ratio = (total_columns as f64 * progress_ratio).ceil(); let bar = character.repeat(columns_ratio as usize); let bar = Text::new(bar, TextStyle::default().colors(*colors)); - vec![RenderOperation::JumpToBottomRow { index: 0 }, RenderOperation::RenderText { - line: vec![bar].into(), - alignment: Alignment::Left { margin: Margin::Fixed(0) }, - }] + vec![ + RenderOperation::JumpToBottomRow { index: 0 }, + RenderOperation::RenderText { + line: vec![bar].into(), + alignment: Alignment::Left { margin: Margin::Fixed(0) }, + }, + ] } FooterStyle::Empty => vec![], } diff --git a/themes/dark.yaml b/themes/dark.yaml index dfd95241..13c18dbe 100644 --- a/themes/dark.yaml +++ b/themes/dark.yaml @@ -106,15 +106,20 @@ block_quote: alert: prefix: "▍ " - colors: + base_colors: foreground: palette:light_gray background: palette:blue_gray - types: - note: palette:blue - tip: palette:light_green - important: palette:purple - warning: palette:orange - caution: palette:red + styles: + note: + color: palette:blue + tip: + color: palette:light_green + important: + color: palette:purple + warning: + color: palette:orange + caution: + color: palette:red typst: colors: