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