Skip to content

Commit f6daf38

Browse files
authored
feat: add symbols to alerts (#430)
This is a follow up on #423 that adds symbols before the title of alert type elements. The structure of the theme changed a bit to be able to pack the styles for each alert type into a single type. This causes the following presentation: ```markdown > [!note] > this is a note > [!tip] > this is a tip > [!important] > this is important > [!warning] > this is warning! > [!caution] > this advises caution! >>> [!note] other title ez multiline >>> ``` To render like this: ![image](https://github.com/user-attachments/assets/ce90a4e0-7543-4100-83b2-5f8d86f4cbe2)
2 parents 22af066 + 7643b8f commit f6daf38

File tree

7 files changed

+203
-106
lines changed

7 files changed

+203
-106
lines changed

.github/workflows/merge.yaml

+8-23
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,33 @@ name: Merge checks
88

99
jobs:
1010
check:
11-
name: Check
11+
name: Checks
1212
runs-on: ubuntu-latest
1313
steps:
1414
- name: Checkout sources
1515
uses: actions/checkout@v4
1616

1717
- name: Install rust toolchain
1818
uses: dtolnay/[email protected]
19+
with:
20+
components: clippy
1921

2022
- name: Run cargo check
2123
run: cargo check --features sixel
2224

23-
test:
24-
name: Tests
25-
runs-on: ubuntu-latest
26-
steps:
27-
- name: Checkout sources
28-
uses: actions/checkout@v4
29-
30-
- name: Install rust toolchain
31-
uses: dtolnay/[email protected]
32-
3325
- name: Run cargo test
3426
run: cargo test
3527

36-
lints:
37-
name: Lints
38-
runs-on: ubuntu-latest
39-
steps:
40-
- name: Checkout sources
41-
uses: actions/checkout@v4
28+
- name: Run cargo clippy
29+
run: cargo clippy -- -D warnings
4230

43-
- name: Install stable toolchain
31+
- name: Install nightly toolchain
4432
uses: dtolnay/rust-toolchain@nightly
4533
with:
46-
components: rustfmt, clippy
34+
components: rustfmt
4735

4836
- name: Run cargo fmt
49-
run: cargo fmt --all -- --check
50-
51-
- name: Run cargo clippy
52-
run: cargo clippy -- -D warnings
37+
run: cargo +nightly fmt --all -- --check
5338

5439
nix-flake:
5540
name: Validate nix flake

src/code/snippet.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -745,13 +745,10 @@ mod test {
745745
#[test]
746746
fn highlight_line_range() {
747747
let attributes = parse_attributes("bash { 1, 2-4,6 , all , 10 - 12 }");
748-
assert_eq!(attributes.highlight_groups, &[HighlightGroup::new(vec![
749-
Single(1),
750-
Range(2..5),
751-
Single(6),
752-
All,
753-
Range(10..13)
754-
])]);
748+
assert_eq!(
749+
attributes.highlight_groups,
750+
&[HighlightGroup::new(vec![Single(1), Range(2..5), Single(6), All, Range(10..13)])]
751+
);
755752
}
756753

757754
#[test]

src/presentation/builder.rs

+21-18
Original file line numberDiff line numberDiff line change
@@ -733,21 +733,25 @@ impl<'a> PresentationBuilder<'a> {
733733
}
734734

735735
fn push_alert(&mut self, alert_type: AlertType, title: Option<String>, mut lines: Vec<Line>) -> BuildResult {
736-
let (default_title, prefix_color) = match alert_type {
737-
AlertType::Note => ("Note", self.theme.alert.colors.types.note),
738-
AlertType::Tip => ("Tip", self.theme.alert.colors.types.tip),
739-
AlertType::Important => ("Important", self.theme.alert.colors.types.important),
740-
AlertType::Warning => ("Warning", self.theme.alert.colors.types.warning),
741-
AlertType::Caution => ("Caution", self.theme.alert.colors.types.caution),
736+
let (prefix_color, default_title, symbol) = match alert_type {
737+
AlertType::Note => self.theme.alert.styles.note.as_parts(),
738+
AlertType::Tip => self.theme.alert.styles.tip.as_parts(),
739+
AlertType::Important => self.theme.alert.styles.important.as_parts(),
740+
AlertType::Warning => self.theme.alert.styles.warning.as_parts(),
741+
AlertType::Caution => self.theme.alert.styles.caution.as_parts(),
742742
};
743-
let prefix_color = prefix_color.or(self.theme.alert.colors.base.foreground);
743+
let prefix_color = prefix_color.or(self.theme.alert.base_colors.foreground);
744744
let title = title.unwrap_or_else(|| default_title.to_string());
745-
let title_colors = Colors { foreground: prefix_color, background: self.theme.alert.colors.base.background };
745+
let title = match symbol {
746+
Some(symbol) => format!("{symbol} {title}"),
747+
None => title,
748+
};
749+
let title_colors = Colors { foreground: prefix_color, background: self.theme.alert.base_colors.background };
746750
lines.insert(0, Line::from(Text::from("")));
747751
lines.insert(0, Line::from(Text::new(title, TextStyle::default().colors(title_colors))));
748752

749753
let prefix = self.theme.block_quote.prefix.clone().unwrap_or_default();
750-
self.push_quoted_text(lines, prefix, self.theme.alert.colors.base, prefix_color)
754+
self.push_quoted_text(lines, prefix, self.theme.alert.base_colors, prefix_color)
751755
}
752756

753757
fn push_quoted_text(
@@ -1681,11 +1685,10 @@ mod test {
16811685
#[test]
16821686
fn iterate_list_starting_from_other() {
16831687
let list = ListIterator::new(
1684-
vec![ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered }, ListItem {
1685-
depth: 0,
1686-
contents: "1".into(),
1687-
item_type: ListItemType::Unordered,
1688-
}],
1688+
vec![
1689+
ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered },
1690+
ListItem { depth: 0, contents: "1".into(), item_type: ListItemType::Unordered },
1691+
],
16891692
3,
16901693
);
16911694
let expected_indexes = [3, 4];
@@ -1772,10 +1775,10 @@ mod test {
17721775

17731776
#[test]
17741777
fn implicit_slide_ends_with_front_matter() {
1775-
let elements =
1776-
vec![MarkdownElement::FrontMatter("theme:\n name: light".into()), MarkdownElement::SetexHeading {
1777-
text: "hi".into(),
1778-
}];
1778+
let elements = vec![
1779+
MarkdownElement::FrontMatter("theme:\n name: light".into()),
1780+
MarkdownElement::SetexHeading { text: "hi".into() },
1781+
];
17791782
let options = PresentationBuilderOptions { implicit_slide_ends: true, ..Default::default() };
17801783
let slides = build_presentation_with_options(elements, options).into_slides();
17811784
assert_eq!(slides.len(), 1);

src/theme.rs

+142-41
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::markdown::text_style::{Color, Colors, FixedStr, UndefinedPaletteColorError};
22
use serde::{Deserialize, Serialize};
3-
use std::{collections::BTreeMap, fs, io, path::Path};
3+
use std::{collections::BTreeMap, fmt, fs, io, marker::PhantomData, path::Path};
44

55
include!(concat!(env!("OUT_DIR"), "/themes.rs"));
66

@@ -395,7 +395,7 @@ pub(crate) struct BlockQuoteStyle {
395395

396396
impl BlockQuoteStyle {
397397
fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> {
398-
let Self { colors, alignment: _alignment, prefix: _prefix } = self;
398+
let Self { colors, alignment: _, prefix: _ } = self;
399399
colors.resolve_palette_colors(palette)?;
400400
Ok(())
401401
}
@@ -431,82 +431,183 @@ pub(crate) struct AlertStyle {
431431
#[serde(flatten, default)]
432432
pub(crate) alignment: Option<Alignment>,
433433

434+
/// The base colors.
435+
#[serde(default)]
436+
pub(crate) base_colors: Colors,
437+
434438
/// The prefix to be added to this block quote.
435439
///
436440
/// This allows adding something like a vertical bar before the text.
437441
#[serde(default)]
438442
pub(crate) prefix: Option<String>,
439443

440-
/// The colors to be used.
444+
/// The style for each alert type.
441445
#[serde(default)]
442-
pub(crate) colors: AlertColors,
446+
pub(crate) styles: AlertTypeStyles,
443447
}
444448

445449
impl AlertStyle {
446450
fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> {
447-
let Self { colors, alignment: _alignment, prefix: _prefix } = self;
448-
colors.resolve_palette_colors(palette)?;
451+
let Self { base_colors, styles, alignment: _, prefix: _ } = self;
452+
*base_colors = base_colors.resolve(palette)?;
453+
styles.resolve_palette_colors(palette)?;
449454
Ok(())
450455
}
451456
}
452457

453-
/// The colors of an alert.
458+
/// The style for each alert type.
454459
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
455-
pub(crate) struct AlertColors {
456-
/// The foreground/background colors.
457-
#[serde(flatten)]
458-
pub(crate) base: Colors,
460+
pub(crate) struct AlertTypeStyles {
461+
/// The style for note alert types.
462+
#[serde(default)]
463+
pub(crate) note: AlertTypeStyle<NoteAlertType>,
459464

460-
/// The color of the vertical bar that prefixes each line in the quote.
465+
/// The style for tip alert types.
461466
#[serde(default)]
462-
pub(crate) types: AlertTypeColors,
467+
pub(crate) tip: AlertTypeStyle<TipAlertType>,
468+
469+
/// The style for important alert types.
470+
#[serde(default)]
471+
pub(crate) important: AlertTypeStyle<ImportantAlertType>,
472+
473+
/// The style for warning alert types.
474+
#[serde(default)]
475+
pub(crate) warning: AlertTypeStyle<WarningAlertType>,
476+
477+
/// The style for caution alert types.
478+
#[serde(default)]
479+
pub(crate) caution: AlertTypeStyle<CautionAlertType>,
463480
}
464481

465-
impl AlertColors {
482+
impl AlertTypeStyles {
466483
fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> {
467-
let Self { base, types } = self;
468-
*base = base.resolve(palette)?;
469-
types.resolve_palette_colors(palette)?;
484+
let Self { note, tip, important, warning, caution } = self;
485+
note.resolve_palette_colors(palette)?;
486+
tip.resolve_palette_colors(palette)?;
487+
important.resolve_palette_colors(palette)?;
488+
warning.resolve_palette_colors(palette)?;
489+
caution.resolve_palette_colors(palette)?;
470490
Ok(())
471491
}
472492
}
473493

474-
/// The colors of each alert type.
475-
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
476-
pub(crate) struct AlertTypeColors {
477-
/// The color for note type alerts.
478-
#[serde(default)]
479-
pub(crate) note: Option<Color>,
480-
481-
/// The color for tip type alerts.
494+
/// The style for an alert type.
495+
#[derive(Deserialize, Serialize)]
496+
pub(crate) struct AlertTypeStyle<S: AlertTypeProperties> {
497+
/// The color to be used.
482498
#[serde(default)]
483-
pub(crate) tip: Option<Color>,
499+
pub(crate) color: Option<Color>,
484500

485-
/// The color for important type alerts.
486-
#[serde(default)]
487-
pub(crate) important: Option<Color>,
501+
/// The title to be used.
502+
#[serde(default = "S::default_title")]
503+
pub(crate) title: String,
488504

489-
/// The color for warning type alerts.
490-
#[serde(default)]
491-
pub(crate) warning: Option<Color>,
505+
/// The symbol to be used.
506+
#[serde(default = "S::default_symbol")]
507+
pub(crate) symbol: Option<String>,
492508

493-
/// The color for caution type alerts.
494-
#[serde(default)]
495-
pub(crate) caution: Option<Color>,
509+
#[serde(skip)]
510+
_unused: PhantomData<S>,
496511
}
497512

498-
impl AlertTypeColors {
513+
impl<S: AlertTypeProperties> AlertTypeStyle<S> {
514+
pub(crate) fn as_parts(&self) -> (&Option<Color>, &str, Option<&str>) {
515+
(&self.color, &self.title, self.symbol.as_deref())
516+
}
517+
499518
fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> {
500-
let Self { note, tip, important, warning, caution } = self;
501-
for c in [note, tip, important, warning, caution] {
502-
if let Some(c) = c.as_mut() {
503-
*c = c.resolve(palette)?;
504-
}
519+
let Self { color, title: _, symbol: _, _unused: _ } = self;
520+
if let Some(color) = color.as_mut() {
521+
*color = color.resolve(palette)?;
505522
}
506523
Ok(())
507524
}
508525
}
509526

527+
impl<S: AlertTypeProperties> fmt::Debug for AlertTypeStyle<S> {
528+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529+
f.debug_struct("AlertTypeStyle")
530+
.field("color", &self.color)
531+
.field("title", &self.title)
532+
.field("symbol", &self.symbol)
533+
.field("_unused", &self._unused)
534+
.finish()
535+
}
536+
}
537+
538+
impl<S: AlertTypeProperties> Clone for AlertTypeStyle<S> {
539+
fn clone(&self) -> Self {
540+
Self { color: self.color, title: self.title.clone(), symbol: self.symbol.clone(), _unused: PhantomData }
541+
}
542+
}
543+
544+
impl<S: AlertTypeProperties> Default for AlertTypeStyle<S> {
545+
fn default() -> Self {
546+
Self { color: None, title: S::default_title(), symbol: S::default_symbol(), _unused: PhantomData }
547+
}
548+
}
549+
550+
pub(crate) trait AlertTypeProperties {
551+
fn default_title() -> String;
552+
fn default_symbol() -> Option<String>;
553+
}
554+
555+
pub(crate) struct NoteAlertType;
556+
pub(crate) struct TipAlertType;
557+
pub(crate) struct ImportantAlertType;
558+
pub(crate) struct WarningAlertType;
559+
pub(crate) struct CautionAlertType;
560+
561+
impl AlertTypeProperties for NoteAlertType {
562+
fn default_title() -> String {
563+
"Note".into()
564+
}
565+
566+
fn default_symbol() -> Option<String> {
567+
Some("󰋽".into())
568+
}
569+
}
570+
571+
impl AlertTypeProperties for TipAlertType {
572+
fn default_title() -> String {
573+
"Tip".into()
574+
}
575+
576+
fn default_symbol() -> Option<String> {
577+
Some("".into())
578+
}
579+
}
580+
581+
impl AlertTypeProperties for ImportantAlertType {
582+
fn default_title() -> String {
583+
"Important".into()
584+
}
585+
586+
fn default_symbol() -> Option<String> {
587+
Some("".into())
588+
}
589+
}
590+
591+
impl AlertTypeProperties for WarningAlertType {
592+
fn default_title() -> String {
593+
"Warning".into()
594+
}
595+
596+
fn default_symbol() -> Option<String> {
597+
Some("".into())
598+
}
599+
}
600+
601+
impl AlertTypeProperties for CautionAlertType {
602+
fn default_title() -> String {
603+
"Caution".into()
604+
}
605+
606+
fn default_symbol() -> Option<String> {
607+
Some("󰳦".into())
608+
}
609+
}
610+
510611
/// The style for the presentation introduction slide.
511612
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
512613
pub(crate) struct IntroSlideStyle {

0 commit comments

Comments
 (0)