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