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 if let Some(margin) = self.margin {
202 let line_len = text.chars().count();
203 let mut left = margin.left(line_len);
204 let right = margin.right(line_len);
205
206 if margin.was_cut_left() {
207 // We have stripped some code/whitespace from the beginning, make it clear.
208 "...".fmt(f)?;
209 left += 3;
210 }
211
212 // On long lines, we strip the source line, accounting for unicode.
213 let mut taken = 0;
214 let cut_right = if margin.was_cut_right(line_len) {
215 taken += 3;
216 true
217 } else {
218 false
219 };
220 let range = text
221 .char_indices()
222 .skip(left)
223 .take_while(|(_, ch)| {
224 // Make sure that the trimming on the right will fall within the terminal width.
225 // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is.
226 // For now, just accept that sometimes the code line will be longer than desired.
227 taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
228 if taken > right - left {
229 return false;
230 }
231 true
232 })
233 .fold((None, 0), |acc, (i, _)| {
234 if acc.0.is_some() {
235 (acc.0, i)
236 } else {
237 (Some(i), i)
238 }
239 });
240
241 text[range.0.expect("One character at line")..=range.1].fmt(f)?;
242
243 if cut_right {
244 // We have stripped some code after the right-most span end, make it clear we did so.
245 "...".fmt(f)?;
246 }
247 Ok(())
248 } else {
249 text.fmt(f)
250 }
251 }
252 DisplaySourceLine::Annotation {
253 range,
254 annotation,
255 annotation_type,
256 annotation_part,
257 } => {
258 let indent_char = match annotation_part {
259 DisplayAnnotationPart::Standalone => ' ',
260 DisplayAnnotationPart::LabelContinuation => ' ',
261 DisplayAnnotationPart::Consequitive => ' ',
262 DisplayAnnotationPart::MultilineStart => '_',
263 DisplayAnnotationPart::MultilineEnd => '_',
264 };
265 let mark = match annotation_type {
266 DisplayAnnotationType::Error => '^',
267 DisplayAnnotationType::Warning => '-',
268 DisplayAnnotationType::Info => '-',
269 DisplayAnnotationType::Note => '-',
270 DisplayAnnotationType::Help => '-',
271 DisplayAnnotationType::None => ' ',
272 };
273 let color = self.get_annotation_style(annotation_type);
274 let indent_length = match annotation_part {
275 DisplayAnnotationPart::LabelContinuation => range.1,
276 DisplayAnnotationPart::Consequitive => range.1,
277 _ => range.0,
278 };
279
280 color.paint_fn(
281 Box::new(|f| {
282 format_repeat_char(indent_char, indent_length + 1, f)?;
283 format_repeat_char(mark, range.1 - indent_length, f)
284 }),
285 f,
286 )?;
287
288 if !is_annotation_empty(&annotation) {
289 f.write_char(' ')?;
290 color.paint_fn(
291 Box::new(|f| {
292 self.format_annotation(
293 annotation,
294 annotation_part == &DisplayAnnotationPart::LabelContinuation,
295 true,
296 f,
297 )
298 }),
299 f,
300 )?;
301 }
302
303 Ok(())
304 }
305 }
306 }
307
308 #[inline]
format_raw_line( &self, line: &DisplayRawLine<'_>, lineno_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result309 fn format_raw_line(
310 &self,
311 line: &DisplayRawLine<'_>,
312 lineno_width: usize,
313 f: &mut fmt::Formatter<'_>,
314 ) -> fmt::Result {
315 match line {
316 DisplayRawLine::Origin {
317 path,
318 pos,
319 header_type,
320 } => {
321 let header_sigil = match header_type {
322 DisplayHeaderType::Initial => "-->",
323 DisplayHeaderType::Continuation => ":::",
324 };
325 let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
326
327 if let Some((col, row)) = pos {
328 format_repeat_char(' ', lineno_width, f)?;
329 lineno_color.paint(header_sigil, f)?;
330 f.write_char(' ')?;
331 path.fmt(f)?;
332 f.write_char(':')?;
333 col.fmt(f)?;
334 f.write_char(':')?;
335 row.fmt(f)
336 } else {
337 format_repeat_char(' ', lineno_width, f)?;
338 lineno_color.paint(header_sigil, f)?;
339 f.write_char(' ')?;
340 path.fmt(f)
341 }
342 }
343 DisplayRawLine::Annotation {
344 annotation,
345 source_aligned,
346 continuation,
347 } => {
348 if *source_aligned {
349 if *continuation {
350 format_repeat_char(' ', lineno_width + 3, f)?;
351 self.format_annotation(annotation, *continuation, false, f)
352 } else {
353 let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
354 format_repeat_char(' ', lineno_width, f)?;
355 f.write_char(' ')?;
356 lineno_color.paint("=", f)?;
357 f.write_char(' ')?;
358 self.format_annotation(annotation, *continuation, false, f)
359 }
360 } else {
361 self.format_annotation(annotation, *continuation, false, f)
362 }
363 }
364 }
365 }
366
367 #[inline]
format_line( &self, dl: &DisplayLine<'_>, lineno_width: usize, inline_marks_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result368 fn format_line(
369 &self,
370 dl: &DisplayLine<'_>,
371 lineno_width: usize,
372 inline_marks_width: usize,
373 f: &mut fmt::Formatter<'_>,
374 ) -> fmt::Result {
375 match dl {
376 DisplayLine::Source {
377 lineno,
378 inline_marks,
379 line,
380 } => {
381 let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
382 if self.anonymized_line_numbers && lineno.is_some() {
383 lineno_color.paint_fn(
384 Box::new(|f| {
385 f.write_str(Self::ANONYMIZED_LINE_NUM)?;
386 f.write_str(" |")
387 }),
388 f,
389 )?;
390 } else {
391 lineno_color.paint_fn(
392 Box::new(|f| {
393 match lineno {
394 Some(n) => write!(f, "{:>width$}", n, width = lineno_width),
395 None => format_repeat_char(' ', lineno_width, f),
396 }?;
397 f.write_str(" |")
398 }),
399 f,
400 )?;
401 }
402 if *line != DisplaySourceLine::Empty {
403 if !inline_marks.is_empty() || 0 < inline_marks_width {
404 f.write_char(' ')?;
405 self.format_inline_marks(inline_marks, inline_marks_width, f)?;
406 }
407 self.format_source_line(line, f)?;
408 } else if !inline_marks.is_empty() {
409 f.write_char(' ')?;
410 self.format_inline_marks(inline_marks, inline_marks_width, f)?;
411 }
412 Ok(())
413 }
414 DisplayLine::Fold { inline_marks } => {
415 f.write_str("...")?;
416 if !inline_marks.is_empty() || 0 < inline_marks_width {
417 format_repeat_char(' ', lineno_width, f)?;
418 self.format_inline_marks(inline_marks, inline_marks_width, f)?;
419 }
420 Ok(())
421 }
422 DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f),
423 }
424 }
425
format_inline_marks( &self, inline_marks: &[DisplayMark], inline_marks_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result426 fn format_inline_marks(
427 &self,
428 inline_marks: &[DisplayMark],
429 inline_marks_width: usize,
430 f: &mut fmt::Formatter<'_>,
431 ) -> fmt::Result {
432 format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?;
433 for mark in inline_marks {
434 self.get_annotation_style(&mark.annotation_type).paint_fn(
435 Box::new(|f| {
436 f.write_char(match mark.mark_type {
437 DisplayMarkType::AnnotationThrough => '|',
438 DisplayMarkType::AnnotationStart => '/',
439 })
440 }),
441 f,
442 )?;
443 }
444 Ok(())
445 }
446 }
447