annotate_snippets/renderer/
display_list.rs

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::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
51/// List of lines to be displayed.
52pub(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    // Adapted from https://github.com/rust-lang/rust/blob/d371d17496f2ce3a56da76aa083f4ef157572c20/compiler/rustc_errors/src/emitter.rs#L706-L1211
273    #[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                    // The width of the line number, a space, pipe, and a space
308                    // `123 | ` is `lineno_width + 3`.
309                    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                    // Add any inline marks to the code line
317                    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                    // On long lines, we strip the source line, accounting for unicode.
333                    let mut taken = 0;
334                    let code: String = text
335                        .chars()
336                        .skip(left)
337                        .take_while(|ch| {
338                            // Make sure that the trimming on the right will fall within the terminal width.
339                            // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char`
340                            // is. For now, just accept that sometimes the code line will be longer than
341                            // desired.
342                            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                        // We have stripped some code/whitespace from the beginning, make it clear.
353                        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                            // This label overlaps with another one and both take space (
374                            // they have text and are not multiline lines).
375                            if overlaps(next, annotation, 0)
376                                && annotation.has_label()
377                                && j > i
378                                && p == 0
379                            // We're currently on the first line, move the label one line down
380                            {
381                                // If we're overlapping with an un-labelled annotation with the same span
382                                // we can just merge them in the output
383                                if next.range.0 == annotation.range.0
384                                    && next.range.1 == annotation.range.1
385                                    && !next.has_label()
386                                {
387                                    continue;
388                                }
389
390                                // This annotation needs a new line in the output.
391                                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                                // Do not allow two labels to be in the same line if they
408                                // overlap including padding, to avoid situations like:
409                                //
410                                // fn foo(x: u32) {
411                                // -------^------
412                                // |      |
413                                // fn_spanx_span
414                                //
415                                // Both labels must have some text, otherwise they are not
416                                // overlapping. Do not add a new line if this annotation or
417                                // the next are vertical line placeholders. If either this
418                                // or the next annotation is multiline start/end, move it
419                                // to a new line so as not to overlap the horizontal lines.
420                                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                                // Avoid #42595.
431                                {
432                                    // This annotation needs a new line in the output.
433                                    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                            // Special case the following, so that we minimize overlapping multiline spans.
455                            //
456                            // 3 │       X0 Y0 Z0
457                            //   │ ┏━━━━━┛  │  │     < We are writing these lines
458                            //   │ ┃┌───────┘  │     < by reverting the "depth" of
459                            //   │ ┃│┌─────────┘     < their multilne spans.
460                            // 4 │ ┃││   X1 Y1 Z1
461                            // 5 │ ┃││   X2 Y2 Z2
462                            //   │ ┃│└────╿──│──┘ `Z` label
463                            //   │ ┃└─────│──┤
464                            //   │ ┗━━━━━━┥  `Y` is a good letter too
465                            //   ╰╴       `X` is a good letter
466                            for (pos, _) in &mut annotations_positions {
467                                *pos = max_pos - *pos;
468                            }
469                            // We know then that we don't need an additional line for the span label, saving us
470                            // one line of vertical space.
471                            line_len = line_len.saturating_sub(1);
472                        }
473                    }
474
475                    // This is a special case where we have a multiline
476                    // annotation that is at the start of the line disregarding
477                    // any leading whitespace, and no other multiline
478                    // annotations overlap it. In this case, we want to draw
479                    //
480                    // 2 |   fn foo() {
481                    //   |  _^
482                    // 3 | |
483                    // 4 | | }
484                    //   | |_^ test
485                    //
486                    // we simplify the output to:
487                    //
488                    // 2 | / fn foo() {
489                    // 3 | |
490                    // 4 | | }
491                    //   | |_^ test
492                    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                    // Draw the column separator for any extra lines that were
512                    // created
513                    //
514                    // After this we will have:
515                    //
516                    // 2 |   fn foo() {
517                    //   |
518                    //   |
519                    //   |
520                    // 3 |
521                    // 4 |   }
522                    //   |
523                    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                    // Write the horizontal lines for multiline annotations
535                    // (only the first and last lines need this).
536                    //
537                    // After this we will have:
538                    //
539                    // 2 |   fn foo() {
540                    //   |  __________
541                    //   |
542                    //   |
543                    // 3 |
544                    // 4 |   }
545                    //   |  _
546                    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                    // Write the vertical lines for labels that are on a different line as the underline.
563                    //
564                    // After this we will have:
565                    //
566                    // 2 |   fn foo() {
567                    //   |  __________
568                    //   | |    |
569                    //   | |
570                    // 3 | |
571                    // 4 | | }
572                    //   | |_
573                    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                    // Add in any inline marks for any extra lines that have
602                    // been created. Output should look like above.
603                    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                    // Write the labels on the annotations that actually have a label.
616                    //
617                    // After this we will have:
618                    //
619                    // 2 |   fn foo() {
620                    //   |  __________
621                    //   |      |
622                    //   |      something about `foo`
623                    // 3 |
624                    // 4 |   }
625                    //   |  _  test
626                    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                    // Sort from biggest span to smallest span so that smaller spans are
674                    // represented in the output:
675                    //
676                    // x | fn foo()
677                    //   | ^^^---^^
678                    //   | |  |
679                    //   | |  something about `foo`
680                    //   | something about `fn foo()`
681                    annotations_positions.sort_by_key(|(_, ann)| {
682                        // Decreasing order. When annotations share the same length, prefer `Primary`.
683                        Reverse(ann.len())
684                    });
685
686                    // Write the underlines.
687                    //
688                    // After this we will have:
689                    //
690                    // 2 |   fn foo() {
691                    //   |  ____-_____^
692                    //   |      |
693                    //   |      something about `foo`
694                    // 3 |
695                    // 4 |   }
696                    //   |  _^  test
697                    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/// Inline annotation which can be used in either Raw or Source line.
748#[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/// A single line used in `DisplayList`.
756#[derive(Debug, PartialEq)]
757pub(crate) enum DisplayLine<'a> {
758    /// A line with `lineno` portion of the slice.
759    Source {
760        lineno: Option<usize>,
761        inline_marks: Vec<DisplayMark>,
762        line: DisplaySourceLine<'a>,
763        annotations: Vec<DisplaySourceAnnotation<'a>>,
764    },
765
766    /// A line indicating a folded part of the slice.
767    Fold { inline_marks: Vec<DisplayMark> },
768
769    /// A line which is displayed outside of slices.
770    Raw(DisplayRawLine<'a>),
771}
772
773/// A source line.
774#[derive(Debug, PartialEq)]
775pub(crate) enum DisplaySourceLine<'a> {
776    /// A line with the content of the Snippet.
777    Content {
778        text: &'a str,
779        range: (usize, usize), // meta information for annotation placement.
780        end_line: EndLine,
781    },
782    /// An empty source line.
783    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    // Length of this annotation as displayed in the stderr output
804    fn len(&self) -> usize {
805        // Account for usize underflows
806        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        // Multiline annotations always have to keep vertical space.
815        matches!(
816            self.annotation_part,
817            DisplayAnnotationPart::MultilineStart(_) | DisplayAnnotationPart::MultilineEnd(_)
818        )
819    }
820}
821
822/// Raw line - a line which does not have the `lineno` part and is not considered
823/// a part of the snippet.
824#[derive(Debug, PartialEq)]
825pub(crate) enum DisplayRawLine<'a> {
826    /// A line which provides information about the location of the given
827    /// slice in the project structure.
828    Origin {
829        path: &'a str,
830        pos: Option<(usize, usize)>,
831        header_type: DisplayHeaderType,
832    },
833
834    /// An annotation line which is not part of any snippet.
835    Annotation {
836        annotation: Annotation<'a>,
837
838        /// If set to `true`, the annotation will be aligned to the
839        /// lineno delimiter of the snippet.
840        source_aligned: bool,
841        /// If set to `true`, only the label of the `Annotation` will be
842        /// displayed. It allows for a multiline annotation to be aligned
843        /// without displaying the meta information (`type` and `id`) to be
844        /// displayed on each line.
845        continuation: bool,
846    },
847}
848
849/// An inline text fragment which any label is composed of.
850#[derive(Clone, Debug, PartialEq)]
851pub(crate) struct DisplayTextFragment<'a> {
852    pub(crate) content: &'a str,
853    pub(crate) style: DisplayTextStyle,
854}
855
856/// A style for the `DisplayTextFragment` which can be visually formatted.
857///
858/// This information may be used to emphasis parts of the label.
859#[derive(Debug, Clone, Copy, PartialEq)]
860pub(crate) enum DisplayTextStyle {
861    Regular,
862    Emphasis,
863}
864
865/// An indicator of what part of the annotation a given `Annotation` is.
866#[derive(Debug, Clone, PartialEq)]
867pub(crate) enum DisplayAnnotationPart {
868    /// A standalone, single-line annotation.
869    Standalone,
870    /// A continuation of a multi-line label of an annotation.
871    LabelContinuation,
872    /// A line starting a multiline annotation.
873    MultilineStart(usize),
874    /// A line ending a multiline annotation.
875    MultilineEnd(usize),
876}
877
878/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
879#[derive(Debug, Clone, PartialEq)]
880pub(crate) struct DisplayMark {
881    pub(crate) mark_type: DisplayMarkType,
882    pub(crate) annotation_type: DisplayAnnotationType,
883}
884
885/// A type of the `DisplayMark`.
886#[derive(Debug, Clone, PartialEq)]
887pub(crate) enum DisplayMarkType {
888    /// A mark indicating a multiline annotation going through the current line.
889    AnnotationThrough(usize),
890}
891
892/// A type of the `Annotation` which may impact the sigils, style or text displayed.
893///
894/// There are several ways to uses this information when formatting the `DisplayList`:
895///
896/// * An annotation may display the name of the type like `error` or `info`.
897/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`.
898/// * `ColorStylesheet` may use different colors for different annotations.
899#[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/// Information whether the header is the initial one or a consequitive one
922/// for multi-slice cases.
923// TODO: private
924#[derive(Debug, Clone, PartialEq)]
925pub(crate) enum DisplayHeaderType {
926    /// Initial header is the first header in the snippet.
927    Initial,
928
929    /// Continuation marks all headers of following slices in the snippet.
930    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    /// The number of characters this line ending occupies in bytes.
950    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]
1119// TODO: option_zip
1120fn 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                        // Ignore leading unhighlighed lines
1246                        unhighlighed_lines.clear();
1247                    }
1248                    match unhighlighed_lines.len() {
1249                        0 => {}
1250                        n if n <= INNER_UNFOLD_SIZE => {
1251                            // Rather than render `...`, don't fold
1252                            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        // Allow highlighting one past the last character in the source.
1300        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    // We want to merge multiline annotations that have the same range into one
1327    // multiline annotation to save space. This is done by making any duplicate
1328    // multiline annotations into a single-line annotation pointing at the end
1329    // of the range.
1330    //
1331    // 3 |       X0 Y0 Z0
1332    //   |  _____^
1333    //   | | ____|
1334    //   | || ___|
1335    //   | |||
1336    // 4 | |||   X1 Y1 Z1
1337    // 5 | |||   X2 Y2 Z2
1338    //   | |||    ^
1339    //   | |||____|
1340    //   |  ||____`X` is a good letter
1341    //   |   |____`Y` is a good letter too
1342    //   |        `Z` label
1343    // Should be
1344    // error: foo
1345    //  --> test.rs:3:3
1346    //   |
1347    // 3 | /   X0 Y0 Z0
1348    // 4 | |   X1 Y1 Z1
1349    // 5 | |   X2 Y2 Z2
1350    //   | |    ^
1351    //   | |____|
1352    //   |      `X` is a good letter
1353    //   |      `Y` is a good letter too
1354    //   |      `Z` label
1355    //   |
1356    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                // Skip if the annotation's index matches the range index
1363                if ann_idx != r_idx
1364                    // We only want to merge multiline annotations
1365                    && snippet.source[ann.range.clone()].lines().count() > 1
1366                    // We only want to merge annotations that have the same range
1367                    && 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                    // Tabs are displayed as 4 spaces
1398                    '\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        // It would be nice to use filter_drain here once it's stable.
1414        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                // This handles if the annotation is on the next line. We add
1424                // the `end_line_size` to account for annotating the line end.
1425                Range { start, .. } if start > line_end_index + end_line_size => true,
1426                // This handles the case where an annotation is contained
1427                // within the current line including any line-end characters.
1428                Range { start, end }
1429                    if start >= line_start_index
1430                        // We add at least one to `line_end_index` to allow
1431                        // highlighting the end of a file
1432                        && 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                            // At least highlight something
1451                            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                // This handles the case where a multiline annotation starts
1474                // somewhere on the current line, including any line-end chars
1475                Range { start, end }
1476                    if start >= line_start_index
1477                        // The annotation can start on a line ending
1478                        && 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                // This handles the case where a multiline annotation starts
1515                // somewhere before this line and ends after it as well
1516                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                // This handles the case where a multiline annotation ends
1533                // somewhere on the current line, including any line-end chars
1534                Range { start, end }
1535                    if start < line_start_index
1536                        && end >= line_start_index
1537                        // We add at least one to `line_end_index` to allow
1538                        // highlighting the end of a file
1539                        && 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                        // If the annotation ends on a line-end character, we
1552                        // need to annotate one past the end of the line
1553                        let (end_mark, end_plus_one) = if end > line_end_index
1554                            // Special case for highlighting the end of a file
1555                            || (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        // Reset the depth counter, but only after we've processed all
1585        // annotations for a given line.
1586        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
1695// We replace some characters so the CLI output is always consistent and underlines aligned.
1696const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
1697    ('\t', "    "),   // We do our own tab replacement
1698    ('\u{200D}', ""), // Replace ZWJ with nothing for consistent terminal output of grapheme clusters.
1699    ('\u{202A}', ""), // The following unicode text flow control characters are inconsistently
1700    ('\u{202B}', ""), // supported across CLIs and can cause confusion due to the bytes on disk
1701    ('\u{202D}', ""), // not corresponding to the visible source code, so we replace them always.
1702    ('\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}