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