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
41const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors");
42const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS {
43 AnsiColor::BrightCyan.on_default()
44} else {
45 AnsiColor::BrightBlue.on_default()
46};
47pub const DEFAULT_ERROR_STYLE: Style = AnsiColor::BrightRed.on_default().effects(Effects::BOLD);
49pub const DEFAULT_WARNING_STYLE: Style = if USE_WINDOWS_COLORS {
51 AnsiColor::BrightYellow.on_default()
52} else {
53 AnsiColor::Yellow.on_default()
54}
55.effects(Effects::BOLD);
56pub const DEFAULT_INFO_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
58pub const DEFAULT_NOTE_STYLE: Style = AnsiColor::BrightGreen.on_default().effects(Effects::BOLD);
60pub const DEFAULT_HELP_STYLE: Style = AnsiColor::BrightCyan.on_default().effects(Effects::BOLD);
62pub const DEFAULT_LINE_NUM_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
64pub const DEFAULT_EMPHASIS_STYLE: Style = if USE_WINDOWS_COLORS {
66 AnsiColor::BrightWhite.on_default()
67} else {
68 Style::new()
69}
70.effects(Effects::BOLD);
71pub const DEFAULT_NONE_STYLE: Style = Style::new();
73pub const DEFAULT_CONTEXT_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
75pub const DEFAULT_ADDITION_STYLE: Style = AnsiColor::BrightGreen.on_default();
77pub const DEFAULT_REMOVAL_STYLE: Style = AnsiColor::BrightRed.on_default();
79
80#[derive(Clone, Debug)]
106pub struct Renderer {
107 anonymized_line_numbers: bool,
108 term_width: usize,
109 decor_style: DecorStyle,
110 stylesheet: Stylesheet,
111 short_message: bool,
112}
113
114impl Renderer {
115 pub const fn plain() -> Self {
117 Self {
118 anonymized_line_numbers: false,
119 term_width: DEFAULT_TERM_WIDTH,
120 decor_style: DecorStyle::Ascii,
121 stylesheet: Stylesheet::plain(),
122 short_message: false,
123 }
124 }
125
126 pub const fn styled() -> Self {
136 Self {
137 stylesheet: Stylesheet {
138 error: DEFAULT_ERROR_STYLE,
139 warning: DEFAULT_WARNING_STYLE,
140 info: DEFAULT_INFO_STYLE,
141 note: DEFAULT_NOTE_STYLE,
142 help: DEFAULT_HELP_STYLE,
143 line_num: DEFAULT_LINE_NUM_STYLE,
144 emphasis: DEFAULT_EMPHASIS_STYLE,
145 none: DEFAULT_NONE_STYLE,
146 context: DEFAULT_CONTEXT_STYLE,
147 addition: DEFAULT_ADDITION_STYLE,
148 removal: DEFAULT_REMOVAL_STYLE,
149 },
150 ..Self::plain()
151 }
152 }
153
154 pub const fn short_message(mut self, short_message: bool) -> Self {
156 self.short_message = short_message;
157 self
158 }
159
160 pub const fn term_width(mut self, term_width: usize) -> Self {
164 self.term_width = term_width;
165 self
166 }
167
168 pub const fn decor_style(mut self, decor_style: DecorStyle) -> Self {
170 self.decor_style = decor_style;
171 self
172 }
173
174 pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
188 self.anonymized_line_numbers = anonymized_line_numbers;
189 self
190 }
191}
192
193impl Renderer {
194 pub fn render(&self, groups: Report<'_>) -> String {
196 render::render(self, groups)
197 }
198}
199
200impl Renderer {
202 pub const fn error(mut self, style: Style) -> Self {
204 self.stylesheet.error = style;
205 self
206 }
207
208 pub const fn warning(mut self, style: Style) -> Self {
210 self.stylesheet.warning = style;
211 self
212 }
213
214 pub const fn info(mut self, style: Style) -> Self {
216 self.stylesheet.info = style;
217 self
218 }
219
220 pub const fn note(mut self, style: Style) -> Self {
222 self.stylesheet.note = style;
223 self
224 }
225
226 pub const fn help(mut self, style: Style) -> Self {
228 self.stylesheet.help = style;
229 self
230 }
231
232 pub const fn line_num(mut self, style: Style) -> Self {
234 self.stylesheet.line_num = style;
235 self
236 }
237
238 pub const fn emphasis(mut self, style: Style) -> Self {
241 self.stylesheet.emphasis = style;
242 self
243 }
244
245 pub const fn context(mut self, style: Style) -> Self {
247 self.stylesheet.context = style;
248 self
249 }
250
251 pub const fn addition(mut self, style: Style) -> Self {
253 self.stylesheet.addition = style;
254 self
255 }
256
257 pub const fn removal(mut self, style: Style) -> Self {
259 self.stylesheet.removal = style;
260 self
261 }
262
263 pub const fn none(mut self, style: Style) -> Self {
265 self.stylesheet.none = style;
266 self
267 }
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub enum DecorStyle {
273 Ascii,
274 Unicode,
275}
276
277impl DecorStyle {
278 fn col_separator(&self) -> char {
279 match self {
280 DecorStyle::Ascii => '|',
281 DecorStyle::Unicode => '│',
282 }
283 }
284
285 fn note_separator(&self, is_cont: bool) -> &str {
286 match self {
287 DecorStyle::Ascii => "= ",
288 DecorStyle::Unicode if is_cont => "├ ",
289 DecorStyle::Unicode => "╰ ",
290 }
291 }
292
293 fn multi_suggestion_separator(&self) -> &'static str {
294 match self {
295 DecorStyle::Ascii => "|",
296 DecorStyle::Unicode => "├╴",
297 }
298 }
299
300 fn file_start(&self, is_first: bool) -> &'static str {
301 match self {
302 DecorStyle::Ascii => "--> ",
303 DecorStyle::Unicode if is_first => " ╭▸ ",
304 DecorStyle::Unicode => " ├▸ ",
305 }
306 }
307
308 fn secondary_file_start(&self) -> &'static str {
309 match self {
310 DecorStyle::Ascii => "::: ",
311 DecorStyle::Unicode => " ⸬ ",
312 }
313 }
314
315 fn diff(&self) -> char {
316 match self {
317 DecorStyle::Ascii => '~',
318 DecorStyle::Unicode => '±',
319 }
320 }
321
322 fn margin(&self) -> &'static str {
323 match self {
324 DecorStyle::Ascii => "...",
325 DecorStyle::Unicode => "…",
326 }
327 }
328
329 fn underline(&self, is_primary: bool) -> UnderlineParts {
330 match (self, is_primary) {
355 (DecorStyle::Ascii, true) => UnderlineParts {
356 style: ElementStyle::UnderlinePrimary,
357 underline: '^',
358 label_start: '^',
359 vertical_text_line: '|',
360 multiline_vertical: '|',
361 multiline_horizontal: '_',
362 multiline_whole_line: '/',
363 multiline_start_down: '^',
364 bottom_right: '|',
365 top_left: ' ',
366 top_right_flat: '^',
367 bottom_left: '|',
368 multiline_end_up: '^',
369 multiline_end_same_line: '^',
370 multiline_bottom_right_with_text: '|',
371 },
372 (DecorStyle::Ascii, false) => UnderlineParts {
373 style: ElementStyle::UnderlineSecondary,
374 underline: '-',
375 label_start: '-',
376 vertical_text_line: '|',
377 multiline_vertical: '|',
378 multiline_horizontal: '_',
379 multiline_whole_line: '/',
380 multiline_start_down: '-',
381 bottom_right: '|',
382 top_left: ' ',
383 top_right_flat: '-',
384 bottom_left: '|',
385 multiline_end_up: '-',
386 multiline_end_same_line: '-',
387 multiline_bottom_right_with_text: '|',
388 },
389 (DecorStyle::Unicode, true) => UnderlineParts {
390 style: ElementStyle::UnderlinePrimary,
391 underline: '━',
392 label_start: '┯',
393 vertical_text_line: '│',
394 multiline_vertical: '┃',
395 multiline_horizontal: '━',
396 multiline_whole_line: '┏',
397 multiline_start_down: '╿',
398 bottom_right: '┙',
399 top_left: '┏',
400 top_right_flat: '┛',
401 bottom_left: '┗',
402 multiline_end_up: '╿',
403 multiline_end_same_line: '┛',
404 multiline_bottom_right_with_text: '┥',
405 },
406 (DecorStyle::Unicode, false) => UnderlineParts {
407 style: ElementStyle::UnderlineSecondary,
408 underline: '─',
409 label_start: '┬',
410 vertical_text_line: '│',
411 multiline_vertical: '│',
412 multiline_horizontal: '─',
413 multiline_whole_line: '┌',
414 multiline_start_down: '│',
415 bottom_right: '┘',
416 top_left: '┌',
417 top_right_flat: '┘',
418 bottom_left: '└',
419 multiline_end_up: '│',
420 multiline_end_same_line: '┘',
421 multiline_bottom_right_with_text: '┤',
422 },
423 }
424 }
425}