annotate_snippets/renderer/
mod.rs

1//! The renderer for [`Message`]s
2//!
3//! # Example
4//! ```
5//! use annotate_snippets::{Renderer, Snippet, Level};
6//! let snippet = Level::Error.title("mismatched types")
7//!     .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs"))
8//!     .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
9//!
10//!  let renderer = Renderer::styled();
11//!  println!("{}", renderer.render(snippet));
12
13mod display_list;
14mod margin;
15mod styled_buffer;
16pub(crate) mod stylesheet;
17
18use crate::snippet::Message;
19pub use anstyle::*;
20use display_list::DisplayList;
21use margin::Margin;
22use std::fmt::Display;
23use stylesheet::Stylesheet;
24
25pub const DEFAULT_TERM_WIDTH: usize = 140;
26
27/// A renderer for [`Message`]s
28#[derive(Clone, Debug)]
29pub struct Renderer {
30    anonymized_line_numbers: bool,
31    term_width: usize,
32    stylesheet: Stylesheet,
33}
34
35impl Renderer {
36    /// No terminal styling
37    pub const fn plain() -> Self {
38        Self {
39            anonymized_line_numbers: false,
40            term_width: DEFAULT_TERM_WIDTH,
41            stylesheet: Stylesheet::plain(),
42        }
43    }
44
45    /// Default terminal styling
46    ///
47    /// # Note
48    /// When testing styled terminal output, see the [`testing-colors` feature](crate#features)
49    pub const fn styled() -> Self {
50        const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors");
51        const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS {
52            AnsiColor::BrightCyan.on_default()
53        } else {
54            AnsiColor::BrightBlue.on_default()
55        };
56        Self {
57            stylesheet: Stylesheet {
58                error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD),
59                warning: if USE_WINDOWS_COLORS {
60                    AnsiColor::BrightYellow.on_default()
61                } else {
62                    AnsiColor::Yellow.on_default()
63                }
64                .effects(Effects::BOLD),
65                info: BRIGHT_BLUE.effects(Effects::BOLD),
66                note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD),
67                help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
68                line_no: BRIGHT_BLUE.effects(Effects::BOLD),
69                emphasis: if USE_WINDOWS_COLORS {
70                    AnsiColor::BrightWhite.on_default()
71                } else {
72                    Style::new()
73                }
74                .effects(Effects::BOLD),
75                none: Style::new(),
76            },
77            ..Self::plain()
78        }
79    }
80
81    /// Anonymize line numbers
82    ///
83    /// This enables (or disables) line number anonymization. When enabled, line numbers are replaced
84    /// with `LL`.
85    ///
86    /// # Example
87    ///
88    /// ```text
89    ///   --> $DIR/whitespace-trimming.rs:4:193
90    ///    |
91    /// LL | ...                   let _: () = 42;
92    ///    |                                   ^^ expected (), found integer
93    ///    |
94    /// ```
95    pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
96        self.anonymized_line_numbers = anonymized_line_numbers;
97        self
98    }
99
100    // Set the terminal width
101    pub const fn term_width(mut self, term_width: usize) -> Self {
102        self.term_width = term_width;
103        self
104    }
105
106    /// Set the output style for `error`
107    pub const fn error(mut self, style: Style) -> Self {
108        self.stylesheet.error = style;
109        self
110    }
111
112    /// Set the output style for `warning`
113    pub const fn warning(mut self, style: Style) -> Self {
114        self.stylesheet.warning = style;
115        self
116    }
117
118    /// Set the output style for `info`
119    pub const fn info(mut self, style: Style) -> Self {
120        self.stylesheet.info = style;
121        self
122    }
123
124    /// Set the output style for `note`
125    pub const fn note(mut self, style: Style) -> Self {
126        self.stylesheet.note = style;
127        self
128    }
129
130    /// Set the output style for `help`
131    pub const fn help(mut self, style: Style) -> Self {
132        self.stylesheet.help = style;
133        self
134    }
135
136    /// Set the output style for line numbers
137    pub const fn line_no(mut self, style: Style) -> Self {
138        self.stylesheet.line_no = style;
139        self
140    }
141
142    /// Set the output style for emphasis
143    pub const fn emphasis(mut self, style: Style) -> Self {
144        self.stylesheet.emphasis = style;
145        self
146    }
147
148    /// Set the output style for none
149    pub const fn none(mut self, style: Style) -> Self {
150        self.stylesheet.none = style;
151        self
152    }
153
154    /// Render a snippet into a `Display`able object
155    pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a {
156        DisplayList::new(
157            msg,
158            &self.stylesheet,
159            self.anonymized_line_numbers,
160            self.term_width,
161        )
162    }
163}