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}