1 use std::{
2     cmp,
3     fmt::{self, Display, Write},
4 };
5 
6 pub mod style;
7 
8 use self::style::{Style, StyleClass, Stylesheet};
9 
10 #[cfg(feature = "color")]
11 use crate::stylesheets::color::AnsiTermStylesheet;
12 use crate::{display_list::*, stylesheets::no_color::NoColorStylesheet};
13 
format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result14 fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15     for _ in 0..n {
16         f.write_char(c)?;
17     }
18     Ok(())
19 }
20 
21 #[inline]
is_annotation_empty(annotation: &Annotation<'_>) -> bool22 fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
23     annotation
24         .label
25         .iter()
26         .all(|fragment| fragment.content.is_empty())
27 }
28 
29 #[cfg(feature = "color")]
30 #[inline]
get_term_style(color: bool) -> Box<dyn Stylesheet>31 pub fn get_term_style(color: bool) -> Box<dyn Stylesheet> {
32     if color {
33         Box::new(AnsiTermStylesheet)
34     } else {
35         Box::new(NoColorStylesheet)
36     }
37 }
38 
39 #[cfg(not(feature = "color"))]
40 #[inline]
get_term_style(_color: bool) -> Box<dyn Stylesheet>41 pub fn get_term_style(_color: bool) -> Box<dyn Stylesheet> {
42     Box::new(NoColorStylesheet)
43 }
44 
45 impl<'a> fmt::Display for DisplayList<'a> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result46     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47         let lineno_width = self.body.iter().fold(0, |max, line| match line {
48             DisplayLine::Source {
49                 lineno: Some(lineno),
50                 ..
51             } => {
52                 // The largest line is the largest width.
53                 cmp::max(*lineno, max)
54             }
55             _ => max,
56         });
57         let lineno_width = if lineno_width == 0 {
58             lineno_width
59         } else if self.anonymized_line_numbers {
60             Self::ANONYMIZED_LINE_NUM.len()
61         } else {
62             ((lineno_width as f64).log10().floor() as usize) + 1
63         };
64         let inline_marks_width = self.body.iter().fold(0, |max, line| match line {
65             DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max),
66             _ => max,
67         });
68 
69         for (i, line) in self.body.iter().enumerate() {
70             self.format_line(line, lineno_width, inline_marks_width, f)?;
71             if i + 1 < self.body.len() {
72                 f.write_char('\n')?;
73             }
74         }
75         Ok(())
76     }
77 }
78 
79 impl<'a> DisplayList<'a> {
80     const ANONYMIZED_LINE_NUM: &'static str = "LL";
81     const ERROR_TXT: &'static str = "error";
82     const HELP_TXT: &'static str = "help";
83     const INFO_TXT: &'static str = "info";
84     const NOTE_TXT: &'static str = "note";
85     const WARNING_TXT: &'static str = "warning";
86 
87     #[inline]
format_annotation_type( annotation_type: &DisplayAnnotationType, f: &mut fmt::Formatter<'_>, ) -> fmt::Result88     fn format_annotation_type(
89         annotation_type: &DisplayAnnotationType,
90         f: &mut fmt::Formatter<'_>,
91     ) -> fmt::Result {
92         match annotation_type {
93             DisplayAnnotationType::Error => f.write_str(Self::ERROR_TXT),
94             DisplayAnnotationType::Help => f.write_str(Self::HELP_TXT),
95             DisplayAnnotationType::Info => f.write_str(Self::INFO_TXT),
96             DisplayAnnotationType::Note => f.write_str(Self::NOTE_TXT),
97             DisplayAnnotationType::Warning => f.write_str(Self::WARNING_TXT),
98             DisplayAnnotationType::None => Ok(()),
99         }
100     }
101 
annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize102     fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize {
103         match annotation_type {
104             DisplayAnnotationType::Error => Self::ERROR_TXT.len(),
105             DisplayAnnotationType::Help => Self::HELP_TXT.len(),
106             DisplayAnnotationType::Info => Self::INFO_TXT.len(),
107             DisplayAnnotationType::Note => Self::NOTE_TXT.len(),
108             DisplayAnnotationType::Warning => Self::WARNING_TXT.len(),
109             DisplayAnnotationType::None => 0,
110         }
111     }
112 
get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style>113     fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> {
114         self.stylesheet.get_style(match annotation_type {
115             DisplayAnnotationType::Error => StyleClass::Error,
116             DisplayAnnotationType::Warning => StyleClass::Warning,
117             DisplayAnnotationType::Info => StyleClass::Info,
118             DisplayAnnotationType::Note => StyleClass::Note,
119             DisplayAnnotationType::Help => StyleClass::Help,
120             DisplayAnnotationType::None => StyleClass::None,
121         })
122     }
123 
format_label( &self, label: &[DisplayTextFragment<'_>], f: &mut fmt::Formatter<'_>, ) -> fmt::Result124     fn format_label(
125         &self,
126         label: &[DisplayTextFragment<'_>],
127         f: &mut fmt::Formatter<'_>,
128     ) -> fmt::Result {
129         let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis);
130 
131         for fragment in label {
132             match fragment.style {
133                 DisplayTextStyle::Regular => fragment.content.fmt(f)?,
134                 DisplayTextStyle::Emphasis => emphasis_style.paint(&fragment.content, f)?,
135             }
136         }
137         Ok(())
138     }
139 
format_annotation( &self, annotation: &Annotation<'_>, continuation: bool, in_source: bool, f: &mut fmt::Formatter<'_>, ) -> fmt::Result140     fn format_annotation(
141         &self,
142         annotation: &Annotation<'_>,
143         continuation: bool,
144         in_source: bool,
145         f: &mut fmt::Formatter<'_>,
146     ) -> fmt::Result {
147         let color = self.get_annotation_style(&annotation.annotation_type);
148         let formatted_len = if let Some(id) = &annotation.id {
149             2 + id.len() + Self::annotation_type_len(&annotation.annotation_type)
150         } else {
151             Self::annotation_type_len(&annotation.annotation_type)
152         };
153 
154         if continuation {
155             format_repeat_char(' ', formatted_len + 2, f)?;
156             return self.format_label(&annotation.label, f);
157         }
158         if formatted_len == 0 {
159             self.format_label(&annotation.label, f)
160         } else {
161             color.paint_fn(
162                 Box::new(|f| {
163                     Self::format_annotation_type(&annotation.annotation_type, f)?;
164                     if let Some(id) = &annotation.id {
165                         f.write_char('[')?;
166                         f.write_str(id)?;
167                         f.write_char(']')?;
168                     }
169                     Ok(())
170                 }),
171                 f,
172             )?;
173             if !is_annotation_empty(annotation) {
174                 if in_source {
175                     color.paint_fn(
176                         Box::new(|f| {
177                             f.write_str(": ")?;
178                             self.format_label(&annotation.label, f)
179                         }),
180                         f,
181                     )?;
182                 } else {
183                     f.write_str(": ")?;
184                     self.format_label(&annotation.label, f)?;
185                 }
186             }
187             Ok(())
188         }
189     }
190 
191     #[inline]
format_source_line( &self, line: &DisplaySourceLine<'_>, f: &mut fmt::Formatter<'_>, ) -> fmt::Result192     fn format_source_line(
193         &self,
194         line: &DisplaySourceLine<'_>,
195         f: &mut fmt::Formatter<'_>,
196     ) -> fmt::Result {
197         match line {
198             DisplaySourceLine::Empty => Ok(()),
199             DisplaySourceLine::Content { text, .. } => {
200                 f.write_char(' ')?;
201                 text.fmt(f)
202             }
203             DisplaySourceLine::Annotation {
204                 range,
205                 annotation,
206                 annotation_type,
207                 annotation_part,
208             } => {
209                 let indent_char = match annotation_part {
210                     DisplayAnnotationPart::Standalone => ' ',
211                     DisplayAnnotationPart::LabelContinuation => ' ',
212                     DisplayAnnotationPart::Consequitive => ' ',
213                     DisplayAnnotationPart::MultilineStart => '_',
214                     DisplayAnnotationPart::MultilineEnd => '_',
215                 };
216                 let mark = match annotation_type {
217                     DisplayAnnotationType::Error => '^',
218                     DisplayAnnotationType::Warning => '-',
219                     DisplayAnnotationType::Info => '-',
220                     DisplayAnnotationType::Note => '-',
221                     DisplayAnnotationType::Help => '-',
222                     DisplayAnnotationType::None => ' ',
223                 };
224                 let color = self.get_annotation_style(annotation_type);
225                 let indent_length = match annotation_part {
226                     DisplayAnnotationPart::LabelContinuation => range.1,
227                     DisplayAnnotationPart::Consequitive => range.1,
228                     _ => range.0,
229                 };
230 
231                 color.paint_fn(
232                     Box::new(|f| {
233                         format_repeat_char(indent_char, indent_length + 1, f)?;
234                         format_repeat_char(mark, range.1 - indent_length, f)
235                     }),
236                     f,
237                 )?;
238 
239                 if !is_annotation_empty(&annotation) {
240                     f.write_char(' ')?;
241                     color.paint_fn(
242                         Box::new(|f| {
243                             self.format_annotation(
244                                 annotation,
245                                 annotation_part == &DisplayAnnotationPart::LabelContinuation,
246                                 true,
247                                 f,
248                             )
249                         }),
250                         f,
251                     )?;
252                 }
253 
254                 Ok(())
255             }
256         }
257     }
258 
259     #[inline]
format_raw_line( &self, line: &DisplayRawLine<'_>, lineno_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result260     fn format_raw_line(
261         &self,
262         line: &DisplayRawLine<'_>,
263         lineno_width: usize,
264         f: &mut fmt::Formatter<'_>,
265     ) -> fmt::Result {
266         match line {
267             DisplayRawLine::Origin {
268                 path,
269                 pos,
270                 header_type,
271             } => {
272                 let header_sigil = match header_type {
273                     DisplayHeaderType::Initial => "-->",
274                     DisplayHeaderType::Continuation => ":::",
275                 };
276                 let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
277 
278                 if let Some((col, row)) = pos {
279                     format_repeat_char(' ', lineno_width, f)?;
280                     lineno_color.paint(header_sigil, f)?;
281                     f.write_char(' ')?;
282                     path.fmt(f)?;
283                     f.write_char(':')?;
284                     col.fmt(f)?;
285                     f.write_char(':')?;
286                     row.fmt(f)
287                 } else {
288                     format_repeat_char(' ', lineno_width, f)?;
289                     lineno_color.paint(header_sigil, f)?;
290                     f.write_char(' ')?;
291                     path.fmt(f)
292                 }
293             }
294             DisplayRawLine::Annotation {
295                 annotation,
296                 source_aligned,
297                 continuation,
298             } => {
299                 if *source_aligned {
300                     if *continuation {
301                         format_repeat_char(' ', lineno_width + 3, f)?;
302                         self.format_annotation(annotation, *continuation, false, f)
303                     } else {
304                         let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
305                         format_repeat_char(' ', lineno_width, f)?;
306                         f.write_char(' ')?;
307                         lineno_color.paint("=", f)?;
308                         f.write_char(' ')?;
309                         self.format_annotation(annotation, *continuation, false, f)
310                     }
311                 } else {
312                     self.format_annotation(annotation, *continuation, false, f)
313                 }
314             }
315         }
316     }
317 
318     #[inline]
format_line( &self, dl: &DisplayLine<'_>, lineno_width: usize, inline_marks_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result319     fn format_line(
320         &self,
321         dl: &DisplayLine<'_>,
322         lineno_width: usize,
323         inline_marks_width: usize,
324         f: &mut fmt::Formatter<'_>,
325     ) -> fmt::Result {
326         match dl {
327             DisplayLine::Source {
328                 lineno,
329                 inline_marks,
330                 line,
331             } => {
332                 let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
333                 if self.anonymized_line_numbers && lineno.is_some() {
334                     lineno_color.paint_fn(
335                         Box::new(|f| {
336                             f.write_str(Self::ANONYMIZED_LINE_NUM)?;
337                             f.write_str(" |")
338                         }),
339                         f,
340                     )?;
341                 } else {
342                     lineno_color.paint_fn(
343                         Box::new(|f| {
344                             match lineno {
345                                 Some(n) => write!(f, "{:>width$}", n, width = lineno_width),
346                                 None => format_repeat_char(' ', lineno_width, f),
347                             }?;
348                             f.write_str(" |")
349                         }),
350                         f,
351                     )?;
352                 }
353                 if *line != DisplaySourceLine::Empty {
354                     if !inline_marks.is_empty() || 0 < inline_marks_width {
355                         f.write_char(' ')?;
356                         self.format_inline_marks(inline_marks, inline_marks_width, f)?;
357                     }
358                     self.format_source_line(line, f)?;
359                 } else if !inline_marks.is_empty() {
360                     f.write_char(' ')?;
361                     self.format_inline_marks(inline_marks, inline_marks_width, f)?;
362                 }
363                 Ok(())
364             }
365             DisplayLine::Fold { inline_marks } => {
366                 f.write_str("...")?;
367                 if !inline_marks.is_empty() || 0 < inline_marks_width {
368                     format_repeat_char(' ', lineno_width, f)?;
369                     self.format_inline_marks(inline_marks, inline_marks_width, f)?;
370                 }
371                 Ok(())
372             }
373             DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f),
374         }
375     }
376 
format_inline_marks( &self, inline_marks: &[DisplayMark], inline_marks_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result377     fn format_inline_marks(
378         &self,
379         inline_marks: &[DisplayMark],
380         inline_marks_width: usize,
381         f: &mut fmt::Formatter<'_>,
382     ) -> fmt::Result {
383         format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?;
384         for mark in inline_marks {
385             self.get_annotation_style(&mark.annotation_type).paint_fn(
386                 Box::new(|f| {
387                     f.write_char(match mark.mark_type {
388                         DisplayMarkType::AnnotationThrough => '|',
389                         DisplayMarkType::AnnotationStart => '/',
390                     })
391                 }),
392                 f,
393             )?;
394         }
395         Ok(())
396     }
397 }
398