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