use alloc::string::String; /// Configures how a diagnostic is rendered. #[derive(Clone, Debug)] pub struct Config { /// The display style to use when rendering diagnostics. /// Defaults to: [`DisplayStyle::Rich`]. /// /// [`DisplayStyle::Rich`]: DisplayStyle::Rich pub display_style: DisplayStyle, /// Column width of tabs. /// Defaults to: `4`. pub tab_width: usize, /// Characters to use when rendering the diagnostic. pub chars: Chars, /// The minimum number of lines to be shown after the line on which a multiline [`Label`] begins. /// /// Defaults to: `3`. /// /// [`Label`]: crate::diagnostic::Label pub start_context_lines: usize, /// The minimum number of lines to be shown before the line on which a multiline [`Label`] ends. /// /// Defaults to: `1`. /// /// [`Label`]: crate::diagnostic::Label pub end_context_lines: usize, /// The minimum number of lines before a label that should be included for context. /// /// Defaults to: `0`. pub before_label_lines: usize, /// The minimum number of lines after a label that should be included for context. /// /// Defaults to: `0`. pub after_label_lines: usize, } impl Default for Config { fn default() -> Config { Config { display_style: DisplayStyle::Rich, tab_width: 4, chars: Chars::default(), start_context_lines: 3, end_context_lines: 1, before_label_lines: 0, after_label_lines: 0, } } } /// The display style to use when rendering diagnostics. #[derive(Clone, Debug)] pub enum DisplayStyle { /// Output a richly formatted diagnostic, with source code previews. /// /// ```text /// error[E0001]: unexpected type in `+` application /// ┌─ test:2:9 /// │ /// 2 │ (+ test "") /// │ ^^ expected `Int` but found `String` /// │ /// = expected type `Int` /// found type `String` /// /// error[E0002]: Bad config found /// /// ``` Rich, /// Output a condensed diagnostic, with a line number, severity, message and notes (if any). /// /// ```text /// test:2:9: error[E0001]: unexpected type in `+` application /// = expected type `Int` /// found type `String` /// /// error[E0002]: Bad config found /// ``` Medium, /// Output a short diagnostic, with a line number, severity, and message. /// /// ```text /// test:2:9: error[E0001]: unexpected type in `+` application /// error[E0002]: Bad config found /// ``` Short, } #[cfg(feature = "termcolor")] pub mod styles { use super::super::renderer::{self, WriteStyle}; use crate::diagnostic::{LabelStyle, Severity}; use termcolor::{Color, ColorSpec, WriteColor}; // re-export pub use termcolor; /// Styles to use when rendering the diagnostic. #[derive(Clone, Debug)] pub struct Styles { /// The style to use when rendering bug headers. /// Defaults to `fg:red bold intense`. pub header_bug: ColorSpec, /// The style to use when rendering error headers. /// Defaults to `fg:red bold intense`. pub header_error: ColorSpec, /// The style to use when rendering warning headers. /// Defaults to `fg:yellow bold intense`. pub header_warning: ColorSpec, /// The style to use when rendering note headers. /// Defaults to `fg:green bold intense`. pub header_note: ColorSpec, /// The style to use when rendering help headers. /// Defaults to `fg:cyan bold intense`. pub header_help: ColorSpec, /// The style to use when the main diagnostic message. /// Defaults to `bold intense`. pub header_message: ColorSpec, /// The style to use when rendering bug labels. /// Defaults to `fg:red`. pub primary_label_bug: ColorSpec, /// The style to use when rendering error labels. /// Defaults to `fg:red`. pub primary_label_error: ColorSpec, /// The style to use when rendering warning labels. /// Defaults to `fg:yellow`. pub primary_label_warning: ColorSpec, /// The style to use when rendering note labels. /// Defaults to `fg:green`. pub primary_label_note: ColorSpec, /// The style to use when rendering help labels. /// Defaults to `fg:cyan`. pub primary_label_help: ColorSpec, /// The style to use when rendering secondary labels. /// Defaults `fg:blue` (or `fg:cyan` on windows). pub secondary_label: ColorSpec, /// The style to use when rendering the line numbers. /// Defaults `fg:blue` (or `fg:cyan` on windows). pub line_number: ColorSpec, /// The style to use when rendering the source code borders. /// Defaults `fg:blue` (or `fg:cyan` on windows). pub source_border: ColorSpec, /// The style to use when rendering the note bullets. /// Defaults `fg:blue` (or `fg:cyan` on windows). pub note_bullet: ColorSpec, } impl Styles { /// The style used to mark a header at a given severity. #[must_use] pub fn header(&self, severity: Severity) -> &ColorSpec { match severity { Severity::Bug => &self.header_bug, Severity::Error => &self.header_error, Severity::Warning => &self.header_warning, Severity::Note => &self.header_note, Severity::Help => &self.header_help, } } #[must_use] pub fn header_message(&self) -> &ColorSpec { &self.header_message } #[must_use] pub fn line_number(&self) -> &ColorSpec { &self.line_number } #[must_use] pub fn note_bullet(&self) -> &ColorSpec { &self.note_bullet } #[must_use] pub fn source_border(&self) -> &ColorSpec { &self.source_border } /// The style used to mark a primary or secondary label at a given severity. #[must_use] pub fn label(&self, severity: Severity, label_style: LabelStyle) -> &ColorSpec { match (label_style, severity) { (LabelStyle::Primary, Severity::Bug) => &self.primary_label_bug, (LabelStyle::Primary, Severity::Error) => &self.primary_label_error, (LabelStyle::Primary, Severity::Warning) => &self.primary_label_warning, (LabelStyle::Primary, Severity::Note) => &self.primary_label_note, (LabelStyle::Primary, Severity::Help) => &self.primary_label_help, (LabelStyle::Secondary, _) => &self.secondary_label, } } } impl Styles { #[must_use] pub fn no_color() -> Styles { Styles { header_bug: ColorSpec::new(), header_error: ColorSpec::new(), header_warning: ColorSpec::new(), header_note: ColorSpec::new(), header_help: ColorSpec::new(), header_message: ColorSpec::new(), primary_label_bug: ColorSpec::new(), primary_label_error: ColorSpec::new(), primary_label_warning: ColorSpec::new(), primary_label_note: ColorSpec::new(), primary_label_help: ColorSpec::new(), secondary_label: ColorSpec::new(), line_number: ColorSpec::new(), source_border: ColorSpec::new(), note_bullet: ColorSpec::new(), } } } impl Default for Styles { fn default() -> Styles { // Default style let header = ColorSpec::new().set_bold(true).set_intense(true).clone(); Styles { header_bug: header.clone().set_fg(Some(Color::Red)).clone(), header_error: header.clone().set_fg(Some(Color::Red)).clone(), header_warning: header.clone().set_fg(Some(Color::Yellow)).clone(), header_note: header.clone().set_fg(Some(Color::Green)).clone(), header_help: header.clone().set_fg(Some(Color::Cyan)).clone(), header_message: header, primary_label_bug: ColorSpec::new().set_fg(Some(Color::Red)).clone(), primary_label_error: ColorSpec::new().set_fg(Some(Color::Red)).clone(), primary_label_warning: ColorSpec::new().set_fg(Some(Color::Yellow)).clone(), primary_label_note: ColorSpec::new().set_fg(Some(Color::Green)).clone(), primary_label_help: ColorSpec::new().set_fg(Some(Color::Cyan)).clone(), secondary_label: ColorSpec::new().set_fg(Some(Color::Cyan)).clone(), line_number: ColorSpec::new().set_fg(Some(Color::Cyan)).clone(), source_border: ColorSpec::new().set_fg(Some(Color::Cyan)).clone(), note_bullet: ColorSpec::new().set_fg(Some(Color::Cyan)).clone(), } } } /// A [`WriteStyle`](crate::term::renderer::WriteStyle) implementation that applies custom [`Styles`] /// using termcolor. /// /// Use this to render diagnostics with custom colors pub struct StylesWriter<'a, W> { writer: W, style: &'a Styles, } impl<'a, W> StylesWriter<'a, W> { /// Creates a new `StylesWriter` with the given writer and styles. pub fn new(writer: W, style: &'a Styles) -> Self { Self { writer, style } } } // Always true here #[cfg(feature = "std")] impl<'a, W: WriteColor> std::io::Write for StylesWriter<'a, W> { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.writer.write(buf) } fn flush(&mut self) -> std::io::Result<()> { self.writer.flush() } } impl WriteStyle for StylesWriter<'_, W> { fn set_header(&mut self, severity: Severity) -> renderer::GeneralWriteResult { self.writer.set_color(self.style.header(severity)) } fn set_header_message(&mut self) -> renderer::GeneralWriteResult { self.writer.set_color(&self.style.header_message) } fn set_line_number(&mut self) -> renderer::GeneralWriteResult { self.writer.set_color(&self.style.line_number) } fn set_note_bullet(&mut self) -> renderer::GeneralWriteResult { self.writer.set_color(&self.style.note_bullet) } fn set_source_border(&mut self) -> renderer::GeneralWriteResult { self.writer.set_color(&self.style.source_border) } fn set_label( &mut self, severity: Severity, label_style: LabelStyle, ) -> renderer::GeneralWriteResult { let spec = self.style.label(severity, label_style); self.writer.set_color(spec) } fn reset(&mut self) -> renderer::GeneralWriteResult { self.writer.reset() } } } #[cfg(feature = "termcolor")] impl super::renderer::WriteStyle for T where T: termcolor::WriteColor + ?Sized, { fn set_header( &mut self, severity: crate::diagnostic::Severity, ) -> super::renderer::GeneralWriteResult { self.set_color(styles::Styles::default().header(severity)) } fn set_header_message(&mut self) -> super::renderer::GeneralWriteResult { self.set_color(&styles::Styles::default().header_message) } fn set_line_number(&mut self) -> super::renderer::GeneralWriteResult { self.set_color(&styles::Styles::default().line_number) } fn set_note_bullet(&mut self) -> super::renderer::GeneralWriteResult { self.set_color(&styles::Styles::default().note_bullet) } fn set_source_border(&mut self) -> super::renderer::GeneralWriteResult { self.set_color(&styles::Styles::default().source_border) } fn set_label( &mut self, severity: crate::diagnostic::Severity, label_style: crate::diagnostic::LabelStyle, ) -> super::renderer::GeneralWriteResult { let styles = styles::Styles::default(); let spec = styles.label(severity, label_style); self.set_color(spec) } fn reset(&mut self) -> super::renderer::GeneralWriteResult { self.reset() } } /// Characters to use when rendering the diagnostic. /// /// By using [`Chars::ascii()`] you can switch to an ASCII-only format suitable /// for rendering on terminals that do not support box drawing characters. #[derive(Clone, Debug)] pub struct Chars { /// The characters to use for the top-left border of the snippet. /// Defaults to: `"┌─"` or `"-->"` with [`Chars::ascii()`]. pub snippet_start: String, /// The character to use for the left border of the source. /// Defaults to: `'│'` or `'|'` with [`Chars::ascii()`]. pub source_border_left: char, /// The character to use for the left border break of the source. /// Defaults to: `'·'` or `'.'` with [`Chars::ascii()`]. pub source_border_left_break: char, /// The character to use for the note bullet. /// Defaults to: `'='`. pub note_bullet: char, /// The character to use for marking a single-line primary label. /// Defaults to: `'^'`. pub single_primary_caret: char, /// The character to use for marking a single-line secondary label. /// Defaults to: `'-'`. pub single_secondary_caret: char, /// The character to use for marking the start of a multi-line primary label. /// Defaults to: `'^'`. pub multi_primary_caret_start: char, /// The character to use for marking the end of a multi-line primary label. /// Defaults to: `'^'`. pub multi_primary_caret_end: char, /// The character to use for marking the start of a multi-line secondary label. /// Defaults to: `'\''`. pub multi_secondary_caret_start: char, /// The character to use for marking the end of a multi-line secondary label. /// Defaults to: `'\''`. pub multi_secondary_caret_end: char, /// The character to use for the top-left corner of a multi-line label. /// Defaults to: `'╭'` or `'/'` with [`Chars::ascii()`]. pub multi_top_left: char, /// The character to use for the top of a multi-line label. /// Defaults to: `'─'` or `'-'` with [`Chars::ascii()`]. pub multi_top: char, /// The character to use for the bottom-left corner of a multi-line label. /// Defaults to: `'╰'` or `'\'` with [`Chars::ascii()`]. pub multi_bottom_left: char, /// The character to use when marking the bottom of a multi-line label. /// Defaults to: `'─'` or `'-'` with [`Chars::ascii()`]. pub multi_bottom: char, /// The character to use for the left of a multi-line label. /// Defaults to: `'│'` or `'|'` with [`Chars::ascii()`]. pub multi_left: char, /// The character to use for the left of a pointer underneath a caret. /// Defaults to: `'│'` or `'|'` with [`Chars::ascii()`]. pub pointer_left: char, } impl Default for Chars { fn default() -> Chars { Chars::box_drawing() } } impl Chars { /// A character set that uses Unicode box drawing characters. #[must_use] pub fn box_drawing() -> Chars { Chars { snippet_start: "┌─".into(), source_border_left: '│', source_border_left_break: '·', note_bullet: '=', single_primary_caret: '^', single_secondary_caret: '-', multi_primary_caret_start: '^', multi_primary_caret_end: '^', multi_secondary_caret_start: '\'', multi_secondary_caret_end: '\'', multi_top_left: '╭', multi_top: '─', multi_bottom_left: '╰', multi_bottom: '─', multi_left: '│', pointer_left: '│', } } /// A character set that only uses ASCII characters. /// /// This is useful if your terminal's font does not support box drawing /// characters well and results in output that looks similar to rustc's /// diagnostic output. #[must_use] pub fn ascii() -> Chars { Chars { snippet_start: "-->".into(), source_border_left: '|', source_border_left_break: '.', note_bullet: '=', single_primary_caret: '^', single_secondary_caret: '-', multi_primary_caret_start: '^', multi_primary_caret_end: '^', multi_secondary_caret_start: '\'', multi_secondary_caret_end: '\'', multi_top_left: '/', multi_top: '-', multi_bottom_left: '\\', multi_bottom: '-', multi_left: '|', pointer_left: '|', } } }