tui_markdown/style_sheet.rs
1//! Style sheet abstraction for tui-markdown.
2//!
3//! [`StyleSheet`] supplies the styles and alert text used while Markdown events are rendered.
4//! Every choice has a default. Implementations only need to override the styles or text they want
5//! to customize.
6//!
7//! [`DefaultStyleSheet`] is used by [`crate::Options::default`]. Applications can pass another
8//! implementation to [`crate::Options::new`].
9
10use ratatui_core::style::Style;
11
12/// The kind of a GitHub Flavored Markdown alert.
13#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
14pub enum AlertKind {
15 /// Supplementary information.
16 Note,
17 /// Helpful advice.
18 Tip,
19 /// Information essential to success.
20 Important,
21 /// Urgent information that needs attention.
22 Warning,
23 /// A risk or negative outcome.
24 Caution,
25}
26
27impl AlertKind {
28 /// The canonical English label for this alert kind.
29 pub const fn label(self) -> &'static str {
30 match self {
31 Self::Note => "Note",
32 Self::Tip => "Tip",
33 Self::Important => "Important",
34 Self::Warning => "Warning",
35 Self::Caution => "Caution",
36 }
37 }
38}
39
40/// Visual styles and symbols consumed by the renderer.
41///
42/// Every method has a default, which [`DefaultStyleSheet`] uses unchanged. Implementations only
43/// need to override the choices they want to customize.
44pub trait StyleSheet: Clone + Send + Sync + 'static {
45 /// Style for a Markdown heading.
46 ///
47 /// `level` is one-based (`1` for `# H1`, …).
48 fn heading(&self, level: u8) -> Style {
49 match level {
50 1 => Style::new().on_cyan().bold().underlined(),
51 2 => Style::new().cyan().bold(),
52 3 => Style::new().cyan().bold().italic(),
53 4 => Style::new().light_cyan().italic(),
54 5 => Style::new().light_cyan().italic(),
55 _ => Style::new().light_cyan().italic(),
56 }
57 }
58
59 /// Style for inline `code` spans and fenced code blocks when syntax highlighting is disabled.
60 fn code(&self) -> Style {
61 Style::new().white().on_black()
62 }
63
64 /// Style for an inline or reference link's visible label and appended destination.
65 fn link(&self) -> Style {
66 Style::new().blue().underlined()
67 }
68
69 /// Base style applied to blockquotes (`>` prefix and body text).
70 fn blockquote(&self) -> Style {
71 Style::new().green()
72 }
73
74 /// Style for heading attribute metadata appended to the heading text.
75 fn heading_meta(&self) -> Style {
76 Style::new().dim()
77 }
78
79 /// Style for metadata blocks (front matter).
80 fn metadata_block(&self) -> Style {
81 Style::new().light_yellow()
82 }
83
84 /// Marker displayed before a Markdown heading.
85 ///
86 /// `level` is one-based (`1` for an H1, …). The renderer adds one separating space after a
87 /// non-empty marker. Return an empty string to omit the marker and its separator.
88 fn heading_marker(&self, level: u8) -> &str {
89 match level {
90 1 => "#",
91 2 => "##",
92 3 => "###",
93 4 => "####",
94 5 => "#####",
95 _ => "######",
96 }
97 }
98
99 /// Delimiter displayed above and below block code.
100 ///
101 /// The renderer appends fenced-code info to the opening delimiter. Return an empty string to
102 /// omit both delimiter lines.
103 fn code_block_fence(&self) -> &str {
104 "```"
105 }
106
107 /// Style for raw HTML blocks and inline HTML tags.
108 fn html(&self) -> Style {
109 Style::new().dim()
110 }
111
112 /// Style for inline math (`$...$`).
113 fn math_inline(&self) -> Style {
114 Style::new().italic().magenta()
115 }
116
117 /// Style for display math (`$$...$$`).
118 fn math_display(&self) -> Style {
119 Style::new().magenta()
120 }
121
122 /// Style for footnote references, rendered as `[label]`.
123 fn footnote_ref(&self) -> Style {
124 Style::new().dim().italic()
125 }
126
127 /// Style for footnote definitions, including the `[label]: ` prefix.
128 fn footnote_def(&self) -> Style {
129 Style::new().dim()
130 }
131
132 /// Style for definition list terms.
133 fn definition_term(&self) -> Style {
134 Style::new().bold()
135 }
136
137 /// Style for definition list descriptions, including the `: ` prefix.
138 fn definition_description(&self) -> Style {
139 Style::default()
140 }
141 /// Style for a GFM alert heading and body.
142 ///
143 /// The generated icon and label are bold in addition to this base style.
144 fn alert(&self, kind: AlertKind) -> Style {
145 use ratatui_core::style::Color;
146
147 match kind {
148 AlertKind::Note => Style::new().fg(Color::Blue),
149 AlertKind::Tip => Style::new().fg(Color::Green),
150 AlertKind::Important => Style::new().fg(Color::Magenta),
151 AlertKind::Warning => Style::new().fg(Color::Yellow),
152 AlertKind::Caution => Style::new().fg(Color::Red),
153 }
154 }
155
156 /// Icon displayed before a GFM alert label.
157 ///
158 /// Return an empty string to render the label without an icon.
159 fn alert_icon(&self, kind: AlertKind) -> &str {
160 match kind {
161 AlertKind::Note => "\u{2139}\u{FE0F}",
162 AlertKind::Tip => "\u{1F4A1}",
163 AlertKind::Important => "\u{2757}",
164 AlertKind::Warning => "\u{26A0}\u{FE0F}",
165 AlertKind::Caution => "\u{1F534}",
166 }
167 }
168
169 /// Label displayed after a GFM alert icon.
170 ///
171 /// Return an empty string to render the icon without a label.
172 fn alert_label(&self, kind: AlertKind) -> &str {
173 kind.label()
174 }
175
176 /// Style patched onto cells in the table header row.
177 ///
178 /// Properties set by this style override the same properties in an inline style. The style
179 /// covers cell padding, while borders use [`Self::table_border`].
180 fn table_header(&self) -> Style {
181 Style::new().bold().cyan()
182 }
183
184 /// Style patched onto ordinary table cells.
185 ///
186 /// Properties set by this style override the same properties in an inline style. The style
187 /// covers cell padding, while borders use [`Self::table_border`].
188 fn table_cell(&self) -> Style {
189 Style::default()
190 }
191
192 /// Style for the Unicode box-drawing characters around table cells.
193 ///
194 /// This changes the presentation of the borders, not the box-drawing characters themselves.
195 fn table_border(&self) -> Style {
196 Style::new().dark_gray()
197 }
198
199 /// Style for the `[img]` marker and text used to represent Markdown images.
200 ///
201 /// The default is dim and italic. The renderer patches this over any enclosing inline style.
202 fn image_alt(&self) -> Style {
203 Style::new().dim().italic()
204 }
205}
206
207/// The default style set
208///
209/// This style sheet will be used by default if the user does not provide their own implementation.
210///
211/// Styles are:
212/// - H1: white on cyan, bold, underlined
213/// - H2: cyan, bold
214/// - H3: cyan, bold, italic
215/// - H4-H6: light cyan, italic
216/// - code: white on black
217/// - link: blue, underlined
218/// - blockquote: green
219/// - metadata block: light yellow
220/// - heading markers: one to six `#` characters
221/// - code block fences: three backticks
222/// - raw HTML: dim
223/// - inline math: magenta, italic
224/// - display math: magenta
225/// - footnote references: dim, italic
226/// - footnote definitions: dim
227/// - definition list terms: bold
228/// - definition list descriptions: the surrounding style
229/// - note alerts: blue
230/// - tip alerts: green
231/// - important alerts: magenta
232/// - warning alerts: yellow
233/// - caution alerts: red
234/// - table headers: bold cyan
235/// - table cells: the surrounding style
236/// - table borders: dark gray
237/// - image fallback text: dim and italic
238#[derive(Clone, Copy, Debug, Default)]
239pub struct DefaultStyleSheet;
240
241impl StyleSheet for DefaultStyleSheet {}