1use crate::snippet;
35use std::cmp::{max, min, Reverse};
36use std::collections::HashMap;
37use std::fmt::Display;
38use std::ops::Range;
39use std::{cmp, fmt};
40
41use crate::renderer::styled_buffer::StyledBuffer;
42use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH};
43
44const ANONYMIZED_LINE_NUM: &str = "LL";
45const ERROR_TXT: &str = "error";
46const HELP_TXT: &str = "help";
47const INFO_TXT: &str = "info";
48const NOTE_TXT: &str = "note";
49const WARNING_TXT: &str = "warning";
50
51pub(crate) struct DisplayList<'a> {
53 pub(crate) body: Vec<DisplaySet<'a>>,
54 pub(crate) stylesheet: &'a Stylesheet,
55 pub(crate) anonymized_line_numbers: bool,
56}
57
58impl PartialEq for DisplayList<'_> {
59 fn eq(&self, other: &Self) -> bool {
60 self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
61 }
62}
63
64impl fmt::Debug for DisplayList<'_> {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.debug_struct("DisplayList")
67 .field("body", &self.body)
68 .field("anonymized_line_numbers", &self.anonymized_line_numbers)
69 .finish()
70 }
71}
72
73impl Display for DisplayList<'_> {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 let lineno_width = self.body.iter().fold(0, |max, set| {
76 set.display_lines.iter().fold(max, |max, line| match line {
77 DisplayLine::Source { lineno, .. } => cmp::max(lineno.unwrap_or(0), max),
78 _ => max,
79 })
80 });
81 let lineno_width = if lineno_width == 0 {
82 lineno_width
83 } else if self.anonymized_line_numbers {
84 ANONYMIZED_LINE_NUM.len()
85 } else {
86 ((lineno_width as f64).log10().floor() as usize) + 1
87 };
88
89 let multiline_depth = self.body.iter().fold(0, |max, set| {
90 set.display_lines.iter().fold(max, |max2, line| match line {
91 DisplayLine::Source { annotations, .. } => cmp::max(
92 annotations.iter().fold(max2, |max3, line| {
93 cmp::max(
94 match line.annotation_part {
95 DisplayAnnotationPart::Standalone => 0,
96 DisplayAnnotationPart::LabelContinuation => 0,
97 DisplayAnnotationPart::MultilineStart(depth) => depth + 1,
98 DisplayAnnotationPart::MultilineEnd(depth) => depth + 1,
99 },
100 max3,
101 )
102 }),
103 max,
104 ),
105 _ => max2,
106 })
107 });
108 let mut buffer = StyledBuffer::new();
109 for set in self.body.iter() {
110 self.format_set(set, lineno_width, multiline_depth, &mut buffer)?;
111 }
112 write!(f, "{}", buffer.render(self.stylesheet)?)
113 }
114}
115
116impl<'a> DisplayList<'a> {
117 pub(crate) fn new(
118 message: snippet::Message<'a>,
119 stylesheet: &'a Stylesheet,
120 anonymized_line_numbers: bool,
121 term_width: usize,
122 ) -> DisplayList<'a> {
123 let body = format_message(message, term_width, anonymized_line_numbers, true);
124
125 Self {
126 body,
127 stylesheet,
128 anonymized_line_numbers,
129 }
130 }
131
132 fn format_set(
133 &self,
134 set: &DisplaySet<'_>,
135 lineno_width: usize,
136 multiline_depth: usize,
137 buffer: &mut StyledBuffer,
138 ) -> fmt::Result {
139 for line in &set.display_lines {
140 set.format_line(
141 line,
142 lineno_width,
143 multiline_depth,
144 self.stylesheet,
145 self.anonymized_line_numbers,
146 buffer,
147 )?;
148 }
149 Ok(())
150 }
151}
152
153#[derive(Debug, PartialEq)]
154pub(crate) struct DisplaySet<'a> {
155 pub(crate) display_lines: Vec<DisplayLine<'a>>,
156 pub(crate) margin: Margin,
157}
158
159impl DisplaySet<'_> {
160 fn format_label(
161 &self,
162 line_offset: usize,
163 label: &[DisplayTextFragment<'_>],
164 stylesheet: &Stylesheet,
165 buffer: &mut StyledBuffer,
166 ) -> fmt::Result {
167 for fragment in label {
168 let style = match fragment.style {
169 DisplayTextStyle::Regular => stylesheet.none(),
170 DisplayTextStyle::Emphasis => stylesheet.emphasis(),
171 };
172 buffer.append(line_offset, fragment.content, *style);
173 }
174 Ok(())
175 }
176 fn format_annotation(
177 &self,
178 line_offset: usize,
179 annotation: &Annotation<'_>,
180 continuation: bool,
181 stylesheet: &Stylesheet,
182 buffer: &mut StyledBuffer,
183 ) -> fmt::Result {
184 let color = get_annotation_style(&annotation.annotation_type, stylesheet);
185 let formatted_len = if let Some(id) = &annotation.id {
186 2 + id.len() + annotation_type_len(&annotation.annotation_type)
187 } else {
188 annotation_type_len(&annotation.annotation_type)
189 };
190
191 if continuation {
192 for _ in 0..formatted_len + 2 {
193 buffer.append(line_offset, " ", Style::new());
194 }
195 return self.format_label(line_offset, &annotation.label, stylesheet, buffer);
196 }
197 if formatted_len == 0 {
198 self.format_label(line_offset, &annotation.label, stylesheet, buffer)
199 } else {
200 let id = match &annotation.id {
201 Some(id) => format!("[{id}]"),
202 None => String::new(),
203 };
204 buffer.append(
205 line_offset,
206 &format!("{}{}", annotation_type_str(&annotation.annotation_type), id),
207 *color,
208 );
209
210 if !is_annotation_empty(annotation) {
211 buffer.append(line_offset, ": ", stylesheet.none);
212 self.format_label(line_offset, &annotation.label, stylesheet, buffer)?;
213 }
214 Ok(())
215 }
216 }
217
218 #[inline]
219 fn format_raw_line(
220 &self,
221 line_offset: usize,
222 line: &DisplayRawLine<'_>,
223 lineno_width: usize,
224 stylesheet: &Stylesheet,
225 buffer: &mut StyledBuffer,
226 ) -> fmt::Result {
227 match line {
228 DisplayRawLine::Origin {
229 path,
230 pos,
231 header_type,
232 } => {
233 let header_sigil = match header_type {
234 DisplayHeaderType::Initial => "-->",
235 DisplayHeaderType::Continuation => ":::",
236 };
237 let lineno_color = stylesheet.line_no();
238 buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color);
239 buffer.puts(line_offset, lineno_width + 4, path, stylesheet.none);
240 if let Some((col, row)) = pos {
241 buffer.append(line_offset, ":", stylesheet.none);
242 buffer.append(line_offset, col.to_string().as_str(), stylesheet.none);
243 buffer.append(line_offset, ":", stylesheet.none);
244 buffer.append(line_offset, row.to_string().as_str(), stylesheet.none);
245 }
246 Ok(())
247 }
248 DisplayRawLine::Annotation {
249 annotation,
250 source_aligned,
251 continuation,
252 } => {
253 if *source_aligned {
254 if *continuation {
255 for _ in 0..lineno_width + 3 {
256 buffer.append(line_offset, " ", stylesheet.none);
257 }
258 } else {
259 let lineno_color = stylesheet.line_no();
260 for _ in 0..lineno_width + 1 {
261 buffer.append(line_offset, " ", stylesheet.none);
262 }
263 buffer.append(line_offset, "=", *lineno_color);
264 buffer.append(line_offset, " ", *lineno_color);
265 }
266 }
267 self.format_annotation(line_offset, annotation, *continuation, stylesheet, buffer)
268 }
269 }
270 }
271
272 #[inline]
274 fn format_line(
275 &self,
276 dl: &DisplayLine<'_>,
277 lineno_width: usize,
278 multiline_depth: usize,
279 stylesheet: &Stylesheet,
280 anonymized_line_numbers: bool,
281 buffer: &mut StyledBuffer,
282 ) -> fmt::Result {
283 let line_offset = buffer.num_lines();
284 match dl {
285 DisplayLine::Source {
286 lineno,
287 inline_marks,
288 line,
289 annotations,
290 } => {
291 let lineno_color = stylesheet.line_no();
292 if anonymized_line_numbers && lineno.is_some() {
293 let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$} |");
294 buffer.puts(line_offset, 0, &num, *lineno_color);
295 } else {
296 match lineno {
297 Some(n) => {
298 let num = format!("{n:>lineno_width$} |");
299 buffer.puts(line_offset, 0, &num, *lineno_color);
300 }
301 None => {
302 buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color);
303 }
304 };
305 }
306 if let DisplaySourceLine::Content { text, .. } = line {
307 let width_offset = lineno_width + 3;
310 let code_offset = if multiline_depth == 0 {
311 width_offset
312 } else {
313 width_offset + multiline_depth + 1
314 };
315
316 if !inline_marks.is_empty() || 0 < multiline_depth {
318 format_inline_marks(
319 line_offset,
320 inline_marks,
321 lineno_width,
322 stylesheet,
323 buffer,
324 )?;
325 }
326
327 let text = normalize_whitespace(text);
328 let line_len = text.as_bytes().len();
329 let left = self.margin.left(line_len);
330 let right = self.margin.right(line_len);
331
332 let mut taken = 0;
334 let code: String = text
335 .chars()
336 .skip(left)
337 .take_while(|ch| {
338 let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
343 if taken + next > right - left {
344 return false;
345 }
346 taken += next;
347 true
348 })
349 .collect();
350 buffer.puts(line_offset, code_offset, &code, Style::new());
351 if self.margin.was_cut_left() {
352 buffer.puts(line_offset, code_offset, "...", *lineno_color);
354 }
355 if self.margin.was_cut_right(line_len) {
356 buffer.puts(line_offset, code_offset + taken - 3, "...", *lineno_color);
357 }
358
359 let left: usize = text
360 .chars()
361 .take(left)
362 .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
363 .sum();
364
365 let mut annotations = annotations.clone();
366 annotations.sort_by_key(|a| Reverse(a.range.0));
367
368 let mut annotations_positions = vec![];
369 let mut line_len: usize = 0;
370 let mut p = 0;
371 for (i, annotation) in annotations.iter().enumerate() {
372 for (j, next) in annotations.iter().enumerate() {
373 if overlaps(next, annotation, 0)
376 && annotation.has_label()
377 && j > i
378 && p == 0
379 {
381 if next.range.0 == annotation.range.0
384 && next.range.1 == annotation.range.1
385 && !next.has_label()
386 {
387 continue;
388 }
389
390 p += 1;
392 break;
393 }
394 }
395 annotations_positions.push((p, annotation));
396 for (j, next) in annotations.iter().enumerate() {
397 if j > i {
398 let l = next
399 .annotation
400 .label
401 .iter()
402 .map(|label| label.content)
403 .collect::<Vec<_>>()
404 .join("")
405 .len()
406 + 2;
407 if (overlaps(next, annotation, l)
421 && annotation.has_label()
422 && next.has_label())
423 || (annotation.takes_space() && next.has_label())
424 || (annotation.has_label() && next.takes_space())
425 || (annotation.takes_space() && next.takes_space())
426 || (overlaps(next, annotation, l)
427 && next.range.1 <= annotation.range.1
428 && next.has_label()
429 && p == 0)
430 {
432 p += 1;
434 break;
435 }
436 }
437 }
438 line_len = max(line_len, p);
439 }
440
441 if line_len != 0 {
442 line_len += 1;
443 }
444
445 if annotations_positions.iter().all(|(_, ann)| {
446 matches!(
447 ann.annotation_part,
448 DisplayAnnotationPart::MultilineStart(_)
449 )
450 }) {
451 if let Some(max_pos) =
452 annotations_positions.iter().map(|(pos, _)| *pos).max()
453 {
454 for (pos, _) in &mut annotations_positions {
467 *pos = max_pos - *pos;
468 }
469 line_len = line_len.saturating_sub(1);
472 }
473 }
474
475 if multiline_depth == 1
493 && annotations_positions.len() == 1
494 && annotations_positions
495 .first()
496 .map_or(false, |(_, annotation)| {
497 matches!(
498 annotation.annotation_part,
499 DisplayAnnotationPart::MultilineStart(_)
500 ) && text
501 .chars()
502 .take(annotation.range.0)
503 .all(|c| c.is_whitespace())
504 })
505 {
506 let (_, ann) = annotations_positions.remove(0);
507 let style = get_annotation_style(&ann.annotation_type, stylesheet);
508 buffer.putc(line_offset, 3 + lineno_width, '/', *style);
509 }
510
511 if !annotations_positions.is_empty() {
524 for pos in 0..=line_len {
525 buffer.putc(
526 line_offset + pos + 1,
527 lineno_width + 1,
528 '|',
529 stylesheet.line_no,
530 );
531 }
532 }
533
534 for &(pos, annotation) in &annotations_positions {
547 let style = get_annotation_style(&annotation.annotation_type, stylesheet);
548 let pos = pos + 1;
549 match annotation.annotation_part {
550 DisplayAnnotationPart::MultilineStart(depth)
551 | DisplayAnnotationPart::MultilineEnd(depth) => {
552 for col in width_offset + depth
553 ..(code_offset + annotation.range.0).saturating_sub(left)
554 {
555 buffer.putc(line_offset + pos, col + 1, '_', *style);
556 }
557 }
558 _ => {}
559 }
560 }
561
562 for &(pos, annotation) in &annotations_positions {
574 let style = get_annotation_style(&annotation.annotation_type, stylesheet);
575 let pos = pos + 1;
576 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
577 for p in line_offset + 2..=line_offset + pos {
578 buffer.putc(
579 p,
580 (code_offset + annotation.range.0).saturating_sub(left),
581 '|',
582 *style,
583 );
584 }
585 }
586 match annotation.annotation_part {
587 DisplayAnnotationPart::MultilineStart(depth) => {
588 for p in line_offset + pos + 1..line_offset + line_len + 2 {
589 buffer.putc(p, width_offset + depth, '|', *style);
590 }
591 }
592 DisplayAnnotationPart::MultilineEnd(depth) => {
593 for p in line_offset..=line_offset + pos {
594 buffer.putc(p, width_offset + depth, '|', *style);
595 }
596 }
597 _ => {}
598 }
599 }
600
601 for inline_mark in inline_marks {
604 let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type;
605 let style = get_annotation_style(&inline_mark.annotation_type, stylesheet);
606 if annotations_positions.is_empty() {
607 buffer.putc(line_offset, width_offset + depth, '|', *style);
608 } else {
609 for p in line_offset..=line_offset + line_len + 1 {
610 buffer.putc(p, width_offset + depth, '|', *style);
611 }
612 }
613 }
614
615 for &(pos, annotation) in &annotations_positions {
627 if !is_annotation_empty(&annotation.annotation) {
628 let style =
629 get_annotation_style(&annotation.annotation_type, stylesheet);
630 let mut formatted_len = if let Some(id) = &annotation.annotation.id {
631 2 + id.len()
632 + annotation_type_len(&annotation.annotation.annotation_type)
633 } else {
634 annotation_type_len(&annotation.annotation.annotation_type)
635 };
636 let (pos, col) = if pos == 0 {
637 (pos + 1, (annotation.range.1 + 1).saturating_sub(left))
638 } else {
639 (pos + 2, annotation.range.0.saturating_sub(left))
640 };
641 if annotation.annotation_part
642 == DisplayAnnotationPart::LabelContinuation
643 {
644 formatted_len = 0;
645 } else if formatted_len != 0 {
646 formatted_len += 2;
647 let id = match &annotation.annotation.id {
648 Some(id) => format!("[{id}]"),
649 None => String::new(),
650 };
651 buffer.puts(
652 line_offset + pos,
653 col + code_offset,
654 &format!(
655 "{}{}: ",
656 annotation_type_str(&annotation.annotation_type),
657 id
658 ),
659 *style,
660 );
661 } else {
662 formatted_len = 0;
663 }
664 let mut before = 0;
665 for fragment in &annotation.annotation.label {
666 let inner_col = before + formatted_len + col + code_offset;
667 buffer.puts(line_offset + pos, inner_col, fragment.content, *style);
668 before += fragment.content.len();
669 }
670 }
671 }
672
673 annotations_positions.sort_by_key(|(_, ann)| {
682 Reverse(ann.len())
684 });
685
686 for &(_, annotation) in &annotations_positions {
698 let mark = match annotation.annotation_type {
699 DisplayAnnotationType::Error => '^',
700 DisplayAnnotationType::Warning => '-',
701 DisplayAnnotationType::Info => '-',
702 DisplayAnnotationType::Note => '-',
703 DisplayAnnotationType::Help => '-',
704 DisplayAnnotationType::None => ' ',
705 };
706 let style = get_annotation_style(&annotation.annotation_type, stylesheet);
707 for p in annotation.range.0..annotation.range.1 {
708 buffer.putc(
709 line_offset + 1,
710 (code_offset + p).saturating_sub(left),
711 mark,
712 *style,
713 );
714 }
715 }
716 } else if !inline_marks.is_empty() {
717 format_inline_marks(
718 line_offset,
719 inline_marks,
720 lineno_width,
721 stylesheet,
722 buffer,
723 )?;
724 }
725 Ok(())
726 }
727 DisplayLine::Fold { inline_marks } => {
728 buffer.puts(line_offset, 0, "...", *stylesheet.line_no());
729 if !inline_marks.is_empty() || 0 < multiline_depth {
730 format_inline_marks(
731 line_offset,
732 inline_marks,
733 lineno_width,
734 stylesheet,
735 buffer,
736 )?;
737 }
738 Ok(())
739 }
740 DisplayLine::Raw(line) => {
741 self.format_raw_line(line_offset, line, lineno_width, stylesheet, buffer)
742 }
743 }
744 }
745}
746
747#[derive(Clone, Debug, PartialEq)]
749pub(crate) struct Annotation<'a> {
750 pub(crate) annotation_type: DisplayAnnotationType,
751 pub(crate) id: Option<&'a str>,
752 pub(crate) label: Vec<DisplayTextFragment<'a>>,
753}
754
755#[derive(Debug, PartialEq)]
757pub(crate) enum DisplayLine<'a> {
758 Source {
760 lineno: Option<usize>,
761 inline_marks: Vec<DisplayMark>,
762 line: DisplaySourceLine<'a>,
763 annotations: Vec<DisplaySourceAnnotation<'a>>,
764 },
765
766 Fold { inline_marks: Vec<DisplayMark> },
768
769 Raw(DisplayRawLine<'a>),
771}
772
773#[derive(Debug, PartialEq)]
775pub(crate) enum DisplaySourceLine<'a> {
776 Content {
778 text: &'a str,
779 range: (usize, usize), end_line: EndLine,
781 },
782 Empty,
784}
785
786#[derive(Clone, Debug, PartialEq)]
787pub(crate) struct DisplaySourceAnnotation<'a> {
788 pub(crate) annotation: Annotation<'a>,
789 pub(crate) range: (usize, usize),
790 pub(crate) annotation_type: DisplayAnnotationType,
791 pub(crate) annotation_part: DisplayAnnotationPart,
792}
793
794impl DisplaySourceAnnotation<'_> {
795 fn has_label(&self) -> bool {
796 !self
797 .annotation
798 .label
799 .iter()
800 .all(|label| label.content.is_empty())
801 }
802
803 fn len(&self) -> usize {
805 if self.range.1 > self.range.0 {
807 self.range.1 - self.range.0
808 } else {
809 self.range.0 - self.range.1
810 }
811 }
812
813 fn takes_space(&self) -> bool {
814 matches!(
816 self.annotation_part,
817 DisplayAnnotationPart::MultilineStart(_) | DisplayAnnotationPart::MultilineEnd(_)
818 )
819 }
820}
821
822#[derive(Debug, PartialEq)]
825pub(crate) enum DisplayRawLine<'a> {
826 Origin {
829 path: &'a str,
830 pos: Option<(usize, usize)>,
831 header_type: DisplayHeaderType,
832 },
833
834 Annotation {
836 annotation: Annotation<'a>,
837
838 source_aligned: bool,
841 continuation: bool,
846 },
847}
848
849#[derive(Clone, Debug, PartialEq)]
851pub(crate) struct DisplayTextFragment<'a> {
852 pub(crate) content: &'a str,
853 pub(crate) style: DisplayTextStyle,
854}
855
856#[derive(Debug, Clone, Copy, PartialEq)]
860pub(crate) enum DisplayTextStyle {
861 Regular,
862 Emphasis,
863}
864
865#[derive(Debug, Clone, PartialEq)]
867pub(crate) enum DisplayAnnotationPart {
868 Standalone,
870 LabelContinuation,
872 MultilineStart(usize),
874 MultilineEnd(usize),
876}
877
878#[derive(Debug, Clone, PartialEq)]
880pub(crate) struct DisplayMark {
881 pub(crate) mark_type: DisplayMarkType,
882 pub(crate) annotation_type: DisplayAnnotationType,
883}
884
885#[derive(Debug, Clone, PartialEq)]
887pub(crate) enum DisplayMarkType {
888 AnnotationThrough(usize),
890}
891
892#[derive(Debug, Clone, PartialEq)]
900pub(crate) enum DisplayAnnotationType {
901 None,
902 Error,
903 Warning,
904 Info,
905 Note,
906 Help,
907}
908
909impl From<snippet::Level> for DisplayAnnotationType {
910 fn from(at: snippet::Level) -> Self {
911 match at {
912 snippet::Level::Error => DisplayAnnotationType::Error,
913 snippet::Level::Warning => DisplayAnnotationType::Warning,
914 snippet::Level::Info => DisplayAnnotationType::Info,
915 snippet::Level::Note => DisplayAnnotationType::Note,
916 snippet::Level::Help => DisplayAnnotationType::Help,
917 }
918 }
919}
920
921#[derive(Debug, Clone, PartialEq)]
925pub(crate) enum DisplayHeaderType {
926 Initial,
928
929 Continuation,
931}
932
933struct CursorLines<'a>(&'a str);
934
935impl CursorLines<'_> {
936 fn new(src: &str) -> CursorLines<'_> {
937 CursorLines(src)
938 }
939}
940
941#[derive(Copy, Clone, Debug, PartialEq)]
942pub(crate) enum EndLine {
943 Eof,
944 Lf,
945 Crlf,
946}
947
948impl EndLine {
949 pub(crate) fn len(self) -> usize {
951 match self {
952 EndLine::Eof => 0,
953 EndLine::Lf => 1,
954 EndLine::Crlf => 2,
955 }
956 }
957}
958
959impl<'a> Iterator for CursorLines<'a> {
960 type Item = (&'a str, EndLine);
961
962 fn next(&mut self) -> Option<Self::Item> {
963 if self.0.is_empty() {
964 None
965 } else {
966 self.0
967 .find('\n')
968 .map(|x| {
969 let ret = if 0 < x {
970 if self.0.as_bytes()[x - 1] == b'\r' {
971 (&self.0[..x - 1], EndLine::Crlf)
972 } else {
973 (&self.0[..x], EndLine::Lf)
974 }
975 } else {
976 ("", EndLine::Lf)
977 };
978 self.0 = &self.0[x + 1..];
979 ret
980 })
981 .or_else(|| {
982 let ret = Some((self.0, EndLine::Eof));
983 self.0 = "";
984 ret
985 })
986 }
987 }
988}
989
990fn format_message(
991 message: snippet::Message<'_>,
992 term_width: usize,
993 anonymized_line_numbers: bool,
994 primary: bool,
995) -> Vec<DisplaySet<'_>> {
996 let snippet::Message {
997 level,
998 id,
999 title,
1000 footer,
1001 snippets,
1002 } = message;
1003
1004 let mut sets = vec![];
1005 let body = if !snippets.is_empty() || primary {
1006 vec![format_title(level, id, title)]
1007 } else {
1008 format_footer(level, id, title)
1009 };
1010
1011 for (idx, snippet) in snippets.into_iter().enumerate() {
1012 let snippet = fold_prefix_suffix(snippet);
1013 sets.push(format_snippet(
1014 snippet,
1015 idx == 0,
1016 !footer.is_empty(),
1017 term_width,
1018 anonymized_line_numbers,
1019 ));
1020 }
1021
1022 if let Some(first) = sets.first_mut() {
1023 for line in body {
1024 first.display_lines.insert(0, line);
1025 }
1026 } else {
1027 sets.push(DisplaySet {
1028 display_lines: body,
1029 margin: Margin::new(0, 0, 0, 0, DEFAULT_TERM_WIDTH, 0),
1030 });
1031 }
1032
1033 for annotation in footer {
1034 sets.extend(format_message(
1035 annotation,
1036 term_width,
1037 anonymized_line_numbers,
1038 false,
1039 ));
1040 }
1041
1042 sets
1043}
1044
1045fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> {
1046 DisplayLine::Raw(DisplayRawLine::Annotation {
1047 annotation: Annotation {
1048 annotation_type: DisplayAnnotationType::from(level),
1049 id,
1050 label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
1051 },
1052 source_aligned: false,
1053 continuation: false,
1054 })
1055}
1056
1057fn format_footer<'a>(
1058 level: crate::Level,
1059 id: Option<&'a str>,
1060 label: &'a str,
1061) -> Vec<DisplayLine<'a>> {
1062 let mut result = vec![];
1063 for (i, line) in label.lines().enumerate() {
1064 result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
1065 annotation: Annotation {
1066 annotation_type: DisplayAnnotationType::from(level),
1067 id,
1068 label: format_label(Some(line), None),
1069 },
1070 source_aligned: true,
1071 continuation: i != 0,
1072 }));
1073 }
1074 result
1075}
1076
1077fn format_label(
1078 label: Option<&str>,
1079 style: Option<DisplayTextStyle>,
1080) -> Vec<DisplayTextFragment<'_>> {
1081 let mut result = vec![];
1082 if let Some(label) = label {
1083 let element_style = style.unwrap_or(DisplayTextStyle::Regular);
1084 result.push(DisplayTextFragment {
1085 content: label,
1086 style: element_style,
1087 });
1088 }
1089 result
1090}
1091
1092fn format_snippet(
1093 snippet: snippet::Snippet<'_>,
1094 is_first: bool,
1095 has_footer: bool,
1096 term_width: usize,
1097 anonymized_line_numbers: bool,
1098) -> DisplaySet<'_> {
1099 let main_range = snippet.annotations.first().map(|x| x.range.start);
1100 let origin = snippet.origin;
1101 let need_empty_header = origin.is_some() || is_first;
1102 let mut body = format_body(
1103 snippet,
1104 need_empty_header,
1105 has_footer,
1106 term_width,
1107 anonymized_line_numbers,
1108 );
1109 let header = format_header(origin, main_range, &body.display_lines, is_first);
1110
1111 if let Some(header) = header {
1112 body.display_lines.insert(0, header);
1113 }
1114
1115 body
1116}
1117
1118#[inline]
1119fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> {
1121 a.and_then(|a| b.map(|b| (a, b)))
1122}
1123
1124fn format_header<'a>(
1125 origin: Option<&'a str>,
1126 main_range: Option<usize>,
1127 body: &[DisplayLine<'_>],
1128 is_first: bool,
1129) -> Option<DisplayLine<'a>> {
1130 let display_header = if is_first {
1131 DisplayHeaderType::Initial
1132 } else {
1133 DisplayHeaderType::Continuation
1134 };
1135
1136 if let Some((main_range, path)) = zip_opt(main_range, origin) {
1137 let mut col = 1;
1138 let mut line_offset = 1;
1139
1140 for item in body {
1141 if let DisplayLine::Source {
1142 line:
1143 DisplaySourceLine::Content {
1144 text,
1145 range,
1146 end_line,
1147 },
1148 lineno,
1149 ..
1150 } = item
1151 {
1152 if main_range >= range.0 && main_range < range.1 + max(*end_line as usize, 1) {
1153 let char_column = text[0..(main_range - range.0).min(text.len())]
1154 .chars()
1155 .count();
1156 col = char_column + 1;
1157 line_offset = lineno.unwrap_or(1);
1158 break;
1159 }
1160 }
1161 }
1162
1163 return Some(DisplayLine::Raw(DisplayRawLine::Origin {
1164 path,
1165 pos: Some((line_offset, col)),
1166 header_type: display_header,
1167 }));
1168 }
1169
1170 if let Some(path) = origin {
1171 return Some(DisplayLine::Raw(DisplayRawLine::Origin {
1172 path,
1173 pos: None,
1174 header_type: display_header,
1175 }));
1176 }
1177
1178 None
1179}
1180
1181fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_> {
1182 if !snippet.fold {
1183 return snippet;
1184 }
1185
1186 let ann_start = snippet
1187 .annotations
1188 .iter()
1189 .map(|ann| ann.range.start)
1190 .min()
1191 .unwrap_or(0);
1192 if let Some(before_new_start) = snippet.source[0..ann_start].rfind('\n') {
1193 let new_start = before_new_start + 1;
1194
1195 let line_offset = newline_count(&snippet.source[..new_start]);
1196 snippet.line_start += line_offset;
1197
1198 snippet.source = &snippet.source[new_start..];
1199
1200 for ann in &mut snippet.annotations {
1201 let range_start = ann.range.start - new_start;
1202 let range_end = ann.range.end - new_start;
1203 ann.range = range_start..range_end;
1204 }
1205 }
1206
1207 let ann_end = snippet
1208 .annotations
1209 .iter()
1210 .map(|ann| ann.range.end)
1211 .max()
1212 .unwrap_or(snippet.source.len());
1213 if let Some(end_offset) = snippet.source[ann_end..].find('\n') {
1214 let new_end = ann_end + end_offset;
1215 snippet.source = &snippet.source[..new_end];
1216 }
1217
1218 snippet
1219}
1220
1221fn newline_count(body: &str) -> usize {
1222 #[cfg(feature = "simd")]
1223 {
1224 memchr::memchr_iter(b'\n', body.as_bytes()).count()
1225 }
1226 #[cfg(not(feature = "simd"))]
1227 {
1228 body.lines().count()
1229 }
1230}
1231
1232fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
1233 const INNER_CONTEXT: usize = 1;
1234 const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1;
1235
1236 let mut lines = vec![];
1237 let mut unhighlighed_lines = vec![];
1238 for line in body {
1239 match &line {
1240 DisplayLine::Source { annotations, .. } => {
1241 if annotations.is_empty() {
1242 unhighlighed_lines.push(line);
1243 } else {
1244 if lines.is_empty() {
1245 unhighlighed_lines.clear();
1247 }
1248 match unhighlighed_lines.len() {
1249 0 => {}
1250 n if n <= INNER_UNFOLD_SIZE => {
1251 lines.append(&mut unhighlighed_lines);
1253 }
1254 _ => {
1255 lines.extend(unhighlighed_lines.drain(..INNER_CONTEXT));
1256 let inline_marks = lines
1257 .last()
1258 .and_then(|line| {
1259 if let DisplayLine::Source {
1260 ref inline_marks, ..
1261 } = line
1262 {
1263 let inline_marks = inline_marks.clone();
1264 Some(inline_marks)
1265 } else {
1266 None
1267 }
1268 })
1269 .unwrap_or_default();
1270 lines.push(DisplayLine::Fold {
1271 inline_marks: inline_marks.clone(),
1272 });
1273 unhighlighed_lines
1274 .drain(..unhighlighed_lines.len().saturating_sub(INNER_CONTEXT));
1275 lines.append(&mut unhighlighed_lines);
1276 }
1277 }
1278 lines.push(line);
1279 }
1280 }
1281 _ => {
1282 unhighlighed_lines.push(line);
1283 }
1284 }
1285 }
1286
1287 lines
1288}
1289
1290fn format_body(
1291 snippet: snippet::Snippet<'_>,
1292 need_empty_header: bool,
1293 has_footer: bool,
1294 term_width: usize,
1295 anonymized_line_numbers: bool,
1296) -> DisplaySet<'_> {
1297 let source_len = snippet.source.len();
1298 if let Some(bigger) = snippet.annotations.iter().find_map(|x| {
1299 if source_len + 1 < x.range.end {
1301 Some(&x.range)
1302 } else {
1303 None
1304 }
1305 }) {
1306 panic!("SourceAnnotation range `{bigger:?}` is beyond the end of buffer `{source_len}`")
1307 }
1308
1309 let mut body = vec![];
1310 let mut current_line = snippet.line_start;
1311 let mut current_index = 0;
1312
1313 let mut whitespace_margin = usize::MAX;
1314 let mut span_left_margin = usize::MAX;
1315 let mut span_right_margin = 0;
1316 let mut label_right_margin = 0;
1317 let mut max_line_len = 0;
1318
1319 let mut depth_map: HashMap<usize, usize> = HashMap::new();
1320 let mut current_depth = 0;
1321 let mut annotations = snippet.annotations;
1322 let ranges = annotations
1323 .iter()
1324 .map(|a| a.range.clone())
1325 .collect::<Vec<_>>();
1326 ranges.iter().enumerate().for_each(|(r_idx, range)| {
1357 annotations
1358 .iter_mut()
1359 .enumerate()
1360 .skip(r_idx + 1)
1361 .for_each(|(ann_idx, ann)| {
1362 if ann_idx != r_idx
1364 && snippet.source[ann.range.clone()].lines().count() > 1
1366 && ann.range.start == range.start
1368 && ann.range.end == range.end
1369 {
1370 ann.range.start = ann.range.end.saturating_sub(1);
1371 }
1372 });
1373 });
1374 annotations.sort_by_key(|a| a.range.start);
1375 let mut annotations = annotations.into_iter().enumerate().collect::<Vec<_>>();
1376
1377 for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() {
1378 let line_length: usize = line.len();
1379 let line_range = (current_index, current_index + line_length);
1380 let end_line_size = end_line.len();
1381 body.push(DisplayLine::Source {
1382 lineno: Some(current_line),
1383 inline_marks: vec![],
1384 line: DisplaySourceLine::Content {
1385 text: line,
1386 range: line_range,
1387 end_line,
1388 },
1389 annotations: vec![],
1390 });
1391
1392 let leading_whitespace = line
1393 .chars()
1394 .take_while(|c| c.is_whitespace())
1395 .map(|c| {
1396 match c {
1397 '\t' => 4,
1399 _ => 1,
1400 }
1401 })
1402 .sum();
1403 if line.chars().any(|c| !c.is_whitespace()) {
1404 whitespace_margin = min(whitespace_margin, leading_whitespace);
1405 }
1406 max_line_len = max(max_line_len, line_length);
1407
1408 let line_start_index = line_range.0;
1409 let line_end_index = line_range.1;
1410 current_line += 1;
1411 current_index += line_length + end_line_size;
1412
1413 annotations.retain(|(key, annotation)| {
1415 let body_idx = idx;
1416 let annotation_type = match annotation.level {
1417 snippet::Level::Error => DisplayAnnotationType::None,
1418 snippet::Level::Warning => DisplayAnnotationType::None,
1419 _ => DisplayAnnotationType::from(annotation.level),
1420 };
1421 let label_right = annotation.label.map_or(0, |label| label.len() + 1);
1422 match annotation.range {
1423 Range { start, .. } if start > line_end_index + end_line_size => true,
1426 Range { start, end }
1429 if start >= line_start_index
1430 && end <= line_end_index + max(end_line_size, 1) =>
1433 {
1434 if let DisplayLine::Source {
1435 ref mut annotations,
1436 ..
1437 } = body[body_idx]
1438 {
1439 let annotation_start_col = line
1440 [0..(start - line_start_index).min(line_length)]
1441 .chars()
1442 .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
1443 .sum::<usize>();
1444 let mut annotation_end_col = line
1445 [0..(end - line_start_index).min(line_length)]
1446 .chars()
1447 .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
1448 .sum::<usize>();
1449 if annotation_start_col == annotation_end_col {
1450 annotation_end_col += 1;
1452 }
1453
1454 span_left_margin = min(span_left_margin, annotation_start_col);
1455 span_right_margin = max(span_right_margin, annotation_end_col);
1456 label_right_margin =
1457 max(label_right_margin, annotation_end_col + label_right);
1458
1459 let range = (annotation_start_col, annotation_end_col);
1460 annotations.push(DisplaySourceAnnotation {
1461 annotation: Annotation {
1462 annotation_type,
1463 id: None,
1464 label: format_label(annotation.label, None),
1465 },
1466 range,
1467 annotation_type: DisplayAnnotationType::from(annotation.level),
1468 annotation_part: DisplayAnnotationPart::Standalone,
1469 });
1470 }
1471 false
1472 }
1473 Range { start, end }
1476 if start >= line_start_index
1477 && start <= line_end_index + end_line_size.saturating_sub(1)
1479 && end > line_end_index =>
1480 {
1481 if let DisplayLine::Source {
1482 ref mut annotations,
1483 ..
1484 } = body[body_idx]
1485 {
1486 let annotation_start_col = line
1487 [0..(start - line_start_index).min(line_length)]
1488 .chars()
1489 .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
1490 .sum::<usize>();
1491 let annotation_end_col = annotation_start_col + 1;
1492
1493 span_left_margin = min(span_left_margin, annotation_start_col);
1494 span_right_margin = max(span_right_margin, annotation_end_col);
1495 label_right_margin =
1496 max(label_right_margin, annotation_end_col + label_right);
1497
1498 let range = (annotation_start_col, annotation_end_col);
1499 annotations.push(DisplaySourceAnnotation {
1500 annotation: Annotation {
1501 annotation_type,
1502 id: None,
1503 label: vec![],
1504 },
1505 range,
1506 annotation_type: DisplayAnnotationType::from(annotation.level),
1507 annotation_part: DisplayAnnotationPart::MultilineStart(current_depth),
1508 });
1509 depth_map.insert(*key, current_depth);
1510 current_depth += 1;
1511 }
1512 true
1513 }
1514 Range { start, end }
1517 if start < line_start_index && end > line_end_index + max(end_line_size, 1) =>
1518 {
1519 if let DisplayLine::Source {
1520 ref mut inline_marks,
1521 ..
1522 } = body[body_idx]
1523 {
1524 let depth = depth_map.get(key).cloned().unwrap_or_default();
1525 inline_marks.push(DisplayMark {
1526 mark_type: DisplayMarkType::AnnotationThrough(depth),
1527 annotation_type: DisplayAnnotationType::from(annotation.level),
1528 });
1529 }
1530 true
1531 }
1532 Range { start, end }
1535 if start < line_start_index
1536 && end >= line_start_index
1537 && end <= line_end_index + max(end_line_size, 1) =>
1540 {
1541 if let DisplayLine::Source {
1542 ref mut annotations,
1543 ..
1544 } = body[body_idx]
1545 {
1546 let end_mark = line[0..(end - line_start_index).min(line_length)]
1547 .chars()
1548 .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
1549 .sum::<usize>()
1550 .saturating_sub(1);
1551 let (end_mark, end_plus_one) = if end > line_end_index
1554 || (end == line_end_index + 1 && end_line_size == 0)
1556 {
1557 (end_mark + 1, end_mark + 2)
1558 } else {
1559 (end_mark, end_mark + 1)
1560 };
1561
1562 span_left_margin = min(span_left_margin, end_mark);
1563 span_right_margin = max(span_right_margin, end_plus_one);
1564 label_right_margin = max(label_right_margin, end_plus_one + label_right);
1565
1566 let range = (end_mark, end_plus_one);
1567 let depth = depth_map.remove(key).unwrap_or(0);
1568 annotations.push(DisplaySourceAnnotation {
1569 annotation: Annotation {
1570 annotation_type,
1571 id: None,
1572 label: format_label(annotation.label, None),
1573 },
1574 range,
1575 annotation_type: DisplayAnnotationType::from(annotation.level),
1576 annotation_part: DisplayAnnotationPart::MultilineEnd(depth),
1577 });
1578 }
1579 false
1580 }
1581 _ => true,
1582 }
1583 });
1584 let max = depth_map.len();
1587 if current_depth > max {
1588 current_depth = max;
1589 }
1590 }
1591
1592 if snippet.fold {
1593 body = fold_body(body);
1594 }
1595
1596 if need_empty_header {
1597 body.insert(
1598 0,
1599 DisplayLine::Source {
1600 lineno: None,
1601 inline_marks: vec![],
1602 line: DisplaySourceLine::Empty,
1603 annotations: vec![],
1604 },
1605 );
1606 }
1607
1608 if has_footer {
1609 body.push(DisplayLine::Source {
1610 lineno: None,
1611 inline_marks: vec![],
1612 line: DisplaySourceLine::Empty,
1613 annotations: vec![],
1614 });
1615 } else if let Some(DisplayLine::Source { .. }) = body.last() {
1616 body.push(DisplayLine::Source {
1617 lineno: None,
1618 inline_marks: vec![],
1619 line: DisplaySourceLine::Empty,
1620 annotations: vec![],
1621 });
1622 }
1623 let max_line_num_len = if anonymized_line_numbers {
1624 ANONYMIZED_LINE_NUM.len()
1625 } else {
1626 current_line.to_string().len()
1627 };
1628
1629 let width_offset = 3 + max_line_num_len;
1630
1631 if span_left_margin == usize::MAX {
1632 span_left_margin = 0;
1633 }
1634
1635 let margin = Margin::new(
1636 whitespace_margin,
1637 span_left_margin,
1638 span_right_margin,
1639 label_right_margin,
1640 term_width.saturating_sub(width_offset),
1641 max_line_len,
1642 );
1643
1644 DisplaySet {
1645 display_lines: body,
1646 margin,
1647 }
1648}
1649
1650#[inline]
1651fn annotation_type_str(annotation_type: &DisplayAnnotationType) -> &'static str {
1652 match annotation_type {
1653 DisplayAnnotationType::Error => ERROR_TXT,
1654 DisplayAnnotationType::Help => HELP_TXT,
1655 DisplayAnnotationType::Info => INFO_TXT,
1656 DisplayAnnotationType::Note => NOTE_TXT,
1657 DisplayAnnotationType::Warning => WARNING_TXT,
1658 DisplayAnnotationType::None => "",
1659 }
1660}
1661
1662fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize {
1663 match annotation_type {
1664 DisplayAnnotationType::Error => ERROR_TXT.len(),
1665 DisplayAnnotationType::Help => HELP_TXT.len(),
1666 DisplayAnnotationType::Info => INFO_TXT.len(),
1667 DisplayAnnotationType::Note => NOTE_TXT.len(),
1668 DisplayAnnotationType::Warning => WARNING_TXT.len(),
1669 DisplayAnnotationType::None => 0,
1670 }
1671}
1672
1673fn get_annotation_style<'a>(
1674 annotation_type: &DisplayAnnotationType,
1675 stylesheet: &'a Stylesheet,
1676) -> &'a Style {
1677 match annotation_type {
1678 DisplayAnnotationType::Error => stylesheet.error(),
1679 DisplayAnnotationType::Warning => stylesheet.warning(),
1680 DisplayAnnotationType::Info => stylesheet.info(),
1681 DisplayAnnotationType::Note => stylesheet.note(),
1682 DisplayAnnotationType::Help => stylesheet.help(),
1683 DisplayAnnotationType::None => stylesheet.none(),
1684 }
1685}
1686
1687#[inline]
1688fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
1689 annotation
1690 .label
1691 .iter()
1692 .all(|fragment| fragment.content.is_empty())
1693}
1694
1695const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
1697 ('\t', " "), ('\u{200D}', ""), ('\u{202A}', ""), ('\u{202B}', ""), ('\u{202D}', ""), ('\u{202E}', ""),
1703 ('\u{2066}', ""),
1704 ('\u{2067}', ""),
1705 ('\u{2068}', ""),
1706 ('\u{202C}', ""),
1707 ('\u{2069}', ""),
1708];
1709
1710fn normalize_whitespace(str: &str) -> String {
1711 let mut s = str.to_owned();
1712 for (c, replacement) in OUTPUT_REPLACEMENTS {
1713 s = s.replace(*c, replacement);
1714 }
1715 s
1716}
1717
1718fn overlaps(
1719 a1: &DisplaySourceAnnotation<'_>,
1720 a2: &DisplaySourceAnnotation<'_>,
1721 padding: usize,
1722) -> bool {
1723 (a2.range.0..a2.range.1).contains(&a1.range.0)
1724 || (a1.range.0..a1.range.1 + padding).contains(&a2.range.0)
1725}
1726
1727fn format_inline_marks(
1728 line: usize,
1729 inline_marks: &[DisplayMark],
1730 lineno_width: usize,
1731 stylesheet: &Stylesheet,
1732 buf: &mut StyledBuffer,
1733) -> fmt::Result {
1734 for mark in inline_marks.iter() {
1735 let annotation_style = get_annotation_style(&mark.annotation_type, stylesheet);
1736 match mark.mark_type {
1737 DisplayMarkType::AnnotationThrough(depth) => {
1738 buf.putc(line, 3 + lineno_width + depth, '|', *annotation_style);
1739 }
1740 };
1741 }
1742 Ok(())
1743}