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::normalize_whitespace;
33pub(crate) use render::ElementStyle;
34pub(crate) use render::UnderlineParts;
35pub(crate) use render::{char_width, num_overlap, LineAnnotation, LineAnnotationType};
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 short_message: bool,
114}
115
116impl Renderer {
117 pub const fn plain() -> Self {
119 Self {
120 anonymized_line_numbers: false,
121 term_width: DEFAULT_TERM_WIDTH,
122 decor_style: DecorStyle::Ascii,
123 stylesheet: Stylesheet::plain(),
124 short_message: false,
125 }
126 }
127
128 pub const fn styled() -> Self {
138 Self {
139 stylesheet: Stylesheet {
140 error: DEFAULT_ERROR_STYLE,
141 warning: DEFAULT_WARNING_STYLE,
142 info: DEFAULT_INFO_STYLE,
143 note: DEFAULT_NOTE_STYLE,
144 help: DEFAULT_HELP_STYLE,
145 line_num: DEFAULT_LINE_NUM_STYLE,
146 emphasis: DEFAULT_EMPHASIS_STYLE,
147 none: DEFAULT_NONE_STYLE,
148 context: DEFAULT_CONTEXT_STYLE,
149 addition: DEFAULT_ADDITION_STYLE,
150 removal: DEFAULT_REMOVAL_STYLE,
151 },
152 ..Self::plain()
153 }
154 }
155
156 pub const fn short_message(mut self, short_message: bool) -> Self {
158 self.short_message = short_message;
159 self
160 }
161
162 pub const fn term_width(mut self, term_width: usize) -> Self {
166 self.term_width = term_width;
167 self
168 }
169
170 pub const fn decor_style(mut self, decor_style: DecorStyle) -> Self {
172 self.decor_style = decor_style;
173 self
174 }
175
176 pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
190 self.anonymized_line_numbers = anonymized_line_numbers;
191 self
192 }
193}
194
195impl Renderer {
196 pub fn render(&self, groups: Report<'_>) -> String {
198 render::render(self, groups)
199 }
200}
201
202impl Renderer {
204 pub const fn error(mut self, style: Style) -> Self {
206 self.stylesheet.error = style;
207 self
208 }
209
210 pub const fn warning(mut self, style: Style) -> Self {
212 self.stylesheet.warning = style;
213 self
214 }
215
216 pub const fn info(mut self, style: Style) -> Self {
218 self.stylesheet.info = style;
219 self
220 }
221
222 pub const fn note(mut self, style: Style) -> Self {
224 self.stylesheet.note = style;
225 self
226 }
227
228 pub const fn help(mut self, style: Style) -> Self {
230 self.stylesheet.help = style;
231 self
232 }
233
234 pub const fn line_num(mut self, style: Style) -> Self {
236 self.stylesheet.line_num = style;
237 self
238 }
239
240 pub const fn emphasis(mut self, style: Style) -> Self {
243 self.stylesheet.emphasis = style;
244 self
245 }
246
247 pub const fn context(mut self, style: Style) -> Self {
249 self.stylesheet.context = style;
250 self
251 }
252
253 pub const fn addition(mut self, style: Style) -> Self {
255 self.stylesheet.addition = style;
256 self
257 }
258
259 pub const fn removal(mut self, style: Style) -> Self {
261 self.stylesheet.removal = style;
262 self
263 }
264
265 pub const fn none(mut self, style: Style) -> Self {
267 self.stylesheet.none = style;
268 self
269 }
270}
271
272#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub enum DecorStyle {
275 Ascii,
276 Unicode,
277}
278
279impl DecorStyle {
280 fn col_separator(&self) -> char {
281 match self {
282 DecorStyle::Ascii => '|',
283 DecorStyle::Unicode => '│',
284 }
285 }
286
287 fn note_separator(&self, is_cont: bool) -> &str {
288 match self {
289 DecorStyle::Ascii => "= ",
290 DecorStyle::Unicode if is_cont => "├ ",
291 DecorStyle::Unicode => "╰ ",
292 }
293 }
294
295 fn multi_suggestion_separator(&self) -> &'static str {
296 match self {
297 DecorStyle::Ascii => "|",
298 DecorStyle::Unicode => "├╴",
299 }
300 }
301
302 fn file_start(&self, is_first: bool, alone: bool) -> &'static str {
303 match self {
304 DecorStyle::Ascii => "--> ",
305 DecorStyle::Unicode if is_first && alone => " ─▸ ",
306 DecorStyle::Unicode if is_first => " ╭▸ ",
307 DecorStyle::Unicode => " ├▸ ",
308 }
309 }
310
311 fn secondary_file_start(&self) -> &'static str {
312 match self {
313 DecorStyle::Ascii => "::: ",
314 DecorStyle::Unicode => " ⸬ ",
315 }
316 }
317
318 fn diff(&self) -> char {
319 match self {
320 DecorStyle::Ascii => '~',
321 DecorStyle::Unicode => '±',
322 }
323 }
324
325 fn margin(&self) -> &'static str {
326 match self {
327 DecorStyle::Ascii => "...",
328 DecorStyle::Unicode => "…",
329 }
330 }
331
332 fn underline(&self, is_primary: bool) -> UnderlineParts {
333 match (self, is_primary) {
358 (DecorStyle::Ascii, true) => UnderlineParts {
359 style: ElementStyle::UnderlinePrimary,
360 underline: '^',
361 label_start: '^',
362 vertical_text_line: '|',
363 multiline_vertical: '|',
364 multiline_horizontal: '_',
365 multiline_whole_line: '/',
366 multiline_start_down: '^',
367 bottom_right: '|',
368 top_left: ' ',
369 top_right_flat: '^',
370 bottom_left: '|',
371 multiline_end_up: '^',
372 multiline_end_same_line: '^',
373 multiline_bottom_right_with_text: '|',
374 },
375 (DecorStyle::Ascii, false) => UnderlineParts {
376 style: ElementStyle::UnderlineSecondary,
377 underline: '-',
378 label_start: '-',
379 vertical_text_line: '|',
380 multiline_vertical: '|',
381 multiline_horizontal: '_',
382 multiline_whole_line: '/',
383 multiline_start_down: '-',
384 bottom_right: '|',
385 top_left: ' ',
386 top_right_flat: '-',
387 bottom_left: '|',
388 multiline_end_up: '-',
389 multiline_end_same_line: '-',
390 multiline_bottom_right_with_text: '|',
391 },
392 (DecorStyle::Unicode, true) => UnderlineParts {
393 style: ElementStyle::UnderlinePrimary,
394 underline: '━',
395 label_start: '┯',
396 vertical_text_line: '│',
397 multiline_vertical: '┃',
398 multiline_horizontal: '━',
399 multiline_whole_line: '┏',
400 multiline_start_down: '╿',
401 bottom_right: '┙',
402 top_left: '┏',
403 top_right_flat: '┛',
404 bottom_left: '┗',
405 multiline_end_up: '╿',
406 multiline_end_same_line: '┛',
407 multiline_bottom_right_with_text: '┥',
408 },
409 (DecorStyle::Unicode, false) => UnderlineParts {
410 style: ElementStyle::UnderlineSecondary,
411 underline: '─',
412 label_start: '┬',
413 vertical_text_line: '│',
414 multiline_vertical: '│',
415 multiline_horizontal: '─',
416 multiline_whole_line: '┌',
417 multiline_start_down: '│',
418 bottom_right: '┘',
419 top_left: '┌',
420 top_right_flat: '┘',
421 bottom_left: '└',
422 multiline_end_up: '│',
423 multiline_end_same_line: '┘',
424 multiline_bottom_right_with_text: '┤',
425 },
426 }
427 }
428}