miette/handlers/
theme.rs

1use std::io::IsTerminal;
2
3use owo_colors::Style;
4
5/**
6Theme used by [`GraphicalReportHandler`](crate::GraphicalReportHandler) to
7render fancy [`Diagnostic`](crate::Diagnostic) reports.
8
9A theme consists of two things: the set of characters to be used for drawing,
10and the
11[`owo_colors::Style`](https://docs.rs/owo-colors/latest/owo_colors/struct.Style.html)s to be used to paint various items.
12
13You can create your own custom graphical theme using this type, or you can use
14one of the predefined ones using the methods below.
15*/
16#[derive(Debug, Clone)]
17pub struct GraphicalTheme {
18    /// Characters to be used for drawing.
19    pub characters: ThemeCharacters,
20    /// Styles to be used for painting.
21    pub styles: ThemeStyles,
22}
23
24impl GraphicalTheme {
25    /// ASCII-art-based graphical drawing, with ANSI styling.
26    pub fn ascii() -> Self {
27        Self {
28            characters: ThemeCharacters::ascii(),
29            styles: ThemeStyles::ansi(),
30        }
31    }
32
33    /// Graphical theme that draws using both ansi colors and unicode
34    /// characters.
35    ///
36    /// Note that full rgb colors aren't enabled by default because they're
37    /// an accessibility hazard, especially in the context of terminal themes
38    /// that can change the background color and make hardcoded colors illegible.
39    /// Such themes typically remap ansi codes properly, treating them more
40    /// like CSS classes than specific colors.
41    pub fn unicode() -> Self {
42        Self {
43            characters: ThemeCharacters::unicode(),
44            styles: ThemeStyles::ansi(),
45        }
46    }
47
48    /// Graphical theme that draws in monochrome, while still using unicode
49    /// characters.
50    pub fn unicode_nocolor() -> Self {
51        Self {
52            characters: ThemeCharacters::unicode(),
53            styles: ThemeStyles::none(),
54        }
55    }
56
57    /// A "basic" graphical theme that skips colors and unicode characters and
58    /// just does monochrome ascii art. If you want a completely non-graphical
59    /// rendering of your [`Diagnostic`](crate::Diagnostic)s, check out
60    /// [`NarratableReportHandler`](crate::NarratableReportHandler), or write
61    /// your own [`ReportHandler`](crate::ReportHandler)
62    pub fn none() -> Self {
63        Self {
64            characters: ThemeCharacters::ascii(),
65            styles: ThemeStyles::none(),
66        }
67    }
68}
69
70impl Default for GraphicalTheme {
71    fn default() -> Self {
72        match std::env::var("NO_COLOR") {
73            _ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
74                Self::none()
75            }
76            Ok(string) if string != "0" => Self::unicode_nocolor(),
77            _ => Self::unicode(),
78        }
79    }
80}
81
82/**
83Styles for various parts of graphical rendering for the
84[`GraphicalReportHandler`](crate::GraphicalReportHandler).
85*/
86#[derive(Debug, Clone)]
87pub struct ThemeStyles {
88    /// Style to apply to things highlighted as "error".
89    pub error: Style,
90    /// Style to apply to things highlighted as "warning".
91    pub warning: Style,
92    /// Style to apply to things highlighted as "advice".
93    pub advice: Style,
94    /// Style to apply to the help text.
95    pub help: Style,
96    /// Style to apply to filenames/links/URLs.
97    pub link: Style,
98    /// Style to apply to line numbers.
99    pub linum: Style,
100    /// Styles to cycle through (using `.iter().cycle()`), to render the lines
101    /// and text for diagnostic highlights.
102    pub highlights: Vec<Style>,
103}
104
105fn style() -> Style {
106    Style::new()
107}
108
109impl ThemeStyles {
110    /// Nice RGB colors.
111    /// [Credit](http://terminal.sexy/#FRUV0NDQFRUVrEFCkKlZ9L91ap-1qnWfdbWq0NDQUFBQrEFCkKlZ9L91ap-1qnWfdbWq9fX1).
112    pub fn rgb() -> Self {
113        Self {
114            error: style().fg_rgb::<255, 30, 30>(),
115            warning: style().fg_rgb::<244, 191, 117>(),
116            advice: style().fg_rgb::<106, 159, 181>(),
117            help: style().fg_rgb::<106, 159, 181>(),
118            link: style().fg_rgb::<92, 157, 255>().underline().bold(),
119            linum: style().dimmed(),
120            highlights: vec![
121                style().fg_rgb::<246, 87, 248>(),
122                style().fg_rgb::<30, 201, 212>(),
123                style().fg_rgb::<145, 246, 111>(),
124            ],
125        }
126    }
127
128    /// ANSI color-based styles.
129    pub fn ansi() -> Self {
130        Self {
131            error: style().red(),
132            warning: style().yellow(),
133            advice: style().cyan(),
134            help: style().cyan(),
135            link: style().cyan().underline().bold(),
136            linum: style().dimmed(),
137            highlights: vec![
138                style().magenta().bold(),
139                style().yellow().bold(),
140                style().green().bold(),
141            ],
142        }
143    }
144
145    /// No styling. Just regular ol' monochrome.
146    pub fn none() -> Self {
147        Self {
148            error: style(),
149            warning: style(),
150            advice: style(),
151            help: style(),
152            link: style(),
153            linum: style(),
154            highlights: vec![style()],
155        }
156    }
157}
158
159// ----------------------------------------
160// Most of these characters were taken from
161// https://github.com/zesterer/ariadne/blob/e3cb394cb56ecda116a0a1caecd385a49e7f6662/src/draw.rs
162
163/// Characters to be used when drawing when using
164/// [`GraphicalReportHandler`](crate::GraphicalReportHandler).
165#[allow(missing_docs)]
166#[derive(Debug, Clone, Eq, PartialEq)]
167pub struct ThemeCharacters {
168    pub hbar: char,
169    pub vbar: char,
170    pub xbar: char,
171    pub vbar_break: char,
172
173    pub uarrow: char,
174    pub rarrow: char,
175
176    pub ltop: char,
177    pub mtop: char,
178    pub rtop: char,
179    pub lbot: char,
180    pub rbot: char,
181    pub mbot: char,
182
183    pub lbox: char,
184    pub rbox: char,
185
186    pub lcross: char,
187    pub rcross: char,
188
189    pub underbar: char,
190    pub underline: char,
191
192    pub error: String,
193    pub warning: String,
194    pub advice: String,
195}
196
197impl ThemeCharacters {
198    /// Fancy unicode-based graphical elements.
199    pub fn unicode() -> Self {
200        Self {
201            hbar: '─',
202            vbar: '│',
203            xbar: '┼',
204            vbar_break: '·',
205            uarrow: '▲',
206            rarrow: '▶',
207            ltop: '╭',
208            mtop: '┬',
209            rtop: '╮',
210            lbot: '╰',
211            mbot: '┴',
212            rbot: '╯',
213            lbox: '[',
214            rbox: ']',
215            lcross: '├',
216            rcross: '┤',
217            underbar: '┬',
218            underline: '─',
219            error: "×".into(),
220            warning: "⚠".into(),
221            advice: "☞".into(),
222        }
223    }
224
225    /// Emoji-heavy unicode characters.
226    pub fn emoji() -> Self {
227        Self {
228            hbar: '─',
229            vbar: '│',
230            xbar: '┼',
231            vbar_break: '·',
232            uarrow: '▲',
233            rarrow: '▶',
234            ltop: '╭',
235            mtop: '┬',
236            rtop: '╮',
237            lbot: '╰',
238            mbot: '┴',
239            rbot: '╯',
240            lbox: '[',
241            rbox: ']',
242            lcross: '├',
243            rcross: '┤',
244            underbar: '┬',
245            underline: '─',
246            error: "💥".into(),
247            warning: "⚠️".into(),
248            advice: "💡".into(),
249        }
250    }
251    /// ASCII-art-based graphical elements. Works well on older terminals.
252    pub fn ascii() -> Self {
253        Self {
254            hbar: '-',
255            vbar: '|',
256            xbar: '+',
257            vbar_break: ':',
258            uarrow: '^',
259            rarrow: '>',
260            ltop: ',',
261            mtop: 'v',
262            rtop: '.',
263            lbot: '`',
264            mbot: '^',
265            rbot: '\'',
266            lbox: '[',
267            rbox: ']',
268            lcross: '|',
269            rcross: '|',
270            underbar: '|',
271            underline: '^',
272            error: "x".into(),
273            warning: "!".into(),
274            advice: ">".into(),
275        }
276    }
277}