1 use termcolor::{Color, ColorSpec};
2 
3 use crate::diagnostic::{LabelStyle, Severity};
4 
5 /// Configures how a diagnostic is rendered.
6 #[derive(Clone, Debug)]
7 pub struct Config {
8     /// The display style to use when rendering diagnostics.
9     /// Defaults to: [`DisplayStyle::Rich`].
10     ///
11     /// [`DisplayStyle::Rich`]: DisplayStyle::Rich
12     pub display_style: DisplayStyle,
13     /// Column width of tabs.
14     /// Defaults to: `4`.
15     pub tab_width: usize,
16     /// Styles to use when rendering the diagnostic.
17     pub styles: Styles,
18     /// Characters to use when rendering the diagnostic.
19     pub chars: Chars,
20     /// The minimum number of lines to be shown after the line on which a multiline [`Label`] begins.
21     ///
22     /// Defaults to: `3`.
23     ///
24     /// [`Label`]: crate::diagnostic::Label
25     pub start_context_lines: usize,
26     /// The minimum number of lines to be shown before the line on which a multiline [`Label`] ends.
27     ///
28     /// Defaults to: `1`.
29     ///
30     /// [`Label`]: crate::diagnostic::Label
31     pub end_context_lines: usize,
32 }
33 
34 impl Default for Config {
default() -> Config35     fn default() -> Config {
36         Config {
37             display_style: DisplayStyle::Rich,
38             tab_width: 4,
39             styles: Styles::default(),
40             chars: Chars::default(),
41             start_context_lines: 3,
42             end_context_lines: 1,
43         }
44     }
45 }
46 
47 /// The display style to use when rendering diagnostics.
48 #[derive(Clone, Debug)]
49 pub enum DisplayStyle {
50     /// Output a richly formatted diagnostic, with source code previews.
51     ///
52     /// ```text
53     /// error[E0001]: unexpected type in `+` application
54     ///   ┌─ test:2:9
55     ///   │
56     /// 2 │ (+ test "")
57     ///   │         ^^ expected `Int` but found `String`
58     ///   │
59     ///   = expected type `Int`
60     ///        found type `String`
61     ///
62     /// error[E0002]: Bad config found
63     ///
64     /// ```
65     Rich,
66     /// Output a condensed diagnostic, with a line number, severity, message and notes (if any).
67     ///
68     /// ```text
69     /// test:2:9: error[E0001]: unexpected type in `+` application
70     /// = expected type `Int`
71     ///      found type `String`
72     ///
73     /// error[E0002]: Bad config found
74     /// ```
75     Medium,
76     /// Output a short diagnostic, with a line number, severity, and message.
77     ///
78     /// ```text
79     /// test:2:9: error[E0001]: unexpected type in `+` application
80     /// error[E0002]: Bad config found
81     /// ```
82     Short,
83 }
84 
85 /// Styles to use when rendering the diagnostic.
86 #[derive(Clone, Debug)]
87 pub struct Styles {
88     /// The style to use when rendering bug headers.
89     /// Defaults to `fg:red bold intense`.
90     pub header_bug: ColorSpec,
91     /// The style to use when rendering error headers.
92     /// Defaults to `fg:red bold intense`.
93     pub header_error: ColorSpec,
94     /// The style to use when rendering warning headers.
95     /// Defaults to `fg:yellow bold intense`.
96     pub header_warning: ColorSpec,
97     /// The style to use when rendering note headers.
98     /// Defaults to `fg:green bold intense`.
99     pub header_note: ColorSpec,
100     /// The style to use when rendering help headers.
101     /// Defaults to `fg:cyan bold intense`.
102     pub header_help: ColorSpec,
103     /// The style to use when the main diagnostic message.
104     /// Defaults to `bold intense`.
105     pub header_message: ColorSpec,
106 
107     /// The style to use when rendering bug labels.
108     /// Defaults to `fg:red`.
109     pub primary_label_bug: ColorSpec,
110     /// The style to use when rendering error labels.
111     /// Defaults to `fg:red`.
112     pub primary_label_error: ColorSpec,
113     /// The style to use when rendering warning labels.
114     /// Defaults to `fg:yellow`.
115     pub primary_label_warning: ColorSpec,
116     /// The style to use when rendering note labels.
117     /// Defaults to `fg:green`.
118     pub primary_label_note: ColorSpec,
119     /// The style to use when rendering help labels.
120     /// Defaults to `fg:cyan`.
121     pub primary_label_help: ColorSpec,
122     /// The style to use when rendering secondary labels.
123     /// Defaults `fg:blue` (or `fg:cyan` on windows).
124     pub secondary_label: ColorSpec,
125 
126     /// The style to use when rendering the line numbers.
127     /// Defaults `fg:blue` (or `fg:cyan` on windows).
128     pub line_number: ColorSpec,
129     /// The style to use when rendering the source code borders.
130     /// Defaults `fg:blue` (or `fg:cyan` on windows).
131     pub source_border: ColorSpec,
132     /// The style to use when rendering the note bullets.
133     /// Defaults `fg:blue` (or `fg:cyan` on windows).
134     pub note_bullet: ColorSpec,
135 }
136 
137 impl Styles {
138     /// The style used to mark a header at a given severity.
header(&self, severity: Severity) -> &ColorSpec139     pub fn header(&self, severity: Severity) -> &ColorSpec {
140         match severity {
141             Severity::Bug => &self.header_bug,
142             Severity::Error => &self.header_error,
143             Severity::Warning => &self.header_warning,
144             Severity::Note => &self.header_note,
145             Severity::Help => &self.header_help,
146         }
147     }
148 
149     /// The style used to mark a primary or secondary label at a given severity.
label(&self, severity: Severity, label_style: LabelStyle) -> &ColorSpec150     pub fn label(&self, severity: Severity, label_style: LabelStyle) -> &ColorSpec {
151         match (label_style, severity) {
152             (LabelStyle::Primary, Severity::Bug) => &self.primary_label_bug,
153             (LabelStyle::Primary, Severity::Error) => &self.primary_label_error,
154             (LabelStyle::Primary, Severity::Warning) => &self.primary_label_warning,
155             (LabelStyle::Primary, Severity::Note) => &self.primary_label_note,
156             (LabelStyle::Primary, Severity::Help) => &self.primary_label_help,
157             (LabelStyle::Secondary, _) => &self.secondary_label,
158         }
159     }
160 
161     #[doc(hidden)]
with_blue(blue: Color) -> Styles162     pub fn with_blue(blue: Color) -> Styles {
163         let header = ColorSpec::new().set_bold(true).set_intense(true).clone();
164 
165         Styles {
166             header_bug: header.clone().set_fg(Some(Color::Red)).clone(),
167             header_error: header.clone().set_fg(Some(Color::Red)).clone(),
168             header_warning: header.clone().set_fg(Some(Color::Yellow)).clone(),
169             header_note: header.clone().set_fg(Some(Color::Green)).clone(),
170             header_help: header.clone().set_fg(Some(Color::Cyan)).clone(),
171             header_message: header,
172 
173             primary_label_bug: ColorSpec::new().set_fg(Some(Color::Red)).clone(),
174             primary_label_error: ColorSpec::new().set_fg(Some(Color::Red)).clone(),
175             primary_label_warning: ColorSpec::new().set_fg(Some(Color::Yellow)).clone(),
176             primary_label_note: ColorSpec::new().set_fg(Some(Color::Green)).clone(),
177             primary_label_help: ColorSpec::new().set_fg(Some(Color::Cyan)).clone(),
178             secondary_label: ColorSpec::new().set_fg(Some(blue)).clone(),
179 
180             line_number: ColorSpec::new().set_fg(Some(blue)).clone(),
181             source_border: ColorSpec::new().set_fg(Some(blue)).clone(),
182             note_bullet: ColorSpec::new().set_fg(Some(blue)).clone(),
183         }
184     }
185 }
186 
187 impl Default for Styles {
default() -> Styles188     fn default() -> Styles {
189         // Blue is really difficult to see on the standard windows command line
190         #[cfg(windows)]
191         const BLUE: Color = Color::Cyan;
192         #[cfg(not(windows))]
193         const BLUE: Color = Color::Blue;
194 
195         Self::with_blue(BLUE)
196     }
197 }
198 
199 /// Characters to use when rendering the diagnostic.
200 ///
201 /// By using [`Chars::ascii()`] you can switch to an ASCII-only format suitable
202 /// for rendering on terminals that do not support box drawing characters.
203 #[derive(Clone, Debug)]
204 pub struct Chars {
205     /// The characters to use for the top-left border of the snippet.
206     /// Defaults to: `"┌─"` or `"-->"` with [`Chars::ascii()`].
207     pub snippet_start: String,
208     /// The character to use for the left border of the source.
209     /// Defaults to: `'│'` or `'|'` with [`Chars::ascii()`].
210     pub source_border_left: char,
211     /// The character to use for the left border break of the source.
212     /// Defaults to: `'·'` or `'.'` with [`Chars::ascii()`].
213     pub source_border_left_break: char,
214 
215     /// The character to use for the note bullet.
216     /// Defaults to: `'='`.
217     pub note_bullet: char,
218 
219     /// The character to use for marking a single-line primary label.
220     /// Defaults to: `'^'`.
221     pub single_primary_caret: char,
222     /// The character to use for marking a single-line secondary label.
223     /// Defaults to: `'-'`.
224     pub single_secondary_caret: char,
225 
226     /// The character to use for marking the start of a multi-line primary label.
227     /// Defaults to: `'^'`.
228     pub multi_primary_caret_start: char,
229     /// The character to use for marking the end of a multi-line primary label.
230     /// Defaults to: `'^'`.
231     pub multi_primary_caret_end: char,
232     /// The character to use for marking the start of a multi-line secondary label.
233     /// Defaults to: `'\''`.
234     pub multi_secondary_caret_start: char,
235     /// The character to use for marking the end of a multi-line secondary label.
236     /// Defaults to: `'\''`.
237     pub multi_secondary_caret_end: char,
238     /// The character to use for the top-left corner of a multi-line label.
239     /// Defaults to: `'╭'` or `'/'` with [`Chars::ascii()`].
240     pub multi_top_left: char,
241     /// The character to use for the top of a multi-line label.
242     /// Defaults to: `'─'` or `'-'` with [`Chars::ascii()`].
243     pub multi_top: char,
244     /// The character to use for the bottom-left corner of a multi-line label.
245     /// Defaults to: `'╰'` or `'\'` with [`Chars::ascii()`].
246     pub multi_bottom_left: char,
247     /// The character to use when marking the bottom of a multi-line label.
248     /// Defaults to: `'─'` or `'-'` with [`Chars::ascii()`].
249     pub multi_bottom: char,
250     /// The character to use for the left of a multi-line label.
251     /// Defaults to: `'│'` or `'|'` with [`Chars::ascii()`].
252     pub multi_left: char,
253 
254     /// The character to use for the left of a pointer underneath a caret.
255     /// Defaults to: `'│'` or `'|'` with [`Chars::ascii()`].
256     pub pointer_left: char,
257 }
258 
259 impl Default for Chars {
default() -> Chars260     fn default() -> Chars {
261         Chars::box_drawing()
262     }
263 }
264 
265 impl Chars {
266     /// A character set that uses Unicode box drawing characters.
box_drawing() -> Chars267     pub fn box_drawing() -> Chars {
268         Chars {
269             snippet_start: "┌─".into(),
270             source_border_left: '│',
271             source_border_left_break: '·',
272 
273             note_bullet: '=',
274 
275             single_primary_caret: '^',
276             single_secondary_caret: '-',
277 
278             multi_primary_caret_start: '^',
279             multi_primary_caret_end: '^',
280             multi_secondary_caret_start: '\'',
281             multi_secondary_caret_end: '\'',
282             multi_top_left: '╭',
283             multi_top: '─',
284             multi_bottom_left: '╰',
285             multi_bottom: '─',
286             multi_left: '│',
287 
288             pointer_left: '│',
289         }
290     }
291 
292     /// A character set that only uses ASCII characters.
293     ///
294     /// This is useful if your terminal's font does not support box drawing
295     /// characters well and results in output that looks similar to rustc's
296     /// diagnostic output.
ascii() -> Chars297     pub fn ascii() -> Chars {
298         Chars {
299             snippet_start: "-->".into(),
300             source_border_left: '|',
301             source_border_left_break: '.',
302 
303             note_bullet: '=',
304 
305             single_primary_caret: '^',
306             single_secondary_caret: '-',
307 
308             multi_primary_caret_start: '^',
309             multi_primary_caret_end: '^',
310             multi_secondary_caret_start: '\'',
311             multi_secondary_caret_end: '\'',
312             multi_top_left: '/',
313             multi_top: '-',
314             multi_bottom_left: '\\',
315             multi_bottom: '-',
316             multi_left: '|',
317 
318             pointer_left: '|',
319         }
320     }
321 }
322