annotate_snippets/renderer/
mod.rs1pub(crate) mod graphics;
22pub(crate) mod no_graphics;
23pub(crate) mod source_map;
24pub(crate) mod stylesheet;
25
26mod margin;
27mod preprocess;
28mod styled_buffer;
29
30use alloc::string::String;
31
32use crate::Report;
33
34pub(crate) use graphics::ElementStyle;
35pub(crate) use graphics::UnderlineParts;
36pub(crate) use graphics::normalize_whitespace;
37pub(crate) use graphics::{LineAnnotation, LineAnnotationType, char_width, num_overlap};
38pub(crate) use stylesheet::Stylesheet;
39
40pub use anstyle::*;
41
42pub const DEFAULT_TERM_WIDTH: usize = 140;
44
45const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors");
46const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS {
47 AnsiColor::BrightCyan.on_default()
48} else {
49 AnsiColor::BrightBlue.on_default()
50};
51pub const DEFAULT_ERROR_STYLE: Style = AnsiColor::BrightRed.on_default().effects(Effects::BOLD);
53pub const DEFAULT_WARNING_STYLE: Style = if USE_WINDOWS_COLORS {
55 AnsiColor::BrightYellow.on_default()
56} else {
57 AnsiColor::Yellow.on_default()
58}
59.effects(Effects::BOLD);
60pub const DEFAULT_INFO_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
62pub const DEFAULT_NOTE_STYLE: Style = AnsiColor::BrightGreen.on_default().effects(Effects::BOLD);
64pub const DEFAULT_HELP_STYLE: Style = AnsiColor::BrightCyan.on_default().effects(Effects::BOLD);
66pub const DEFAULT_LINE_NUM_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
68pub const DEFAULT_EMPHASIS_STYLE: Style = if USE_WINDOWS_COLORS {
70 AnsiColor::BrightWhite.on_default()
71} else {
72 Style::new()
73}
74.effects(Effects::BOLD);
75pub const DEFAULT_NONE_STYLE: Style = Style::new();
77pub const DEFAULT_CONTEXT_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
79pub const DEFAULT_ADDITION_STYLE: Style = AnsiColor::BrightGreen.on_default();
81pub const DEFAULT_REMOVAL_STYLE: Style = AnsiColor::BrightRed.on_default();
83
84#[derive(Clone, Debug)]
110pub struct Renderer {
111 anonymized_origin_line_numbers: bool,
112 anonymized_snippet_line_numbers: bool,
113 term_width: usize,
114 decor_style: DecorStyle,
115 stylesheet: Stylesheet,
116 hyperlink: bool,
117 short_message: bool,
118 no_graphics: bool,
119}
120
121impl Renderer {
122 pub const fn plain() -> Self {
124 Self {
125 anonymized_origin_line_numbers: false,
126 anonymized_snippet_line_numbers: false,
127 term_width: DEFAULT_TERM_WIDTH,
128 decor_style: DecorStyle::Ascii,
129 stylesheet: Stylesheet::plain(),
130 hyperlink: false,
131 short_message: false,
132 no_graphics: false,
133 }
134 }
135
136 pub const fn styled() -> Self {
146 Self {
147 stylesheet: Stylesheet {
148 error: DEFAULT_ERROR_STYLE,
149 warning: DEFAULT_WARNING_STYLE,
150 info: DEFAULT_INFO_STYLE,
151 note: DEFAULT_NOTE_STYLE,
152 help: DEFAULT_HELP_STYLE,
153 line_num: DEFAULT_LINE_NUM_STYLE,
154 emphasis: DEFAULT_EMPHASIS_STYLE,
155 none: DEFAULT_NONE_STYLE,
156 context: DEFAULT_CONTEXT_STYLE,
157 addition: DEFAULT_ADDITION_STYLE,
158 removal: DEFAULT_REMOVAL_STYLE,
159 },
160 hyperlink: true,
161 ..Self::plain()
162 }
163 }
164
165 #[doc = include_str!("../../examples/no_graphics.txt")]
186 pub const fn no_graphics(mut self, no_graphics: bool) -> Self {
188 self.no_graphics = no_graphics;
189 self
190 }
191
192 pub const fn short_message(mut self, short_message: bool) -> Self {
194 self.short_message = short_message;
195 self
196 }
197
198 pub const fn term_width(mut self, term_width: usize) -> Self {
202 self.term_width = term_width;
203 self
204 }
205
206 pub const fn decor_style(mut self, decor_style: DecorStyle) -> Self {
208 self.decor_style = decor_style;
209 self
210 }
211
212 pub const fn anonymized_snippet_line_numbers(mut self, yes: bool) -> Self {
226 self.anonymized_snippet_line_numbers = yes;
227 self
228 }
229
230 #[deprecated(
232 since = "0.12.17",
233 note = "replaced with `Renderer::anonymized_snippet_line_numbers`"
234 )]
235 pub const fn anonymized_line_numbers(mut self, yes: bool) -> Self {
236 self.anonymized_snippet_line_numbers = yes;
237 self
238 }
239
240 pub const fn anonymized_origin_line_numbers(mut self, yes: bool) -> Self {
254 self.anonymized_origin_line_numbers = yes;
255 self
256 }
257}
258
259impl Renderer {
260 pub fn render(&self, groups: Report<'_>) -> String {
262 if self.no_graphics {
263 no_graphics::render_no_graphics(self, groups).unwrap()
264 } else {
265 graphics::render(self, groups)
266 }
267 }
268}
269
270impl Renderer {
272 pub const fn error(mut self, style: Style) -> Self {
274 self.stylesheet.error = style;
275 self
276 }
277
278 pub const fn warning(mut self, style: Style) -> Self {
280 self.stylesheet.warning = style;
281 self
282 }
283
284 pub const fn info(mut self, style: Style) -> Self {
286 self.stylesheet.info = style;
287 self
288 }
289
290 pub const fn note(mut self, style: Style) -> Self {
292 self.stylesheet.note = style;
293 self
294 }
295
296 pub const fn help(mut self, style: Style) -> Self {
298 self.stylesheet.help = style;
299 self
300 }
301
302 pub const fn line_num(mut self, style: Style) -> Self {
304 self.stylesheet.line_num = style;
305 self
306 }
307
308 pub const fn emphasis(mut self, style: Style) -> Self {
311 self.stylesheet.emphasis = style;
312 self
313 }
314
315 pub const fn context(mut self, style: Style) -> Self {
317 self.stylesheet.context = style;
318 self
319 }
320
321 pub const fn addition(mut self, style: Style) -> Self {
323 self.stylesheet.addition = style;
324 self
325 }
326
327 pub const fn removal(mut self, style: Style) -> Self {
329 self.stylesheet.removal = style;
330 self
331 }
332
333 pub const fn none(mut self, style: Style) -> Self {
335 self.stylesheet.none = style;
336 self
337 }
338
339 pub const fn hyperlink(mut self, hyperlink: bool) -> Self {
341 self.hyperlink = hyperlink;
342 self
343 }
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
348pub enum DecorStyle {
349 Ascii,
350 Unicode,
351}
352
353impl DecorStyle {
354 fn col_separator(&self) -> char {
355 match self {
356 Self::Ascii => '|',
357 Self::Unicode => '│',
358 }
359 }
360
361 fn note_separator(&self, is_cont: bool) -> &str {
362 match self {
363 Self::Ascii => "= ",
364 Self::Unicode if is_cont => "├ ",
365 Self::Unicode => "╰ ",
366 }
367 }
368
369 fn multi_suggestion_separator(&self) -> &'static str {
370 match self {
371 Self::Ascii => "|",
372 Self::Unicode => "├╴",
373 }
374 }
375
376 fn file_start(&self, is_first: bool, alone: bool) -> &'static str {
377 match self {
378 Self::Ascii => "--> ",
379 Self::Unicode if is_first && alone => " ─▸ ",
380 Self::Unicode if is_first => " ╭▸ ",
381 Self::Unicode => " ├▸ ",
382 }
383 }
384
385 fn secondary_file_start(&self) -> &'static str {
386 match self {
387 Self::Ascii => "::: ",
388 Self::Unicode => " ⸬ ",
389 }
390 }
391
392 fn diff(&self) -> char {
393 match self {
394 Self::Ascii => '~',
395 Self::Unicode => '±',
396 }
397 }
398
399 fn margin(&self) -> &'static str {
400 match self {
401 Self::Ascii => "...",
402 Self::Unicode => "…",
403 }
404 }
405
406 fn underline(&self, is_primary: bool) -> UnderlineParts {
407 match (self, is_primary) {
432 (Self::Ascii, true) => UnderlineParts {
433 style: ElementStyle::UnderlinePrimary,
434 underline: '^',
435 label_start: '^',
436 vertical_text_line: '|',
437 multiline_vertical: '|',
438 multiline_horizontal: '_',
439 multiline_whole_line: '/',
440 multiline_start_down: '^',
441 bottom_right: '|',
442 top_left: ' ',
443 top_right_flat: '^',
444 bottom_left: '|',
445 multiline_end_up: '^',
446 multiline_end_same_line: '^',
447 multiline_bottom_right_with_text: '|',
448 },
449 (Self::Ascii, false) => UnderlineParts {
450 style: ElementStyle::UnderlineSecondary,
451 underline: '-',
452 label_start: '-',
453 vertical_text_line: '|',
454 multiline_vertical: '|',
455 multiline_horizontal: '_',
456 multiline_whole_line: '/',
457 multiline_start_down: '-',
458 bottom_right: '|',
459 top_left: ' ',
460 top_right_flat: '-',
461 bottom_left: '|',
462 multiline_end_up: '-',
463 multiline_end_same_line: '-',
464 multiline_bottom_right_with_text: '|',
465 },
466 (Self::Unicode, true) => UnderlineParts {
467 style: ElementStyle::UnderlinePrimary,
468 underline: '━',
469 label_start: '┯',
470 vertical_text_line: '│',
471 multiline_vertical: '┃',
472 multiline_horizontal: '━',
473 multiline_whole_line: '┏',
474 multiline_start_down: '╿',
475 bottom_right: '┙',
476 top_left: '┏',
477 top_right_flat: '┛',
478 bottom_left: '┗',
479 multiline_end_up: '╿',
480 multiline_end_same_line: '┛',
481 multiline_bottom_right_with_text: '┥',
482 },
483 (Self::Unicode, false) => UnderlineParts {
484 style: ElementStyle::UnderlineSecondary,
485 underline: '─',
486 label_start: '┬',
487 vertical_text_line: '│',
488 multiline_vertical: '│',
489 multiline_horizontal: '─',
490 multiline_whole_line: '┌',
491 multiline_start_down: '│',
492 bottom_right: '┘',
493 top_left: '┌',
494 top_right_flat: '┘',
495 bottom_left: '└',
496 multiline_end_up: '│',
497 multiline_end_same_line: '┘',
498 multiline_bottom_right_with_text: '┤',
499 },
500 }
501 }
502}