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, alone: bool) -> &'static str {
301 match self {
302 DecorStyle::Ascii => "--> ",
303 DecorStyle::Unicode if is_first && alone => " ─▸ ",
304 DecorStyle::Unicode if is_first => " ╭▸ ",
305 DecorStyle::Unicode => " ├▸ ",
306 }
307 }
308
309 fn secondary_file_start(&self) -> &'static str {
310 match self {
311 DecorStyle::Ascii => "::: ",
312 DecorStyle::Unicode => " ⸬ ",
313 }
314 }
315
316 fn diff(&self) -> char {
317 match self {
318 DecorStyle::Ascii => '~',
319 DecorStyle::Unicode => '±',
320 }
321 }
322
323 fn margin(&self) -> &'static str {
324 match self {
325 DecorStyle::Ascii => "...",
326 DecorStyle::Unicode => "…",
327 }
328 }
329
330 fn underline(&self, is_primary: bool) -> UnderlineParts {
331 match (self, is_primary) {
356 (DecorStyle::Ascii, true) => UnderlineParts {
357 style: ElementStyle::UnderlinePrimary,
358 underline: '^',
359 label_start: '^',
360 vertical_text_line: '|',
361 multiline_vertical: '|',
362 multiline_horizontal: '_',
363 multiline_whole_line: '/',
364 multiline_start_down: '^',
365 bottom_right: '|',
366 top_left: ' ',
367 top_right_flat: '^',
368 bottom_left: '|',
369 multiline_end_up: '^',
370 multiline_end_same_line: '^',
371 multiline_bottom_right_with_text: '|',
372 },
373 (DecorStyle::Ascii, false) => UnderlineParts {
374 style: ElementStyle::UnderlineSecondary,
375 underline: '-',
376 label_start: '-',
377 vertical_text_line: '|',
378 multiline_vertical: '|',
379 multiline_horizontal: '_',
380 multiline_whole_line: '/',
381 multiline_start_down: '-',
382 bottom_right: '|',
383 top_left: ' ',
384 top_right_flat: '-',
385 bottom_left: '|',
386 multiline_end_up: '-',
387 multiline_end_same_line: '-',
388 multiline_bottom_right_with_text: '|',
389 },
390 (DecorStyle::Unicode, true) => UnderlineParts {
391 style: ElementStyle::UnderlinePrimary,
392 underline: '━',
393 label_start: '┯',
394 vertical_text_line: '│',
395 multiline_vertical: '┃',
396 multiline_horizontal: '━',
397 multiline_whole_line: '┏',
398 multiline_start_down: '╿',
399 bottom_right: '┙',
400 top_left: '┏',
401 top_right_flat: '┛',
402 bottom_left: '┗',
403 multiline_end_up: '╿',
404 multiline_end_same_line: '┛',
405 multiline_bottom_right_with_text: '┥',
406 },
407 (DecorStyle::Unicode, false) => UnderlineParts {
408 style: ElementStyle::UnderlineSecondary,
409 underline: '─',
410 label_start: '┬',
411 vertical_text_line: '│',
412 multiline_vertical: '│',
413 multiline_horizontal: '─',
414 multiline_whole_line: '┌',
415 multiline_start_down: '│',
416 bottom_right: '┘',
417 top_left: '┌',
418 top_right_flat: '┘',
419 bottom_left: '└',
420 multiline_end_up: '│',
421 multiline_end_same_line: '┘',
422 multiline_bottom_right_with_text: '┤',
423 },
424 }
425 }
426}