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