1//! display_list module stores the output model for the snippet.
2//!
3//! `DisplayList` is a central structure in the crate, which contains
4//! the structured list of lines to be displayed.
5//!
6//! It is made of two types of lines: `Source` and `Raw`. All `Source` lines
7//! are structured using four columns:
8//!
9//! ```text
10//! /------------ (1) Line number column.
11//! | /--------- (2) Line number column delimiter.
12//! | | /------- (3) Inline marks column.
13//! | | | /--- (4) Content column with the source and annotations for slices.
14//! | | | |
15//! =============================================================================
16//! error[E0308]: mismatched types
17//! --> src/format.rs:51:5
18//! |
19//! 151 | / fn test() -> String {
20//! 152 | | return "test";
21//! 153 | | }
22//! | |___^ error: expected `String`, for `&str`.
23//! |
24//! ```
25//!
26//! The first two lines of the example above are `Raw` lines, while the rest
27//! are `Source` lines.
28//!
29//! `DisplayList` does not store column alignment information, and those are
30//! only calculated by the implementation of `std::fmt::Display` using information such as
31//! styling.
32//!
33//! The above snippet has been built out of the following structure:
34use crate::snippet;
35use std::fmt::{Display, Write};
36use std::{cmp, fmt};
37
38use crate::renderer::{stylesheet::Stylesheet, Margin, Style};
39
40/// List of lines to be displayed.
41pub(crate) struct DisplayList<'a> {
42 pub body: Vec<DisplayLine<'a>>,
43 pub stylesheet: &'a Stylesheet,
44 pub anonymized_line_numbers: bool,
45 pub margin: Option<Margin>,
46}
47
48impl<'a> PartialEq for DisplayList<'a> {
49 fn eq(&self, other: &Self) -> bool {
50 self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
51 }
52}
53
54impl<'a> fmt::Debug for DisplayList<'a> {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 f&mut DebugStruct<'_, '_>.debug_struct("DisplayList")
57 .field("body", &self.body)
58 .field(name:"anonymized_line_numbers", &self.anonymized_line_numbers)
59 .finish()
60 }
61}
62
63impl<'a> Display for DisplayList<'a> {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 let lineno_width = self.body.iter().fold(0, |max, line| match line {
66 DisplayLine::Source {
67 lineno: Some(lineno),
68 ..
69 } => {
70 // The largest line is the largest width.
71 cmp::max(*lineno, max)
72 }
73 _ => max,
74 });
75 let lineno_width = if lineno_width == 0 {
76 lineno_width
77 } else if self.anonymized_line_numbers {
78 Self::ANONYMIZED_LINE_NUM.len()
79 } else {
80 ((lineno_width as f64).log10().floor() as usize) + 1
81 };
82 let inline_marks_width = self.body.iter().fold(0, |max, line| match line {
83 DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max),
84 _ => max,
85 });
86
87 for (i, line) in self.body.iter().enumerate() {
88 self.format_line(line, lineno_width, inline_marks_width, f)?;
89 if i + 1 < self.body.len() {
90 f.write_char('\n')?;
91 }
92 }
93 Ok(())
94 }
95}
96
97impl<'a> DisplayList<'a> {
98 const ANONYMIZED_LINE_NUM: &'static str = "LL";
99 const ERROR_TXT: &'static str = "error";
100 const HELP_TXT: &'static str = "help";
101 const INFO_TXT: &'static str = "info";
102 const NOTE_TXT: &'static str = "note";
103 const WARNING_TXT: &'static str = "warning";
104
105 pub(crate) fn new(
106 snippet::Snippet {
107 title,
108 footer,
109 slices,
110 }: snippet::Snippet<'a>,
111 stylesheet: &'a Stylesheet,
112 anonymized_line_numbers: bool,
113 margin: Option<Margin>,
114 ) -> DisplayList<'a> {
115 let mut body = vec![];
116 if let Some(annotation) = title {
117 body.push(format_title(annotation));
118 }
119
120 for (idx, slice) in slices.into_iter().enumerate() {
121 body.append(&mut format_slice(
122 slice,
123 idx == 0,
124 !footer.is_empty(),
125 margin,
126 ));
127 }
128
129 for annotation in footer {
130 body.append(&mut format_annotation(annotation));
131 }
132
133 Self {
134 body,
135 stylesheet,
136 anonymized_line_numbers,
137 margin,
138 }
139 }
140
141 #[inline]
142 fn format_annotation_type(
143 annotation_type: &DisplayAnnotationType,
144 f: &mut fmt::Formatter<'_>,
145 ) -> fmt::Result {
146 match annotation_type {
147 DisplayAnnotationType::Error => f.write_str(Self::ERROR_TXT),
148 DisplayAnnotationType::Help => f.write_str(Self::HELP_TXT),
149 DisplayAnnotationType::Info => f.write_str(Self::INFO_TXT),
150 DisplayAnnotationType::Note => f.write_str(Self::NOTE_TXT),
151 DisplayAnnotationType::Warning => f.write_str(Self::WARNING_TXT),
152 DisplayAnnotationType::None => Ok(()),
153 }
154 }
155
156 fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize {
157 match annotation_type {
158 DisplayAnnotationType::Error => Self::ERROR_TXT.len(),
159 DisplayAnnotationType::Help => Self::HELP_TXT.len(),
160 DisplayAnnotationType::Info => Self::INFO_TXT.len(),
161 DisplayAnnotationType::Note => Self::NOTE_TXT.len(),
162 DisplayAnnotationType::Warning => Self::WARNING_TXT.len(),
163 DisplayAnnotationType::None => 0,
164 }
165 }
166
167 fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> &Style {
168 match annotation_type {
169 DisplayAnnotationType::Error => self.stylesheet.error(),
170 DisplayAnnotationType::Warning => self.stylesheet.warning(),
171 DisplayAnnotationType::Info => self.stylesheet.info(),
172 DisplayAnnotationType::Note => self.stylesheet.note(),
173 DisplayAnnotationType::Help => self.stylesheet.help(),
174 DisplayAnnotationType::None => self.stylesheet.none(),
175 }
176 }
177
178 fn format_label(
179 &self,
180 label: &[DisplayTextFragment<'_>],
181 f: &mut fmt::Formatter<'_>,
182 ) -> fmt::Result {
183 let emphasis_style = self.stylesheet.emphasis();
184
185 for fragment in label {
186 match fragment.style {
187 DisplayTextStyle::Regular => fragment.content.fmt(f)?,
188 DisplayTextStyle::Emphasis => {
189 write!(
190 f,
191 "{}{}{}",
192 emphasis_style.render(),
193 fragment.content,
194 emphasis_style.render_reset()
195 )?;
196 }
197 }
198 }
199 Ok(())
200 }
201
202 fn format_annotation(
203 &self,
204 annotation: &Annotation<'_>,
205 continuation: bool,
206 in_source: bool,
207 f: &mut fmt::Formatter<'_>,
208 ) -> fmt::Result {
209 let color = self.get_annotation_style(&annotation.annotation_type);
210 let formatted_len = if let Some(id) = &annotation.id {
211 2 + id.len() + Self::annotation_type_len(&annotation.annotation_type)
212 } else {
213 Self::annotation_type_len(&annotation.annotation_type)
214 };
215
216 if continuation {
217 format_repeat_char(' ', formatted_len + 2, f)?;
218 return self.format_label(&annotation.label, f);
219 }
220 if formatted_len == 0 {
221 self.format_label(&annotation.label, f)
222 } else {
223 write!(f, "{}", color.render())?;
224 Self::format_annotation_type(&annotation.annotation_type, f)?;
225 if let Some(id) = &annotation.id {
226 f.write_char('[')?;
227 f.write_str(id)?;
228 f.write_char(']')?;
229 }
230 write!(f, "{}", color.render_reset())?;
231
232 if !is_annotation_empty(annotation) {
233 if in_source {
234 write!(f, "{}", color.render())?;
235 f.write_str(": ")?;
236 self.format_label(&annotation.label, f)?;
237 write!(f, "{}", color.render_reset())?;
238 } else {
239 f.write_str(": ")?;
240 self.format_label(&annotation.label, f)?;
241 }
242 }
243 Ok(())
244 }
245 }
246
247 #[inline]
248 fn format_source_line(
249 &self,
250 line: &DisplaySourceLine<'_>,
251 f: &mut fmt::Formatter<'_>,
252 ) -> fmt::Result {
253 match line {
254 DisplaySourceLine::Empty => Ok(()),
255 DisplaySourceLine::Content { text, .. } => {
256 f.write_char(' ')?;
257 if let Some(margin) = self.margin {
258 let line_len = text.chars().count();
259 let mut left = margin.left(line_len);
260 let right = margin.right(line_len);
261
262 if margin.was_cut_left() {
263 // We have stripped some code/whitespace from the beginning, make it clear.
264 "...".fmt(f)?;
265 left += 3;
266 }
267
268 // On long lines, we strip the source line, accounting for unicode.
269 let mut taken = 0;
270 let cut_right = if margin.was_cut_right(line_len) {
271 taken += 3;
272 true
273 } else {
274 false
275 };
276 // Specifies that it will end on the next character, so it will return
277 // until the next one to the final condition.
278 let mut ended = false;
279 let range = text
280 .char_indices()
281 .skip(left)
282 // Complete char iterator with final character
283 .chain(std::iter::once((text.len(), '\0')))
284 // Take until the next one to the final condition
285 .take_while(|(_, ch)| {
286 // Fast return to iterate over final byte position
287 if ended {
288 return false;
289 }
290 // Make sure that the trimming on the right will fall within the terminal width.
291 // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is.
292 // For now, just accept that sometimes the code line will be longer than desired.
293 taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
294 if taken > right - left {
295 ended = true;
296 }
297 true
298 })
299 // Reduce to start and end byte position
300 .fold((None, 0), |acc, (i, _)| {
301 if acc.0.is_some() {
302 (acc.0, i)
303 } else {
304 (Some(i), i)
305 }
306 });
307
308 // Format text with margins
309 text[range.0.expect("One character at line")..range.1].fmt(f)?;
310
311 if cut_right {
312 // We have stripped some code after the right-most span end, make it clear we did so.
313 "...".fmt(f)?;
314 }
315 Ok(())
316 } else {
317 text.fmt(f)
318 }
319 }
320 DisplaySourceLine::Annotation {
321 range,
322 annotation,
323 annotation_type,
324 annotation_part,
325 } => {
326 let indent_char = match annotation_part {
327 DisplayAnnotationPart::Standalone => ' ',
328 DisplayAnnotationPart::LabelContinuation => ' ',
329 DisplayAnnotationPart::MultilineStart => '_',
330 DisplayAnnotationPart::MultilineEnd => '_',
331 };
332 let mark = match annotation_type {
333 DisplayAnnotationType::Error => '^',
334 DisplayAnnotationType::Warning => '-',
335 DisplayAnnotationType::Info => '-',
336 DisplayAnnotationType::Note => '-',
337 DisplayAnnotationType::Help => '-',
338 DisplayAnnotationType::None => ' ',
339 };
340 let color = self.get_annotation_style(annotation_type);
341 let indent_length = match annotation_part {
342 DisplayAnnotationPart::LabelContinuation => range.1,
343 _ => range.0,
344 };
345
346 write!(f, "{}", color.render())?;
347 format_repeat_char(indent_char, indent_length + 1, f)?;
348 format_repeat_char(mark, range.1 - indent_length, f)?;
349 write!(f, "{}", color.render_reset())?;
350
351 if !is_annotation_empty(annotation) {
352 f.write_char(' ')?;
353 write!(f, "{}", color.render())?;
354 self.format_annotation(
355 annotation,
356 annotation_part == &DisplayAnnotationPart::LabelContinuation,
357 true,
358 f,
359 )?;
360 write!(f, "{}", color.render_reset())?;
361 }
362
363 Ok(())
364 }
365 }
366 }
367
368 #[inline]
369 fn format_raw_line(
370 &self,
371 line: &DisplayRawLine<'_>,
372 lineno_width: usize,
373 f: &mut fmt::Formatter<'_>,
374 ) -> fmt::Result {
375 match line {
376 DisplayRawLine::Origin {
377 path,
378 pos,
379 header_type,
380 } => {
381 let header_sigil = match header_type {
382 DisplayHeaderType::Initial => "-->",
383 DisplayHeaderType::Continuation => ":::",
384 };
385 let lineno_color = self.stylesheet.line_no();
386
387 if let Some((col, row)) = pos {
388 format_repeat_char(' ', lineno_width, f)?;
389 write!(
390 f,
391 "{}{}{}",
392 lineno_color.render(),
393 header_sigil,
394 lineno_color.render_reset()
395 )?;
396 f.write_char(' ')?;
397 path.fmt(f)?;
398 f.write_char(':')?;
399 col.fmt(f)?;
400 f.write_char(':')?;
401 row.fmt(f)
402 } else {
403 format_repeat_char(' ', lineno_width, f)?;
404 write!(
405 f,
406 "{}{}{}",
407 lineno_color.render(),
408 header_sigil,
409 lineno_color.render_reset()
410 )?;
411 f.write_char(' ')?;
412 path.fmt(f)
413 }
414 }
415 DisplayRawLine::Annotation {
416 annotation,
417 source_aligned,
418 continuation,
419 } => {
420 if *source_aligned {
421 if *continuation {
422 format_repeat_char(' ', lineno_width + 3, f)?;
423 } else {
424 let lineno_color = self.stylesheet.line_no();
425 format_repeat_char(' ', lineno_width, f)?;
426 f.write_char(' ')?;
427 write!(
428 f,
429 "{}={}",
430 lineno_color.render(),
431 lineno_color.render_reset()
432 )?;
433 f.write_char(' ')?;
434 }
435 }
436 self.format_annotation(annotation, *continuation, false, f)
437 }
438 }
439 }
440
441 #[inline]
442 fn format_line(
443 &self,
444 dl: &DisplayLine<'_>,
445 lineno_width: usize,
446 inline_marks_width: usize,
447 f: &mut fmt::Formatter<'_>,
448 ) -> fmt::Result {
449 match dl {
450 DisplayLine::Source {
451 lineno,
452 inline_marks,
453 line,
454 } => {
455 let lineno_color = self.stylesheet.line_no();
456 if self.anonymized_line_numbers && lineno.is_some() {
457 write!(f, "{}", lineno_color.render())?;
458 f.write_str(Self::ANONYMIZED_LINE_NUM)?;
459 f.write_str(" |")?;
460 write!(f, "{}", lineno_color.render_reset())?;
461 } else {
462 write!(f, "{}", lineno_color.render())?;
463 match lineno {
464 Some(n) => write!(f, "{:>width$}", n, width = lineno_width),
465 None => format_repeat_char(' ', lineno_width, f),
466 }?;
467 f.write_str(" |")?;
468 write!(f, "{}", lineno_color.render_reset())?;
469 }
470 if *line != DisplaySourceLine::Empty {
471 if !inline_marks.is_empty() || 0 < inline_marks_width {
472 f.write_char(' ')?;
473 self.format_inline_marks(inline_marks, inline_marks_width, f)?;
474 }
475 self.format_source_line(line, f)?;
476 } else if !inline_marks.is_empty() {
477 f.write_char(' ')?;
478 self.format_inline_marks(inline_marks, inline_marks_width, f)?;
479 }
480 Ok(())
481 }
482 DisplayLine::Fold { inline_marks } => {
483 f.write_str("...")?;
484 if !inline_marks.is_empty() || 0 < inline_marks_width {
485 format_repeat_char(' ', lineno_width, f)?;
486 self.format_inline_marks(inline_marks, inline_marks_width, f)?;
487 }
488 Ok(())
489 }
490 DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f),
491 }
492 }
493
494 fn format_inline_marks(
495 &self,
496 inline_marks: &[DisplayMark],
497 inline_marks_width: usize,
498 f: &mut fmt::Formatter<'_>,
499 ) -> fmt::Result {
500 format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?;
501 for mark in inline_marks {
502 let annotation_style = self.get_annotation_style(&mark.annotation_type);
503 write!(f, "{}", annotation_style.render())?;
504 f.write_char(match mark.mark_type {
505 DisplayMarkType::AnnotationThrough => '|',
506 DisplayMarkType::AnnotationStart => '/',
507 })?;
508 write!(f, "{}", annotation_style.render_reset())?;
509 }
510 Ok(())
511 }
512}
513
514/// Inline annotation which can be used in either Raw or Source line.
515#[derive(Debug, PartialEq)]
516pub struct Annotation<'a> {
517 pub annotation_type: DisplayAnnotationType,
518 pub id: Option<&'a str>,
519 pub label: Vec<DisplayTextFragment<'a>>,
520}
521
522/// A single line used in `DisplayList`.
523#[derive(Debug, PartialEq)]
524pub enum DisplayLine<'a> {
525 /// A line with `lineno` portion of the slice.
526 Source {
527 lineno: Option<usize>,
528 inline_marks: Vec<DisplayMark>,
529 line: DisplaySourceLine<'a>,
530 },
531
532 /// A line indicating a folded part of the slice.
533 Fold { inline_marks: Vec<DisplayMark> },
534
535 /// A line which is displayed outside of slices.
536 Raw(DisplayRawLine<'a>),
537}
538
539/// A source line.
540#[derive(Debug, PartialEq)]
541pub enum DisplaySourceLine<'a> {
542 /// A line with the content of the Slice.
543 Content {
544 text: &'a str,
545 range: (usize, usize), // meta information for annotation placement.
546 },
547
548 /// An annotation line which is displayed in context of the slice.
549 Annotation {
550 annotation: Annotation<'a>,
551 range: (usize, usize),
552 annotation_type: DisplayAnnotationType,
553 annotation_part: DisplayAnnotationPart,
554 },
555
556 /// An empty source line.
557 Empty,
558}
559
560/// Raw line - a line which does not have the `lineno` part and is not considered
561/// a part of the snippet.
562#[derive(Debug, PartialEq)]
563pub enum DisplayRawLine<'a> {
564 /// A line which provides information about the location of the given
565 /// slice in the project structure.
566 Origin {
567 path: &'a str,
568 pos: Option<(usize, usize)>,
569 header_type: DisplayHeaderType,
570 },
571
572 /// An annotation line which is not part of any snippet.
573 Annotation {
574 annotation: Annotation<'a>,
575
576 /// If set to `true`, the annotation will be aligned to the
577 /// lineno delimiter of the snippet.
578 source_aligned: bool,
579 /// If set to `true`, only the label of the `Annotation` will be
580 /// displayed. It allows for a multiline annotation to be aligned
581 /// without displaying the meta information (`type` and `id`) to be
582 /// displayed on each line.
583 continuation: bool,
584 },
585}
586
587/// An inline text fragment which any label is composed of.
588#[derive(Debug, PartialEq)]
589pub struct DisplayTextFragment<'a> {
590 pub content: &'a str,
591 pub style: DisplayTextStyle,
592}
593
594/// A style for the `DisplayTextFragment` which can be visually formatted.
595///
596/// This information may be used to emphasis parts of the label.
597#[derive(Debug, Clone, Copy, PartialEq)]
598pub enum DisplayTextStyle {
599 Regular,
600 Emphasis,
601}
602
603/// An indicator of what part of the annotation a given `Annotation` is.
604#[derive(Debug, Clone, PartialEq)]
605pub enum DisplayAnnotationPart {
606 /// A standalone, single-line annotation.
607 Standalone,
608 /// A continuation of a multi-line label of an annotation.
609 LabelContinuation,
610 /// A line starting a multiline annotation.
611 MultilineStart,
612 /// A line ending a multiline annotation.
613 MultilineEnd,
614}
615
616/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
617#[derive(Debug, Clone, PartialEq)]
618pub struct DisplayMark {
619 pub mark_type: DisplayMarkType,
620 pub annotation_type: DisplayAnnotationType,
621}
622
623/// A type of the `DisplayMark`.
624#[derive(Debug, Clone, PartialEq)]
625pub enum DisplayMarkType {
626 /// A mark indicating a multiline annotation going through the current line.
627 AnnotationThrough,
628 /// A mark indicating a multiline annotation starting on the given line.
629 AnnotationStart,
630}
631
632/// A type of the `Annotation` which may impact the sigils, style or text displayed.
633///
634/// There are several ways to uses this information when formatting the `DisplayList`:
635///
636/// * An annotation may display the name of the type like `error` or `info`.
637/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`.
638/// * `ColorStylesheet` may use different colors for different annotations.
639#[derive(Debug, Clone, PartialEq)]
640pub enum DisplayAnnotationType {
641 None,
642 Error,
643 Warning,
644 Info,
645 Note,
646 Help,
647}
648
649impl From<snippet::AnnotationType> for DisplayAnnotationType {
650 fn from(at: snippet::AnnotationType) -> Self {
651 match at {
652 snippet::AnnotationType::Error => DisplayAnnotationType::Error,
653 snippet::AnnotationType::Warning => DisplayAnnotationType::Warning,
654 snippet::AnnotationType::Info => DisplayAnnotationType::Info,
655 snippet::AnnotationType::Note => DisplayAnnotationType::Note,
656 snippet::AnnotationType::Help => DisplayAnnotationType::Help,
657 }
658 }
659}
660
661/// Information whether the header is the initial one or a consequitive one
662/// for multi-slice cases.
663// TODO: private
664#[derive(Debug, Clone, PartialEq)]
665pub enum DisplayHeaderType {
666 /// Initial header is the first header in the snippet.
667 Initial,
668
669 /// Continuation marks all headers of following slices in the snippet.
670 Continuation,
671}
672
673struct CursorLines<'a>(&'a str);
674
675impl<'a> CursorLines<'a> {
676 fn new(src: &str) -> CursorLines<'_> {
677 CursorLines(src)
678 }
679}
680
681enum EndLine {
682 Eof = 0,
683 Crlf = 1,
684 Lf = 2,
685}
686
687impl<'a> Iterator for CursorLines<'a> {
688 type Item = (&'a str, EndLine);
689
690 fn next(&mut self) -> Option<Self::Item> {
691 if self.0.is_empty() {
692 None
693 } else {
694 self.0
695 .find('\n')
696 .map(|x| {
697 let ret = if 0 < x {
698 if self.0.as_bytes()[x - 1] == b'\r' {
699 (&self.0[..x - 1], EndLine::Lf)
700 } else {
701 (&self.0[..x], EndLine::Crlf)
702 }
703 } else {
704 ("", EndLine::Crlf)
705 };
706 self.0 = &self.0[x + 1..];
707 ret
708 })
709 .or_else(|| {
710 let ret = Some((self.0, EndLine::Eof));
711 self.0 = "";
712 ret
713 })
714 }
715 }
716}
717
718fn format_label(
719 label: Option<&str>,
720 style: Option<DisplayTextStyle>,
721) -> Vec<DisplayTextFragment<'_>> {
722 let mut result: Vec> = vec![];
723 if let Some(label: &str) = label {
724 let element_style: DisplayTextStyle = style.unwrap_or(default:DisplayTextStyle::Regular);
725 result.push(DisplayTextFragment {
726 content: label,
727 style: element_style,
728 });
729 }
730 result
731}
732
733fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> {
734 let label: &str = annotation.label.unwrap_or_default();
735 DisplayLine::Raw(DisplayRawLine::Annotation {
736 annotation: Annotation {
737 annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
738 id: annotation.id,
739 label: format_label(label:Some(label), style:Some(DisplayTextStyle::Emphasis)),
740 },
741 source_aligned: false,
742 continuation: false,
743 })
744}
745
746fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> {
747 let mut result: Vec> = vec![];
748 let label: &str = annotation.label.unwrap_or_default();
749 for (i: usize, line: &str) in label.lines().enumerate() {
750 result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
751 annotation: Annotation {
752 annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
753 id: None,
754 label: format_label(label:Some(line), style:None),
755 },
756 source_aligned: true,
757 continuation: i != 0,
758 }));
759 }
760 result
761}
762
763fn format_slice(
764 slice: snippet::Slice<'_>,
765 is_first: bool,
766 has_footer: bool,
767 margin: Option<Margin>,
768) -> Vec<DisplayLine<'_>> {
769 let main_range: Option = slice.annotations.first().map(|x: &SourceAnnotation<'_>| x.range.0);
770 let origin: Option<&str> = slice.origin;
771 let need_empty_header: bool = origin.is_some() || is_first;
772 let mut body: Vec> = format_body(slice, need_empty_header, has_footer, margin);
773 let header: Option> = format_header(origin, main_range, &body, is_first);
774 let mut result: Vec> = vec![];
775
776 if let Some(header: DisplayLine<'_>) = header {
777 result.push(header);
778 }
779 result.append(&mut body);
780 result
781}
782
783#[inline]
784// TODO: option_zip
785fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> {
786 a.and_then(|a: A| b.map(|b: B| (a, b)))
787}
788
789fn format_header<'a>(
790 origin: Option<&'a str>,
791 main_range: Option<usize>,
792 body: &[DisplayLine<'_>],
793 is_first: bool,
794) -> Option<DisplayLine<'a>> {
795 let display_header = if is_first {
796 DisplayHeaderType::Initial
797 } else {
798 DisplayHeaderType::Continuation
799 };
800
801 if let Some((main_range, path)) = zip_opt(main_range, origin) {
802 let mut col = 1;
803 let mut line_offset = 1;
804
805 for item in body {
806 if let DisplayLine::Source {
807 line: DisplaySourceLine::Content { range, .. },
808 lineno,
809 ..
810 } = item
811 {
812 if main_range >= range.0 && main_range <= range.1 {
813 col = main_range - range.0 + 1;
814 line_offset = lineno.unwrap_or(1);
815 break;
816 }
817 }
818 }
819
820 return Some(DisplayLine::Raw(DisplayRawLine::Origin {
821 path,
822 pos: Some((line_offset, col)),
823 header_type: display_header,
824 }));
825 }
826
827 if let Some(path) = origin {
828 return Some(DisplayLine::Raw(DisplayRawLine::Origin {
829 path,
830 pos: None,
831 header_type: display_header,
832 }));
833 }
834
835 None
836}
837
838fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
839 enum Line {
840 Fold(usize),
841 Source(usize),
842 }
843
844 let mut lines = vec![];
845 let mut no_annotation_lines_counter = 0;
846
847 for (idx, line) in body.iter().enumerate() {
848 match line {
849 DisplayLine::Source {
850 line: DisplaySourceLine::Annotation { .. },
851 ..
852 } => {
853 let fold_start = idx - no_annotation_lines_counter;
854 if no_annotation_lines_counter > 2 {
855 let fold_end = idx;
856 let pre_len = if no_annotation_lines_counter > 8 {
857 4
858 } else {
859 0
860 };
861 let post_len = if no_annotation_lines_counter > 8 {
862 2
863 } else {
864 1
865 };
866 for (i, _) in body
867 .iter()
868 .enumerate()
869 .take(fold_start + pre_len)
870 .skip(fold_start)
871 {
872 lines.push(Line::Source(i));
873 }
874 lines.push(Line::Fold(idx));
875 for (i, _) in body
876 .iter()
877 .enumerate()
878 .take(fold_end)
879 .skip(fold_end - post_len)
880 {
881 lines.push(Line::Source(i));
882 }
883 } else {
884 for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) {
885 lines.push(Line::Source(i));
886 }
887 }
888 no_annotation_lines_counter = 0;
889 }
890 DisplayLine::Source { .. } => {
891 no_annotation_lines_counter += 1;
892 continue;
893 }
894 _ => {
895 no_annotation_lines_counter += 1;
896 }
897 }
898 lines.push(Line::Source(idx));
899 }
900
901 let mut new_body = vec![];
902 let mut removed = 0;
903 for line in lines {
904 match line {
905 Line::Source(i) => {
906 new_body.push(body.remove(i - removed));
907 removed += 1;
908 }
909 Line::Fold(i) => {
910 if let DisplayLine::Source {
911 line: DisplaySourceLine::Annotation { .. },
912 ref inline_marks,
913 ..
914 } = body.get(i - removed).unwrap()
915 {
916 new_body.push(DisplayLine::Fold {
917 inline_marks: inline_marks.clone(),
918 })
919 } else {
920 unreachable!()
921 }
922 }
923 }
924 }
925
926 new_body
927}
928
929fn format_body(
930 slice: snippet::Slice<'_>,
931 need_empty_header: bool,
932 has_footer: bool,
933 margin: Option<Margin>,
934) -> Vec<DisplayLine<'_>> {
935 let source_len = slice.source.chars().count();
936 if let Some(bigger) = slice.annotations.iter().find_map(|x| {
937 // Allow highlighting one past the last character in the source.
938 if source_len + 1 < x.range.1 {
939 Some(x.range)
940 } else {
941 None
942 }
943 }) {
944 panic!(
945 "SourceAnnotation range `{:?}` is beyond the end of buffer `{}`",
946 bigger, source_len
947 )
948 }
949
950 let mut body = vec![];
951 let mut current_line = slice.line_start;
952 let mut current_index = 0;
953 let mut line_info = vec![];
954
955 struct LineInfo {
956 line_start_index: usize,
957 line_end_index: usize,
958 // How many spaces each character in the line take up when displayed
959 char_widths: Vec<usize>,
960 }
961
962 for (line, end_line) in CursorLines::new(slice.source) {
963 let line_length = line.chars().count();
964 let line_range = (current_index, current_index + line_length);
965 let char_widths = line
966 .chars()
967 .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
968 .chain(std::iter::once(1)) // treat the end of line as single-width
969 .collect::<Vec<_>>();
970 body.push(DisplayLine::Source {
971 lineno: Some(current_line),
972 inline_marks: vec![],
973 line: DisplaySourceLine::Content {
974 text: line,
975 range: line_range,
976 },
977 });
978 line_info.push(LineInfo {
979 line_start_index: line_range.0,
980 line_end_index: line_range.1,
981 char_widths,
982 });
983 current_line += 1;
984 current_index += line_length + end_line as usize;
985 }
986
987 let mut annotation_line_count = 0;
988 let mut annotations = slice.annotations;
989 for (
990 idx,
991 LineInfo {
992 line_start_index,
993 line_end_index,
994 char_widths,
995 },
996 ) in line_info.into_iter().enumerate()
997 {
998 let margin_left = margin
999 .map(|m| m.left(line_end_index - line_start_index))
1000 .unwrap_or_default();
1001 // It would be nice to use filter_drain here once it's stable.
1002 annotations.retain(|annotation| {
1003 let body_idx = idx + annotation_line_count;
1004 let annotation_type = match annotation.annotation_type {
1005 snippet::AnnotationType::Error => DisplayAnnotationType::None,
1006 snippet::AnnotationType::Warning => DisplayAnnotationType::None,
1007 _ => DisplayAnnotationType::from(annotation.annotation_type),
1008 };
1009 match annotation.range {
1010 (start, _) if start > line_end_index => true,
1011 (start, end)
1012 if start >= line_start_index && end <= line_end_index
1013 || start == line_end_index && end - start <= 1 =>
1014 {
1015 let annotation_start_col = char_widths
1016 .iter()
1017 .take(start - line_start_index)
1018 .sum::<usize>()
1019 - margin_left;
1020 let annotation_end_col = char_widths
1021 .iter()
1022 .take(end - line_start_index)
1023 .sum::<usize>()
1024 - margin_left;
1025 let range = (annotation_start_col, annotation_end_col);
1026 body.insert(
1027 body_idx + 1,
1028 DisplayLine::Source {
1029 lineno: None,
1030 inline_marks: vec![],
1031 line: DisplaySourceLine::Annotation {
1032 annotation: Annotation {
1033 annotation_type,
1034 id: None,
1035 label: format_label(Some(annotation.label), None),
1036 },
1037 range,
1038 annotation_type: DisplayAnnotationType::from(
1039 annotation.annotation_type,
1040 ),
1041 annotation_part: DisplayAnnotationPart::Standalone,
1042 },
1043 },
1044 );
1045 annotation_line_count += 1;
1046 false
1047 }
1048 (start, end)
1049 if start >= line_start_index
1050 && start <= line_end_index
1051 && end > line_end_index =>
1052 {
1053 if start - line_start_index == 0 {
1054 if let DisplayLine::Source {
1055 ref mut inline_marks,
1056 ..
1057 } = body[body_idx]
1058 {
1059 inline_marks.push(DisplayMark {
1060 mark_type: DisplayMarkType::AnnotationStart,
1061 annotation_type: DisplayAnnotationType::from(
1062 annotation.annotation_type,
1063 ),
1064 });
1065 }
1066 } else {
1067 let annotation_start_col = char_widths
1068 .iter()
1069 .take(start - line_start_index)
1070 .sum::<usize>();
1071 let range = (annotation_start_col, annotation_start_col + 1);
1072 body.insert(
1073 body_idx + 1,
1074 DisplayLine::Source {
1075 lineno: None,
1076 inline_marks: vec![],
1077 line: DisplaySourceLine::Annotation {
1078 annotation: Annotation {
1079 annotation_type: DisplayAnnotationType::None,
1080 id: None,
1081 label: vec![],
1082 },
1083 range,
1084 annotation_type: DisplayAnnotationType::from(
1085 annotation.annotation_type,
1086 ),
1087 annotation_part: DisplayAnnotationPart::MultilineStart,
1088 },
1089 },
1090 );
1091 annotation_line_count += 1;
1092 }
1093 true
1094 }
1095 (start, end) if start < line_start_index && end > line_end_index => {
1096 if let DisplayLine::Source {
1097 ref mut inline_marks,
1098 ..
1099 } = body[body_idx]
1100 {
1101 inline_marks.push(DisplayMark {
1102 mark_type: DisplayMarkType::AnnotationThrough,
1103 annotation_type: DisplayAnnotationType::from(
1104 annotation.annotation_type,
1105 ),
1106 });
1107 }
1108 true
1109 }
1110 (start, end)
1111 if start < line_start_index
1112 && end >= line_start_index
1113 && end <= line_end_index =>
1114 {
1115 if let DisplayLine::Source {
1116 ref mut inline_marks,
1117 ..
1118 } = body[body_idx]
1119 {
1120 inline_marks.push(DisplayMark {
1121 mark_type: DisplayMarkType::AnnotationThrough,
1122 annotation_type: DisplayAnnotationType::from(
1123 annotation.annotation_type,
1124 ),
1125 });
1126 }
1127
1128 let end_mark = char_widths
1129 .iter()
1130 .take(end - line_start_index)
1131 .sum::<usize>()
1132 .saturating_sub(1);
1133 let range = (end_mark - margin_left, (end_mark + 1) - margin_left);
1134 body.insert(
1135 body_idx + 1,
1136 DisplayLine::Source {
1137 lineno: None,
1138 inline_marks: vec![DisplayMark {
1139 mark_type: DisplayMarkType::AnnotationThrough,
1140 annotation_type: DisplayAnnotationType::from(
1141 annotation.annotation_type,
1142 ),
1143 }],
1144 line: DisplaySourceLine::Annotation {
1145 annotation: Annotation {
1146 annotation_type,
1147 id: None,
1148 label: format_label(Some(annotation.label), None),
1149 },
1150 range,
1151 annotation_type: DisplayAnnotationType::from(
1152 annotation.annotation_type,
1153 ),
1154 annotation_part: DisplayAnnotationPart::MultilineEnd,
1155 },
1156 },
1157 );
1158 annotation_line_count += 1;
1159 false
1160 }
1161 _ => true,
1162 }
1163 });
1164 }
1165
1166 if slice.fold {
1167 body = fold_body(body);
1168 }
1169
1170 if need_empty_header {
1171 body.insert(
1172 0,
1173 DisplayLine::Source {
1174 lineno: None,
1175 inline_marks: vec![],
1176 line: DisplaySourceLine::Empty,
1177 },
1178 );
1179 }
1180
1181 if has_footer {
1182 body.push(DisplayLine::Source {
1183 lineno: None,
1184 inline_marks: vec![],
1185 line: DisplaySourceLine::Empty,
1186 });
1187 } else if let Some(DisplayLine::Source { .. }) = body.last() {
1188 body.push(DisplayLine::Source {
1189 lineno: None,
1190 inline_marks: vec![],
1191 line: DisplaySourceLine::Empty,
1192 });
1193 }
1194 body
1195}
1196
1197fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1198 for _ in 0..n {
1199 f.write_char(c)?;
1200 }
1201 Ok(())
1202}
1203
1204#[inline]
1205fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
1206 annotationIter<'_, DisplayTextFragment<'_>>
1207 .label
1208 .iter()
1209 .all(|fragment: &DisplayTextFragment<'_>| fragment.content.is_empty())
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214 use super::*;
1215
1216 const STYLESHEET: Stylesheet = Stylesheet::plain();
1217
1218 fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> DisplayList<'_> {
1219 DisplayList {
1220 body: lines,
1221 stylesheet: &STYLESHEET,
1222 anonymized_line_numbers: false,
1223 margin: None,
1224 }
1225 }
1226
1227 #[test]
1228 fn test_format_title() {
1229 let input = snippet::Snippet {
1230 title: Some(snippet::Annotation {
1231 id: Some("E0001"),
1232 label: Some("This is a title"),
1233 annotation_type: snippet::AnnotationType::Error,
1234 }),
1235 footer: vec![],
1236 slices: vec![],
1237 };
1238 let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
1239 annotation: Annotation {
1240 annotation_type: DisplayAnnotationType::Error,
1241 id: Some("E0001"),
1242 label: vec![DisplayTextFragment {
1243 content: "This is a title",
1244 style: DisplayTextStyle::Emphasis,
1245 }],
1246 },
1247 source_aligned: false,
1248 continuation: false,
1249 })]);
1250 assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
1251 }
1252
1253 #[test]
1254 fn test_format_slice() {
1255 let line_1 = "This is line 1";
1256 let line_2 = "This is line 2";
1257 let source = [line_1, line_2].join("\n");
1258 let input = snippet::Snippet {
1259 title: None,
1260 footer: vec![],
1261 slices: vec![snippet::Slice {
1262 source: &source,
1263 line_start: 5402,
1264 origin: None,
1265 annotations: vec![],
1266 fold: false,
1267 }],
1268 };
1269 let output = from_display_lines(vec![
1270 DisplayLine::Source {
1271 lineno: None,
1272 inline_marks: vec![],
1273 line: DisplaySourceLine::Empty,
1274 },
1275 DisplayLine::Source {
1276 lineno: Some(5402),
1277 inline_marks: vec![],
1278 line: DisplaySourceLine::Content {
1279 text: line_1,
1280 range: (0, line_1.len()),
1281 },
1282 },
1283 DisplayLine::Source {
1284 lineno: Some(5403),
1285 inline_marks: vec![],
1286 line: DisplaySourceLine::Content {
1287 range: (line_1.len() + 1, source.len()),
1288 text: line_2,
1289 },
1290 },
1291 DisplayLine::Source {
1292 lineno: None,
1293 inline_marks: vec![],
1294 line: DisplaySourceLine::Empty,
1295 },
1296 ]);
1297 assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
1298 }
1299
1300 #[test]
1301 fn test_format_slices_continuation() {
1302 let src_0 = "This is slice 1";
1303 let src_0_len = src_0.len();
1304 let src_1 = "This is slice 2";
1305 let src_1_len = src_1.len();
1306 let input = snippet::Snippet {
1307 title: None,
1308 footer: vec![],
1309 slices: vec![
1310 snippet::Slice {
1311 source: src_0,
1312 line_start: 5402,
1313 origin: Some("file1.rs"),
1314 annotations: vec![],
1315 fold: false,
1316 },
1317 snippet::Slice {
1318 source: src_1,
1319 line_start: 2,
1320 origin: Some("file2.rs"),
1321 annotations: vec![],
1322 fold: false,
1323 },
1324 ],
1325 };
1326 let output = from_display_lines(vec![
1327 DisplayLine::Raw(DisplayRawLine::Origin {
1328 path: "file1.rs",
1329 pos: None,
1330 header_type: DisplayHeaderType::Initial,
1331 }),
1332 DisplayLine::Source {
1333 lineno: None,
1334 inline_marks: vec![],
1335 line: DisplaySourceLine::Empty,
1336 },
1337 DisplayLine::Source {
1338 lineno: Some(5402),
1339 inline_marks: vec![],
1340 line: DisplaySourceLine::Content {
1341 text: src_0,
1342 range: (0, src_0_len),
1343 },
1344 },
1345 DisplayLine::Source {
1346 lineno: None,
1347 inline_marks: vec![],
1348 line: DisplaySourceLine::Empty,
1349 },
1350 DisplayLine::Raw(DisplayRawLine::Origin {
1351 path: "file2.rs",
1352 pos: None,
1353 header_type: DisplayHeaderType::Continuation,
1354 }),
1355 DisplayLine::Source {
1356 lineno: None,
1357 inline_marks: vec![],
1358 line: DisplaySourceLine::Empty,
1359 },
1360 DisplayLine::Source {
1361 lineno: Some(2),
1362 inline_marks: vec![],
1363 line: DisplaySourceLine::Content {
1364 text: src_1,
1365 range: (0, src_1_len),
1366 },
1367 },
1368 DisplayLine::Source {
1369 lineno: None,
1370 inline_marks: vec![],
1371 line: DisplaySourceLine::Empty,
1372 },
1373 ]);
1374 assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
1375 }
1376
1377 #[test]
1378 fn test_format_slice_annotation_standalone() {
1379 let line_1 = "This is line 1";
1380 let line_2 = "This is line 2";
1381 let source = [line_1, line_2].join("\n");
1382 // In line 2
1383 let range = (22, 24);
1384 let input = snippet::Snippet {
1385 title: None,
1386 footer: vec![],
1387 slices: vec![snippet::Slice {
1388 source: &source,
1389 line_start: 5402,
1390 origin: None,
1391 annotations: vec![snippet::SourceAnnotation {
1392 range,
1393 label: "Test annotation",
1394 annotation_type: snippet::AnnotationType::Info,
1395 }],
1396 fold: false,
1397 }],
1398 };
1399 let output = from_display_lines(vec![
1400 DisplayLine::Source {
1401 lineno: None,
1402 inline_marks: vec![],
1403 line: DisplaySourceLine::Empty,
1404 },
1405 DisplayLine::Source {
1406 lineno: Some(5402),
1407 inline_marks: vec![],
1408 line: DisplaySourceLine::Content {
1409 range: (0, line_1.len()),
1410 text: line_1,
1411 },
1412 },
1413 DisplayLine::Source {
1414 lineno: Some(5403),
1415 inline_marks: vec![],
1416 line: DisplaySourceLine::Content {
1417 range: (line_1.len() + 1, source.len()),
1418 text: line_2,
1419 },
1420 },
1421 DisplayLine::Source {
1422 lineno: None,
1423 inline_marks: vec![],
1424 line: DisplaySourceLine::Annotation {
1425 annotation: Annotation {
1426 annotation_type: DisplayAnnotationType::Info,
1427 id: None,
1428 label: vec![DisplayTextFragment {
1429 content: "Test annotation",
1430 style: DisplayTextStyle::Regular,
1431 }],
1432 },
1433 range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)),
1434 annotation_type: DisplayAnnotationType::Info,
1435 annotation_part: DisplayAnnotationPart::Standalone,
1436 },
1437 },
1438 DisplayLine::Source {
1439 lineno: None,
1440 inline_marks: vec![],
1441 line: DisplaySourceLine::Empty,
1442 },
1443 ]);
1444 assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
1445 }
1446
1447 #[test]
1448 fn test_format_label() {
1449 let input = snippet::Snippet {
1450 title: None,
1451 footer: vec![snippet::Annotation {
1452 id: None,
1453 label: Some("This __is__ a title"),
1454 annotation_type: snippet::AnnotationType::Error,
1455 }],
1456 slices: vec![],
1457 };
1458 let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
1459 annotation: Annotation {
1460 annotation_type: DisplayAnnotationType::Error,
1461 id: None,
1462 label: vec![DisplayTextFragment {
1463 content: "This __is__ a title",
1464 style: DisplayTextStyle::Regular,
1465 }],
1466 },
1467 source_aligned: true,
1468 continuation: false,
1469 })]);
1470 assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
1471 }
1472
1473 #[test]
1474 #[should_panic]
1475 fn test_i26() {
1476 let source = "short";
1477 let label = "label";
1478 let input = snippet::Snippet {
1479 title: None,
1480 footer: vec![],
1481 slices: vec![snippet::Slice {
1482 annotations: vec![snippet::SourceAnnotation {
1483 range: (0, source.len() + 2),
1484 label,
1485 annotation_type: snippet::AnnotationType::Error,
1486 }],
1487 source,
1488 line_start: 0,
1489 origin: None,
1490 fold: false,
1491 }],
1492 };
1493 let _ = DisplayList::new(input, &STYLESHEET, false, None);
1494 }
1495
1496 #[test]
1497 fn test_i_29() {
1498 let snippets = snippet::Snippet {
1499 title: Some(snippet::Annotation {
1500 id: None,
1501 label: Some("oops"),
1502 annotation_type: snippet::AnnotationType::Error,
1503 }),
1504 footer: vec![],
1505 slices: vec![snippet::Slice {
1506 source: "First line\r\nSecond oops line",
1507 line_start: 1,
1508 origin: Some("<current file>"),
1509 annotations: vec![snippet::SourceAnnotation {
1510 range: (19, 23),
1511 label: "oops",
1512 annotation_type: snippet::AnnotationType::Error,
1513 }],
1514 fold: true,
1515 }],
1516 };
1517
1518 let expected = from_display_lines(vec![
1519 DisplayLine::Raw(DisplayRawLine::Annotation {
1520 annotation: Annotation {
1521 annotation_type: DisplayAnnotationType::Error,
1522 id: None,
1523 label: vec![DisplayTextFragment {
1524 content: "oops",
1525 style: DisplayTextStyle::Emphasis,
1526 }],
1527 },
1528 source_aligned: false,
1529 continuation: false,
1530 }),
1531 DisplayLine::Raw(DisplayRawLine::Origin {
1532 path: "<current file>",
1533 pos: Some((2, 8)),
1534 header_type: DisplayHeaderType::Initial,
1535 }),
1536 DisplayLine::Source {
1537 lineno: None,
1538 inline_marks: vec![],
1539 line: DisplaySourceLine::Empty,
1540 },
1541 DisplayLine::Source {
1542 lineno: Some(1),
1543 inline_marks: vec![],
1544 line: DisplaySourceLine::Content {
1545 text: "First line",
1546 range: (0, 10),
1547 },
1548 },
1549 DisplayLine::Source {
1550 lineno: Some(2),
1551 inline_marks: vec![],
1552 line: DisplaySourceLine::Content {
1553 text: "Second oops line",
1554 range: (12, 28),
1555 },
1556 },
1557 DisplayLine::Source {
1558 lineno: None,
1559 inline_marks: vec![],
1560 line: DisplaySourceLine::Annotation {
1561 annotation: Annotation {
1562 annotation_type: DisplayAnnotationType::None,
1563 id: None,
1564 label: vec![DisplayTextFragment {
1565 content: "oops",
1566 style: DisplayTextStyle::Regular,
1567 }],
1568 },
1569 range: (7, 11),
1570 annotation_type: DisplayAnnotationType::Error,
1571 annotation_part: DisplayAnnotationPart::Standalone,
1572 },
1573 },
1574 DisplayLine::Source {
1575 lineno: None,
1576 inline_marks: vec![],
1577 line: DisplaySourceLine::Empty,
1578 },
1579 ]);
1580 assert_eq!(
1581 DisplayList::new(snippets, &STYLESHEET, false, None),
1582 expected
1583 );
1584 }
1585
1586 #[test]
1587 fn test_source_empty() {
1588 let dl = from_display_lines(vec![DisplayLine::Source {
1589 lineno: None,
1590 inline_marks: vec![],
1591 line: DisplaySourceLine::Empty,
1592 }]);
1593
1594 assert_eq!(dl.to_string(), " |");
1595 }
1596
1597 #[test]
1598 fn test_source_content() {
1599 let dl = from_display_lines(vec![
1600 DisplayLine::Source {
1601 lineno: Some(56),
1602 inline_marks: vec![],
1603 line: DisplaySourceLine::Content {
1604 text: "This is an example",
1605 range: (0, 19),
1606 },
1607 },
1608 DisplayLine::Source {
1609 lineno: Some(57),
1610 inline_marks: vec![],
1611 line: DisplaySourceLine::Content {
1612 text: "of content lines",
1613 range: (0, 19),
1614 },
1615 },
1616 ]);
1617
1618 assert_eq!(
1619 dl.to_string(),
1620 "56 | This is an example\n57 | of content lines"
1621 );
1622 }
1623
1624 #[test]
1625 fn test_source_annotation_standalone_singleline() {
1626 let dl = from_display_lines(vec![DisplayLine::Source {
1627 lineno: None,
1628 inline_marks: vec![],
1629 line: DisplaySourceLine::Annotation {
1630 range: (0, 5),
1631 annotation: Annotation {
1632 annotation_type: DisplayAnnotationType::None,
1633 id: None,
1634 label: vec![DisplayTextFragment {
1635 content: "Example string",
1636 style: DisplayTextStyle::Regular,
1637 }],
1638 },
1639 annotation_type: DisplayAnnotationType::Error,
1640 annotation_part: DisplayAnnotationPart::Standalone,
1641 },
1642 }]);
1643
1644 assert_eq!(dl.to_string(), " | ^^^^^ Example string");
1645 }
1646
1647 #[test]
1648 fn test_source_annotation_standalone_multiline() {
1649 let dl = from_display_lines(vec![
1650 DisplayLine::Source {
1651 lineno: None,
1652 inline_marks: vec![],
1653 line: DisplaySourceLine::Annotation {
1654 range: (0, 5),
1655 annotation: Annotation {
1656 annotation_type: DisplayAnnotationType::Help,
1657 id: None,
1658 label: vec![DisplayTextFragment {
1659 content: "Example string",
1660 style: DisplayTextStyle::Regular,
1661 }],
1662 },
1663 annotation_type: DisplayAnnotationType::Warning,
1664 annotation_part: DisplayAnnotationPart::Standalone,
1665 },
1666 },
1667 DisplayLine::Source {
1668 lineno: None,
1669 inline_marks: vec![],
1670 line: DisplaySourceLine::Annotation {
1671 range: (0, 5),
1672 annotation: Annotation {
1673 annotation_type: DisplayAnnotationType::Help,
1674 id: None,
1675 label: vec![DisplayTextFragment {
1676 content: "Second line",
1677 style: DisplayTextStyle::Regular,
1678 }],
1679 },
1680 annotation_type: DisplayAnnotationType::Warning,
1681 annotation_part: DisplayAnnotationPart::LabelContinuation,
1682 },
1683 },
1684 ]);
1685
1686 assert_eq!(
1687 dl.to_string(),
1688 " | ----- help: Example string\n | Second line"
1689 );
1690 }
1691
1692 #[test]
1693 fn test_source_annotation_standalone_multi_annotation() {
1694 let dl = from_display_lines(vec![
1695 DisplayLine::Source {
1696 lineno: None,
1697 inline_marks: vec![],
1698 line: DisplaySourceLine::Annotation {
1699 range: (0, 5),
1700 annotation: Annotation {
1701 annotation_type: DisplayAnnotationType::Info,
1702 id: None,
1703 label: vec![DisplayTextFragment {
1704 content: "Example string",
1705 style: DisplayTextStyle::Regular,
1706 }],
1707 },
1708 annotation_type: DisplayAnnotationType::Note,
1709 annotation_part: DisplayAnnotationPart::Standalone,
1710 },
1711 },
1712 DisplayLine::Source {
1713 lineno: None,
1714 inline_marks: vec![],
1715 line: DisplaySourceLine::Annotation {
1716 range: (0, 5),
1717 annotation: Annotation {
1718 annotation_type: DisplayAnnotationType::Info,
1719 id: None,
1720 label: vec![DisplayTextFragment {
1721 content: "Second line",
1722 style: DisplayTextStyle::Regular,
1723 }],
1724 },
1725 annotation_type: DisplayAnnotationType::Note,
1726 annotation_part: DisplayAnnotationPart::LabelContinuation,
1727 },
1728 },
1729 DisplayLine::Source {
1730 lineno: None,
1731 inline_marks: vec![],
1732 line: DisplaySourceLine::Annotation {
1733 range: (0, 5),
1734 annotation: Annotation {
1735 annotation_type: DisplayAnnotationType::Warning,
1736 id: None,
1737 label: vec![DisplayTextFragment {
1738 content: "Second line of the warning",
1739 style: DisplayTextStyle::Regular,
1740 }],
1741 },
1742 annotation_type: DisplayAnnotationType::Note,
1743 annotation_part: DisplayAnnotationPart::LabelContinuation,
1744 },
1745 },
1746 DisplayLine::Source {
1747 lineno: None,
1748 inline_marks: vec![],
1749 line: DisplaySourceLine::Annotation {
1750 range: (0, 5),
1751 annotation: Annotation {
1752 annotation_type: DisplayAnnotationType::Info,
1753 id: None,
1754 label: vec![DisplayTextFragment {
1755 content: "This is an info",
1756 style: DisplayTextStyle::Regular,
1757 }],
1758 },
1759 annotation_type: DisplayAnnotationType::Info,
1760 annotation_part: DisplayAnnotationPart::Standalone,
1761 },
1762 },
1763 DisplayLine::Source {
1764 lineno: None,
1765 inline_marks: vec![],
1766 line: DisplaySourceLine::Annotation {
1767 range: (0, 5),
1768 annotation: Annotation {
1769 annotation_type: DisplayAnnotationType::Help,
1770 id: None,
1771 label: vec![DisplayTextFragment {
1772 content: "This is help",
1773 style: DisplayTextStyle::Regular,
1774 }],
1775 },
1776 annotation_type: DisplayAnnotationType::Help,
1777 annotation_part: DisplayAnnotationPart::Standalone,
1778 },
1779 },
1780 DisplayLine::Source {
1781 lineno: None,
1782 inline_marks: vec![],
1783 line: DisplaySourceLine::Annotation {
1784 range: (0, 0),
1785 annotation: Annotation {
1786 annotation_type: DisplayAnnotationType::None,
1787 id: None,
1788 label: vec![DisplayTextFragment {
1789 content: "This is an annotation of type none",
1790 style: DisplayTextStyle::Regular,
1791 }],
1792 },
1793 annotation_type: DisplayAnnotationType::None,
1794 annotation_part: DisplayAnnotationPart::Standalone,
1795 },
1796 },
1797 ]);
1798
1799 assert_eq!(dl.to_string(), " | ----- info: Example string\n | Second line\n | Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n | This is an annotation of type none");
1800 }
1801
1802 #[test]
1803 fn test_fold_line() {
1804 let dl = from_display_lines(vec![
1805 DisplayLine::Source {
1806 lineno: Some(5),
1807 inline_marks: vec![],
1808 line: DisplaySourceLine::Content {
1809 text: "This is line 5",
1810 range: (0, 19),
1811 },
1812 },
1813 DisplayLine::Fold {
1814 inline_marks: vec![],
1815 },
1816 DisplayLine::Source {
1817 lineno: Some(10021),
1818 inline_marks: vec![],
1819 line: DisplaySourceLine::Content {
1820 text: "... and now we're at line 10021",
1821 range: (0, 19),
1822 },
1823 },
1824 ]);
1825
1826 assert_eq!(
1827 dl.to_string(),
1828 " 5 | This is line 5\n...\n10021 | ... and now we're at line 10021"
1829 );
1830 }
1831
1832 #[test]
1833 fn test_raw_origin_initial_nopos() {
1834 let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
1835 path: "src/test.rs",
1836 pos: None,
1837 header_type: DisplayHeaderType::Initial,
1838 })]);
1839
1840 assert_eq!(dl.to_string(), "--> src/test.rs");
1841 }
1842
1843 #[test]
1844 fn test_raw_origin_initial_pos() {
1845 let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
1846 path: "src/test.rs",
1847 pos: Some((23, 15)),
1848 header_type: DisplayHeaderType::Initial,
1849 })]);
1850
1851 assert_eq!(dl.to_string(), "--> src/test.rs:23:15");
1852 }
1853
1854 #[test]
1855 fn test_raw_origin_continuation() {
1856 let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
1857 path: "src/test.rs",
1858 pos: Some((23, 15)),
1859 header_type: DisplayHeaderType::Continuation,
1860 })]);
1861
1862 assert_eq!(dl.to_string(), "::: src/test.rs:23:15");
1863 }
1864
1865 #[test]
1866 fn test_raw_annotation_unaligned() {
1867 let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
1868 annotation: Annotation {
1869 annotation_type: DisplayAnnotationType::Error,
1870 id: Some("E0001"),
1871 label: vec![DisplayTextFragment {
1872 content: "This is an error",
1873 style: DisplayTextStyle::Regular,
1874 }],
1875 },
1876 source_aligned: false,
1877 continuation: false,
1878 })]);
1879
1880 assert_eq!(dl.to_string(), "error[E0001]: This is an error");
1881 }
1882
1883 #[test]
1884 fn test_raw_annotation_unaligned_multiline() {
1885 let dl = from_display_lines(vec![
1886 DisplayLine::Raw(DisplayRawLine::Annotation {
1887 annotation: Annotation {
1888 annotation_type: DisplayAnnotationType::Warning,
1889 id: Some("E0001"),
1890 label: vec![DisplayTextFragment {
1891 content: "This is an error",
1892 style: DisplayTextStyle::Regular,
1893 }],
1894 },
1895 source_aligned: false,
1896 continuation: false,
1897 }),
1898 DisplayLine::Raw(DisplayRawLine::Annotation {
1899 annotation: Annotation {
1900 annotation_type: DisplayAnnotationType::Warning,
1901 id: Some("E0001"),
1902 label: vec![DisplayTextFragment {
1903 content: "Second line of the error",
1904 style: DisplayTextStyle::Regular,
1905 }],
1906 },
1907 source_aligned: false,
1908 continuation: true,
1909 }),
1910 ]);
1911
1912 assert_eq!(
1913 dl.to_string(),
1914 "warning[E0001]: This is an error\n Second line of the error"
1915 );
1916 }
1917
1918 #[test]
1919 fn test_raw_annotation_aligned() {
1920 let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
1921 annotation: Annotation {
1922 annotation_type: DisplayAnnotationType::Error,
1923 id: Some("E0001"),
1924 label: vec![DisplayTextFragment {
1925 content: "This is an error",
1926 style: DisplayTextStyle::Regular,
1927 }],
1928 },
1929 source_aligned: true,
1930 continuation: false,
1931 })]);
1932
1933 assert_eq!(dl.to_string(), " = error[E0001]: This is an error");
1934 }
1935
1936 #[test]
1937 fn test_raw_annotation_aligned_multiline() {
1938 let dl = from_display_lines(vec![
1939 DisplayLine::Raw(DisplayRawLine::Annotation {
1940 annotation: Annotation {
1941 annotation_type: DisplayAnnotationType::Warning,
1942 id: Some("E0001"),
1943 label: vec![DisplayTextFragment {
1944 content: "This is an error",
1945 style: DisplayTextStyle::Regular,
1946 }],
1947 },
1948 source_aligned: true,
1949 continuation: false,
1950 }),
1951 DisplayLine::Raw(DisplayRawLine::Annotation {
1952 annotation: Annotation {
1953 annotation_type: DisplayAnnotationType::Warning,
1954 id: Some("E0001"),
1955 label: vec![DisplayTextFragment {
1956 content: "Second line of the error",
1957 style: DisplayTextStyle::Regular,
1958 }],
1959 },
1960 source_aligned: true,
1961 continuation: true,
1962 }),
1963 ]);
1964
1965 assert_eq!(
1966 dl.to_string(),
1967 " = warning[E0001]: This is an error\n Second line of the error"
1968 );
1969 }
1970
1971 #[test]
1972 fn test_different_annotation_types() {
1973 let dl = from_display_lines(vec![
1974 DisplayLine::Raw(DisplayRawLine::Annotation {
1975 annotation: Annotation {
1976 annotation_type: DisplayAnnotationType::Note,
1977 id: None,
1978 label: vec![DisplayTextFragment {
1979 content: "This is a note",
1980 style: DisplayTextStyle::Regular,
1981 }],
1982 },
1983 source_aligned: false,
1984 continuation: false,
1985 }),
1986 DisplayLine::Raw(DisplayRawLine::Annotation {
1987 annotation: Annotation {
1988 annotation_type: DisplayAnnotationType::None,
1989 id: None,
1990 label: vec![DisplayTextFragment {
1991 content: "This is just a string",
1992 style: DisplayTextStyle::Regular,
1993 }],
1994 },
1995 source_aligned: false,
1996 continuation: false,
1997 }),
1998 DisplayLine::Raw(DisplayRawLine::Annotation {
1999 annotation: Annotation {
2000 annotation_type: DisplayAnnotationType::None,
2001 id: None,
2002 label: vec![DisplayTextFragment {
2003 content: "Second line of none type annotation",
2004 style: DisplayTextStyle::Regular,
2005 }],
2006 },
2007 source_aligned: false,
2008 continuation: true,
2009 }),
2010 ]);
2011
2012 assert_eq!(
2013 dl.to_string(),
2014 "note: This is a note\nThis is just a string\n Second line of none type annotation",
2015 );
2016 }
2017
2018 #[test]
2019 fn test_inline_marks_empty_line() {
2020 let dl = from_display_lines(vec![DisplayLine::Source {
2021 lineno: None,
2022 inline_marks: vec![DisplayMark {
2023 mark_type: DisplayMarkType::AnnotationThrough,
2024 annotation_type: DisplayAnnotationType::Error,
2025 }],
2026 line: DisplaySourceLine::Empty,
2027 }]);
2028
2029 assert_eq!(dl.to_string(), " | |",);
2030 }
2031
2032 #[test]
2033 fn test_anon_lines() {
2034 let mut dl = from_display_lines(vec![
2035 DisplayLine::Source {
2036 lineno: Some(56),
2037 inline_marks: vec![],
2038 line: DisplaySourceLine::Content {
2039 text: "This is an example",
2040 range: (0, 19),
2041 },
2042 },
2043 DisplayLine::Source {
2044 lineno: Some(57),
2045 inline_marks: vec![],
2046 line: DisplaySourceLine::Content {
2047 text: "of content lines",
2048 range: (0, 19),
2049 },
2050 },
2051 DisplayLine::Source {
2052 lineno: None,
2053 inline_marks: vec![],
2054 line: DisplaySourceLine::Empty,
2055 },
2056 DisplayLine::Source {
2057 lineno: None,
2058 inline_marks: vec![],
2059 line: DisplaySourceLine::Content {
2060 text: "abc",
2061 range: (0, 19),
2062 },
2063 },
2064 ]);
2065
2066 dl.anonymized_line_numbers = true;
2067 assert_eq!(
2068 dl.to_string(),
2069 "LL | This is an example\nLL | of content lines\n |\n | abc"
2070 );
2071 }
2072
2073 #[test]
2074 fn test_raw_origin_initial_pos_anon_lines() {
2075 let mut dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
2076 path: "src/test.rs",
2077 pos: Some((23, 15)),
2078 header_type: DisplayHeaderType::Initial,
2079 })]);
2080
2081 // Using anonymized_line_numbers should not affect the initial position
2082 dl.anonymized_line_numbers = true;
2083 assert_eq!(dl.to_string(), "--> src/test.rs:23:15");
2084 }
2085}
2086