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 alloc::string::String;
29
30use crate::Report;
31
32pub(crate) use render::ElementStyle;
33pub(crate) use render::UnderlineParts;
34pub(crate) use render::normalize_whitespace;
35pub(crate) use render::{LineAnnotation, LineAnnotationType, char_width, num_overlap};
36pub(crate) use stylesheet::Stylesheet;
37
38pub use anstyle::*;
39
40pub const DEFAULT_TERM_WIDTH: usize = 140;
42
43const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors");
44const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS {
45 AnsiColor::BrightCyan.on_default()
46} else {
47 AnsiColor::BrightBlue.on_default()
48};
49pub const DEFAULT_ERROR_STYLE: Style = AnsiColor::BrightRed.on_default().effects(Effects::BOLD);
51pub const DEFAULT_WARNING_STYLE: Style = if USE_WINDOWS_COLORS {
53 AnsiColor::BrightYellow.on_default()
54} else {
55 AnsiColor::Yellow.on_default()
56}
57.effects(Effects::BOLD);
58pub const DEFAULT_INFO_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
60pub const DEFAULT_NOTE_STYLE: Style = AnsiColor::BrightGreen.on_default().effects(Effects::BOLD);
62pub const DEFAULT_HELP_STYLE: Style = AnsiColor::BrightCyan.on_default().effects(Effects::BOLD);
64pub const DEFAULT_LINE_NUM_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
66pub const DEFAULT_EMPHASIS_STYLE: Style = if USE_WINDOWS_COLORS {
68 AnsiColor::BrightWhite.on_default()
69} else {
70 Style::new()
71}
72.effects(Effects::BOLD);
73pub const DEFAULT_NONE_STYLE: Style = Style::new();
75pub const DEFAULT_CONTEXT_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
77pub const DEFAULT_ADDITION_STYLE: Style = AnsiColor::BrightGreen.on_default();
79pub const DEFAULT_REMOVAL_STYLE: Style = AnsiColor::BrightRed.on_default();
81
82#[derive(Clone, Debug)]
108pub struct Renderer {
109 anonymized_line_numbers: bool,
110 term_width: usize,
111 decor_style: DecorStyle,
112 stylesheet: Stylesheet,
113 hyperlink: bool,
114 short_message: bool,
115 cut_indicator: Option<&'static str>,
116}
117
118impl Renderer {
119 pub const fn plain() -> Self {
121 Self {
122 anonymized_line_numbers: false,
123 term_width: DEFAULT_TERM_WIDTH,
124 decor_style: DecorStyle::Ascii,
125 stylesheet: Stylesheet::plain(),
126 hyperlink: false,
127 short_message: false,
128 cut_indicator: None,
129 }
130 }
131
132 pub const fn styled() -> Self {
142 Self {
143 stylesheet: Stylesheet {
144 error: DEFAULT_ERROR_STYLE,
145 warning: DEFAULT_WARNING_STYLE,
146 info: DEFAULT_INFO_STYLE,
147 note: DEFAULT_NOTE_STYLE,
148 help: DEFAULT_HELP_STYLE,
149 line_num: DEFAULT_LINE_NUM_STYLE,
150 emphasis: DEFAULT_EMPHASIS_STYLE,
151 none: DEFAULT_NONE_STYLE,
152 context: DEFAULT_CONTEXT_STYLE,
153 addition: DEFAULT_ADDITION_STYLE,
154 removal: DEFAULT_REMOVAL_STYLE,
155 },
156 hyperlink: true,
157 ..Self::plain()
158 }
159 }
160
161 pub const fn short_message(mut self, short_message: bool) -> Self {
163 self.short_message = short_message;
164 self
165 }
166
167 pub const fn term_width(mut self, term_width: usize) -> Self {
171 self.term_width = term_width;
172 self
173 }
174
175 pub const fn decor_style(mut self, decor_style: DecorStyle) -> Self {
177 self.decor_style = decor_style;
178 self
179 }
180
181 pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
195 self.anonymized_line_numbers = anonymized_line_numbers;
196 self
197 }
198}
199
200impl Renderer {
201 pub fn render(&self, groups: Report<'_>) -> String {
203 render::render(self, groups)
204 }
205}
206
207impl Renderer {
209 pub const fn error(mut self, style: Style) -> Self {
211 self.stylesheet.error = style;
212 self
213 }
214
215 pub const fn warning(mut self, style: Style) -> Self {
217 self.stylesheet.warning = style;
218 self
219 }
220
221 pub const fn info(mut self, style: Style) -> Self {
223 self.stylesheet.info = style;
224 self
225 }
226
227 pub const fn note(mut self, style: Style) -> Self {
229 self.stylesheet.note = style;
230 self
231 }
232
233 pub const fn help(mut self, style: Style) -> Self {
235 self.stylesheet.help = style;
236 self
237 }
238
239 pub const fn line_num(mut self, style: Style) -> Self {
241 self.stylesheet.line_num = style;
242 self
243 }
244
245 pub const fn emphasis(mut self, style: Style) -> Self {
248 self.stylesheet.emphasis = style;
249 self
250 }
251
252 pub const fn context(mut self, style: Style) -> Self {
254 self.stylesheet.context = style;
255 self
256 }
257
258 pub const fn addition(mut self, style: Style) -> Self {
260 self.stylesheet.addition = style;
261 self
262 }
263
264 pub const fn removal(mut self, style: Style) -> Self {
266 self.stylesheet.removal = style;
267 self
268 }
269
270 pub const fn none(mut self, style: Style) -> Self {
272 self.stylesheet.none = style;
273 self
274 }
275
276 pub const fn hyperlink(mut self, hyperlink: bool) -> Self {
277 self.hyperlink = hyperlink;
278 self
279 }
280
281 pub const fn cut_indicator(mut self, cut: &'static str) -> Self {
285 self.cut_indicator = Some(cut);
286 self
287 }
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum DecorStyle {
293 Ascii,
294 Unicode,
295}
296
297impl DecorStyle {
298 fn col_separator(&self) -> char {
299 match self {
300 DecorStyle::Ascii => '|',
301 DecorStyle::Unicode => '│',
302 }
303 }
304
305 fn note_separator(&self, is_cont: bool) -> &str {
306 match self {
307 DecorStyle::Ascii => "= ",
308 DecorStyle::Unicode if is_cont => "├ ",
309 DecorStyle::Unicode => "╰ ",
310 }
311 }
312
313 fn multi_suggestion_separator(&self) -> &'static str {
314 match self {
315 DecorStyle::Ascii => "|",
316 DecorStyle::Unicode => "├╴",
317 }
318 }
319
320 fn file_start(&self, is_first: bool, alone: bool) -> &'static str {
321 match self {
322 DecorStyle::Ascii => "--> ",
323 DecorStyle::Unicode if is_first && alone => " ─▸ ",
324 DecorStyle::Unicode if is_first => " ╭▸ ",
325 DecorStyle::Unicode => " ├▸ ",
326 }
327 }
328
329 fn secondary_file_start(&self) -> &'static str {
330 match self {
331 DecorStyle::Ascii => "::: ",
332 DecorStyle::Unicode => " ⸬ ",
333 }
334 }
335
336 fn diff(&self) -> char {
337 match self {
338 DecorStyle::Ascii => '~',
339 DecorStyle::Unicode => '±',
340 }
341 }
342
343 fn margin(&self) -> &'static str {
344 match self {
345 DecorStyle::Ascii => "...",
346 DecorStyle::Unicode => "…",
347 }
348 }
349
350 fn underline(&self, is_primary: bool) -> UnderlineParts {
351 match (self, is_primary) {
376 (DecorStyle::Ascii, true) => UnderlineParts {
377 style: ElementStyle::UnderlinePrimary,
378 underline: '^',
379 label_start: '^',
380 vertical_text_line: '|',
381 multiline_vertical: '|',
382 multiline_horizontal: '_',
383 multiline_whole_line: '/',
384 multiline_start_down: '^',
385 bottom_right: '|',
386 top_left: ' ',
387 top_right_flat: '^',
388 bottom_left: '|',
389 multiline_end_up: '^',
390 multiline_end_same_line: '^',
391 multiline_bottom_right_with_text: '|',
392 },
393 (DecorStyle::Ascii, false) => UnderlineParts {
394 style: ElementStyle::UnderlineSecondary,
395 underline: '-',
396 label_start: '-',
397 vertical_text_line: '|',
398 multiline_vertical: '|',
399 multiline_horizontal: '_',
400 multiline_whole_line: '/',
401 multiline_start_down: '-',
402 bottom_right: '|',
403 top_left: ' ',
404 top_right_flat: '-',
405 bottom_left: '|',
406 multiline_end_up: '-',
407 multiline_end_same_line: '-',
408 multiline_bottom_right_with_text: '|',
409 },
410 (DecorStyle::Unicode, true) => UnderlineParts {
411 style: ElementStyle::UnderlinePrimary,
412 underline: '━',
413 label_start: '┯',
414 vertical_text_line: '│',
415 multiline_vertical: '┃',
416 multiline_horizontal: '━',
417 multiline_whole_line: '┏',
418 multiline_start_down: '╿',
419 bottom_right: '┙',
420 top_left: '┏',
421 top_right_flat: '┛',
422 bottom_left: '┗',
423 multiline_end_up: '╿',
424 multiline_end_same_line: '┛',
425 multiline_bottom_right_with_text: '┥',
426 },
427 (DecorStyle::Unicode, false) => UnderlineParts {
428 style: ElementStyle::UnderlineSecondary,
429 underline: '─',
430 label_start: '┬',
431 vertical_text_line: '│',
432 multiline_vertical: '│',
433 multiline_horizontal: '─',
434 multiline_whole_line: '┌',
435 multiline_start_down: '│',
436 bottom_right: '┘',
437 top_left: '┌',
438 top_right_flat: '┘',
439 bottom_left: '└',
440 multiline_end_up: '│',
441 multiline_end_same_line: '┘',
442 multiline_bottom_right_with_text: '┤',
443 },
444 }
445 }
446}