annotate_snippets/renderer/
mod.rs1pub(crate) mod render;
22pub(crate) mod source_map;
23pub(crate) mod stylesheet;
24
25mod margin;
26mod styled_buffer;
27
28use crate::Report;
29
30pub(crate) use render::normalize_whitespace;
31pub(crate) use render::ElementStyle;
32pub(crate) use render::UnderlineParts;
33pub(crate) use render::{char_width, num_overlap, LineAnnotation, LineAnnotationType};
34pub(crate) use stylesheet::Stylesheet;
35
36pub use anstyle::*;
37
38pub const DEFAULT_TERM_WIDTH: usize = 140;
40
41#[derive(Clone, Debug)]
67pub struct Renderer {
68 anonymized_line_numbers: bool,
69 term_width: usize,
70 decor_style: DecorStyle,
71 stylesheet: Stylesheet,
72 short_message: bool,
73}
74
75impl Renderer {
76 pub const fn plain() -> Self {
78 Self {
79 anonymized_line_numbers: false,
80 term_width: DEFAULT_TERM_WIDTH,
81 decor_style: DecorStyle::Ascii,
82 stylesheet: Stylesheet::plain(),
83 short_message: false,
84 }
85 }
86
87 pub const fn styled() -> Self {
97 const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors");
98 const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS {
99 AnsiColor::BrightCyan.on_default()
100 } else {
101 AnsiColor::BrightBlue.on_default()
102 };
103 Self {
104 stylesheet: Stylesheet {
105 error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD),
106 warning: if USE_WINDOWS_COLORS {
107 AnsiColor::BrightYellow.on_default()
108 } else {
109 AnsiColor::Yellow.on_default()
110 }
111 .effects(Effects::BOLD),
112 info: BRIGHT_BLUE.effects(Effects::BOLD),
113 note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD),
114 help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
115 line_num: BRIGHT_BLUE.effects(Effects::BOLD),
116 emphasis: if USE_WINDOWS_COLORS {
117 AnsiColor::BrightWhite.on_default()
118 } else {
119 Style::new()
120 }
121 .effects(Effects::BOLD),
122 none: Style::new(),
123 context: BRIGHT_BLUE.effects(Effects::BOLD),
124 addition: AnsiColor::BrightGreen.on_default(),
125 removal: AnsiColor::BrightRed.on_default(),
126 },
127 ..Self::plain()
128 }
129 }
130
131 pub const fn short_message(mut self, short_message: bool) -> Self {
133 self.short_message = short_message;
134 self
135 }
136
137 pub const fn term_width(mut self, term_width: usize) -> Self {
141 self.term_width = term_width;
142 self
143 }
144
145 pub const fn decor_style(mut self, decor_style: DecorStyle) -> Self {
147 self.decor_style = decor_style;
148 self
149 }
150
151 pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
165 self.anonymized_line_numbers = anonymized_line_numbers;
166 self
167 }
168}
169
170impl Renderer {
171 pub fn render(&self, groups: Report<'_>) -> String {
173 render::render(self, groups)
174 }
175}
176
177impl Renderer {
179 pub const fn error(mut self, style: Style) -> Self {
181 self.stylesheet.error = style;
182 self
183 }
184
185 pub const fn warning(mut self, style: Style) -> Self {
187 self.stylesheet.warning = style;
188 self
189 }
190
191 pub const fn info(mut self, style: Style) -> Self {
193 self.stylesheet.info = style;
194 self
195 }
196
197 pub const fn note(mut self, style: Style) -> Self {
199 self.stylesheet.note = style;
200 self
201 }
202
203 pub const fn help(mut self, style: Style) -> Self {
205 self.stylesheet.help = style;
206 self
207 }
208
209 pub const fn line_num(mut self, style: Style) -> Self {
211 self.stylesheet.line_num = style;
212 self
213 }
214
215 pub const fn emphasis(mut self, style: Style) -> Self {
217 self.stylesheet.emphasis = style;
218 self
219 }
220
221 pub const fn none(mut self, style: Style) -> Self {
223 self.stylesheet.none = style;
224 self
225 }
226
227 pub const fn context(mut self, style: Style) -> Self {
229 self.stylesheet.context = style;
230 self
231 }
232
233 pub const fn addition(mut self, style: Style) -> Self {
235 self.stylesheet.addition = style;
236 self
237 }
238
239 pub const fn removal(mut self, style: Style) -> Self {
241 self.stylesheet.removal = style;
242 self
243 }
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum DecorStyle {
249 Ascii,
250 Unicode,
251}
252
253impl DecorStyle {
254 fn col_separator(&self) -> char {
255 match self {
256 DecorStyle::Ascii => '|',
257 DecorStyle::Unicode => '│',
258 }
259 }
260
261 fn note_separator(&self, is_cont: bool) -> &str {
262 match self {
263 DecorStyle::Ascii => "= ",
264 DecorStyle::Unicode if is_cont => "├ ",
265 DecorStyle::Unicode => "╰ ",
266 }
267 }
268
269 fn multi_suggestion_separator(&self) -> &'static str {
270 match self {
271 DecorStyle::Ascii => "|",
272 DecorStyle::Unicode => "├╴",
273 }
274 }
275
276 fn file_start(&self, is_first: bool) -> &'static str {
277 match self {
278 DecorStyle::Ascii => "--> ",
279 DecorStyle::Unicode if is_first => " ╭▸ ",
280 DecorStyle::Unicode => " ├▸ ",
281 }
282 }
283
284 fn secondary_file_start(&self) -> &'static str {
285 match self {
286 DecorStyle::Ascii => "::: ",
287 DecorStyle::Unicode => " ⸬ ",
288 }
289 }
290
291 fn diff(&self) -> char {
292 match self {
293 DecorStyle::Ascii => '~',
294 DecorStyle::Unicode => '±',
295 }
296 }
297
298 fn margin(&self) -> &'static str {
299 match self {
300 DecorStyle::Ascii => "...",
301 DecorStyle::Unicode => "…",
302 }
303 }
304
305 fn underline(&self, is_primary: bool) -> UnderlineParts {
306 match (self, is_primary) {
331 (DecorStyle::Ascii, true) => UnderlineParts {
332 style: ElementStyle::UnderlinePrimary,
333 underline: '^',
334 label_start: '^',
335 vertical_text_line: '|',
336 multiline_vertical: '|',
337 multiline_horizontal: '_',
338 multiline_whole_line: '/',
339 multiline_start_down: '^',
340 bottom_right: '|',
341 top_left: ' ',
342 top_right_flat: '^',
343 bottom_left: '|',
344 multiline_end_up: '^',
345 multiline_end_same_line: '^',
346 multiline_bottom_right_with_text: '|',
347 },
348 (DecorStyle::Ascii, false) => UnderlineParts {
349 style: ElementStyle::UnderlineSecondary,
350 underline: '-',
351 label_start: '-',
352 vertical_text_line: '|',
353 multiline_vertical: '|',
354 multiline_horizontal: '_',
355 multiline_whole_line: '/',
356 multiline_start_down: '-',
357 bottom_right: '|',
358 top_left: ' ',
359 top_right_flat: '-',
360 bottom_left: '|',
361 multiline_end_up: '-',
362 multiline_end_same_line: '-',
363 multiline_bottom_right_with_text: '|',
364 },
365 (DecorStyle::Unicode, true) => UnderlineParts {
366 style: ElementStyle::UnderlinePrimary,
367 underline: '━',
368 label_start: '┯',
369 vertical_text_line: '│',
370 multiline_vertical: '┃',
371 multiline_horizontal: '━',
372 multiline_whole_line: '┏',
373 multiline_start_down: '╿',
374 bottom_right: '┙',
375 top_left: '┏',
376 top_right_flat: '┛',
377 bottom_left: '┗',
378 multiline_end_up: '╿',
379 multiline_end_same_line: '┛',
380 multiline_bottom_right_with_text: '┥',
381 },
382 (DecorStyle::Unicode, false) => UnderlineParts {
383 style: ElementStyle::UnderlineSecondary,
384 underline: '─',
385 label_start: '┬',
386 vertical_text_line: '│',
387 multiline_vertical: '│',
388 multiline_horizontal: '─',
389 multiline_whole_line: '┌',
390 multiline_start_down: '│',
391 bottom_right: '┘',
392 top_left: '┌',
393 top_right_flat: '┘',
394 bottom_left: '└',
395 multiline_end_up: '│',
396 multiline_end_same_line: '┘',
397 multiline_bottom_right_with_text: '┤',
398 },
399 }
400 }
401}