Repository: rust-lang/annotate-snippets-rs
Pull request #105 (2024-03-15 18:21:42)

Fix margin
`Margin` was being utilized for a whole `Message` when it really should be per `Snippet`, since that is what is displayed together. `Margin` should also be calculated by `annotate-snippets` and not passed in, as we know how to figure everything out.

To fix these problems, this PR does the following:
- Grouped a `Snippet`'s `DisplayLine`s together into a `DisplaySet` so all lines to be rendered for a `Snippet` are kept together
- Made annotations be a field in `DisplayLine`, so they can reference information about the line. This will also help us in the future to be able to fold annotations into one line where possible, as well as other future rendering improvements
- Made it so that the `Margin` for each `DisplaySet` is created internally, allowing for better formatting
- Replaced the ability to pass in `Margin` with the ability to pass in only `column_width`, so users can easily specify it without setting all of `Margin`

Breaking changes:
- `Margin` is now private
- You can only pass in `column_width` and not all of `Margin` as we now handle it internally


Patch:
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 05c1910..53caa38 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -32,18 +32,25 @@
 //!
 //! The above snippet has been built out of the following structure:
 use crate::snippet;
+use std::cmp::{max, min};
 use std::fmt::{Display, Write};
 use std::ops::Range;
 use std::{cmp, fmt};
 
-use crate::renderer::{stylesheet::Stylesheet, Margin, Style};
+use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH};
+
+const ANONYMIZED_LINE_NUM: &str = "LL";
+const ERROR_TXT: &str = "error";
+const HELP_TXT: &str = "help";
+const INFO_TXT: &str = "info";
+const NOTE_TXT: &str = "note";
+const WARNING_TXT: &str = "warning";
 
 /// List of lines to be displayed.
 pub(crate) struct DisplayList<'a> {
-    pub body: Vec<DisplayLine<'a>>,
+    pub body: Vec<DisplaySet<'a>>,
     pub stylesheet: &'a Stylesheet,
     pub anonymized_line_numbers: bool,
-    pub margin: Option<Margin>,
 }
 
 impl<'a> PartialEq for DisplayList<'a> {
@@ -63,105 +70,95 @@ impl<'a> fmt::Debug for DisplayList<'a> {
 
 impl<'a> Display for DisplayList<'a> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let lineno_width = self.body.iter().fold(0, |max, line| match line {
-            DisplayLine::Source {
-                lineno: Some(lineno),
-                ..
-            } => {
-                // The largest line is the largest width.
-                cmp::max(*lineno, max)
-            }
-            _ => max,
+        let lineno_width = self.body.iter().fold(0, |max, set| {
+            set.display_lines.iter().fold(max, |max, line| match line {
+                DisplayLine::Source { lineno, .. } => cmp::max(lineno.unwrap_or(0), max),
+                _ => max,
+            })
         });
         let lineno_width = if lineno_width == 0 {
             lineno_width
         } else if self.anonymized_line_numbers {
-            Self::ANONYMIZED_LINE_NUM.len()
+            ANONYMIZED_LINE_NUM.len()
         } else {
             ((lineno_width as f64).log10().floor() as usize) + 1
         };
-        let inline_marks_width = self.body.iter().fold(0, |max, line| match line {
-            DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max),
-            _ => max,
+        let inline_marks_width = self.body.iter().fold(0, |max, set| {
+            set.display_lines.iter().fold(max, |max, line| match line {
+                DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max),
+                _ => max,
+            })
         });
 
-        for (i, line) in self.body.iter().enumerate() {
-            self.format_line(line, lineno_width, inline_marks_width, f)?;
-            if i + 1 < self.body.len() {
-                f.write_char('\n')?;
-            }
+        let mut count_offset = 0;
+        for set in self.body.iter() {
+            self.format_set(set, lineno_width, inline_marks_width, count_offset, f)?;
+            count_offset += set.display_lines.len();
         }
         Ok(())
     }
 }
 
 impl<'a> DisplayList<'a> {
-    const ANONYMIZED_LINE_NUM: &'static str = "LL";
-    const ERROR_TXT: &'static str = "error";
-    const HELP_TXT: &'static str = "help";
-    const INFO_TXT: &'static str = "info";
-    const NOTE_TXT: &'static str = "note";
-    const WARNING_TXT: &'static str = "warning";
-
     pub(crate) fn new(
         message: snippet::Message<'a>,
         stylesheet: &'a Stylesheet,
         anonymized_line_numbers: bool,
-        margin: Option<Margin>,
+        term_width: usize,
     ) -> DisplayList<'a> {
-        let body = format_message(message, margin, true);
+        let body = format_message(message, term_width, anonymized_line_numbers, true);
 
         Self {
             body,
             stylesheet,
             anonymized_line_numbers,
-            margin,
         }
     }
 
-    #[inline]
-    fn format_annotation_type(
-        annotation_type: &DisplayAnnotationType,
+    fn format_set(
+        &self,
+        set: &DisplaySet<'_>,
+        lineno_width: usize,
+        inline_marks_width: usize,
+        count_offset: usize,
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
-        match annotation_type {
-            DisplayAnnotationType::Error => f.write_str(Self::ERROR_TXT),
-            DisplayAnnotationType::Help => f.write_str(Self::HELP_TXT),
-            DisplayAnnotationType::Info => f.write_str(Self::INFO_TXT),
-            DisplayAnnotationType::Note => f.write_str(Self::NOTE_TXT),
-            DisplayAnnotationType::Warning => f.write_str(Self::WARNING_TXT),
-            DisplayAnnotationType::None => Ok(()),
-        }
-    }
-
-    fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize {
-        match annotation_type {
-            DisplayAnnotationType::Error => Self::ERROR_TXT.len(),
-            DisplayAnnotationType::Help => Self::HELP_TXT.len(),
-            DisplayAnnotationType::Info => Self::INFO_TXT.len(),
-            DisplayAnnotationType::Note => Self::NOTE_TXT.len(),
-            DisplayAnnotationType::Warning => Self::WARNING_TXT.len(),
-            DisplayAnnotationType::None => 0,
+        let body_len = self
+            .body
+            .iter()
+            .map(|set| set.display_lines.len())
+            .sum::<usize>();
+        for (i, line) in set.display_lines.iter().enumerate() {
+            set.format_line(
+                line,
+                lineno_width,
+                inline_marks_width,
+                self.stylesheet,
+                self.anonymized_line_numbers,
+                f,
+            )?;
+            if i + count_offset + 1 < body_len {
+                f.write_char('\n')?;
+            }
         }
+        Ok(())
     }
+}
 
-    fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> &Style {
-        match annotation_type {
-            DisplayAnnotationType::Error => self.stylesheet.error(),
-            DisplayAnnotationType::Warning => self.stylesheet.warning(),
-            DisplayAnnotationType::Info => self.stylesheet.info(),
-            DisplayAnnotationType::Note => self.stylesheet.note(),
-            DisplayAnnotationType::Help => self.stylesheet.help(),
-            DisplayAnnotationType::None => self.stylesheet.none(),
-        }
-    }
+#[derive(Debug, PartialEq)]
+pub(crate) struct DisplaySet<'a> {
+    pub display_lines: Vec<DisplayLine<'a>>,
+    pub margin: Margin,
+}
 
+impl<'a> DisplaySet<'a> {
     fn format_label(
         &self,
         label: &[DisplayTextFragment<'_>],
+        stylesheet: &Stylesheet,
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
-        let emphasis_style = self.stylesheet.emphasis();
+        let emphasis_style = stylesheet.emphasis();
 
         for fragment in label {
             match fragment.style {
@@ -185,24 +182,25 @@ impl<'a> DisplayList<'a> {
         annotation: &Annotation<'_>,
         continuation: bool,
         in_source: bool,
+        stylesheet: &Stylesheet,
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
-        let color = self.get_annotation_style(&annotation.annotation_type);
+        let color = get_annotation_style(&annotation.annotation_type, stylesheet);
         let formatted_len = if let Some(id) = &annotation.id {
-            2 + id.len() + Self::annotation_type_len(&annotation.annotation_type)
+            2 + id.len() + annotation_type_len(&annotation.annotation_type)
         } else {
-            Self::annotation_type_len(&annotation.annotation_type)
+            annotation_type_len(&annotation.annotation_type)
         };
 
         if continuation {
             format_repeat_char(' ', formatted_len + 2, f)?;
-            return self.format_label(&annotation.label, f);
+            return self.format_label(&annotation.label, stylesheet, f);
         }
         if formatted_len == 0 {
-            self.format_label(&annotation.label, f)
+            self.format_label(&annotation.label, stylesheet, f)
         } else {
             write!(f, "{}", color.render())?;
-            Self::format_annotation_type(&annotation.annotation_type, f)?;
+            format_annotation_type(&annotation.annotation_type, f)?;
             if let Some(id) = &annotation.id {
                 f.write_char('[')?;
                 f.write_str(id)?;
@@ -214,143 +212,23 @@ impl<'a> DisplayList<'a> {
                 if in_source {
                     write!(f, "{}", color.render())?;
                     f.write_str(": ")?;
-                    self.format_label(&annotation.label, f)?;
+                    self.format_label(&annotation.label, stylesheet, f)?;
                     write!(f, "{}", color.render_reset())?;
                 } else {
                     f.write_str(": ")?;
-                    self.format_label(&annotation.label, f)?;
+                    self.format_label(&annotation.label, stylesheet, f)?;
                 }
             }
             Ok(())
         }
     }
 
-    #[inline]
-    fn format_source_line(
-        &self,
-        line: &DisplaySourceLine<'_>,
-        f: &mut fmt::Formatter<'_>,
-    ) -> fmt::Result {
-        match line {
-            DisplaySourceLine::Empty => Ok(()),
-            DisplaySourceLine::Content { text, .. } => {
-                f.write_char(' ')?;
-                if let Some(margin) = self.margin {
-                    let line_len = text.chars().count();
-                    let mut left = margin.left(line_len);
-                    let right = margin.right(line_len);
-
-                    if margin.was_cut_left() {
-                        // We have stripped some code/whitespace from the beginning, make it clear.
-                        "...".fmt(f)?;
-                        left += 3;
-                    }
-
-                    // On long lines, we strip the source line, accounting for unicode.
-                    let mut taken = 0;
-                    let cut_right = if margin.was_cut_right(line_len) {
-                        taken += 3;
-                        true
-                    } else {
-                        false
-                    };
-                    // Specifies that it will end on the next character, so it will return
-                    // until the next one to the final condition.
-                    let mut ended = false;
-                    let range = text
-                        .char_indices()
-                        .skip(left)
-                        // Complete char iterator with final character
-                        .chain(std::iter::once((text.len(), '\0')))
-                        // Take until the next one to the final condition
-                        .take_while(|(_, ch)| {
-                            // Fast return to iterate over final byte position
-                            if ended {
-                                return false;
-                            }
-                            // Make sure that the trimming on the right will fall within the terminal width.
-                            // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is.
-                            // For now, just accept that sometimes the code line will be longer than desired.
-                            taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
-                            if taken > right - left {
-                                ended = true;
-                            }
-                            true
-                        })
-                        // Reduce to start and end byte position
-                        .fold((None, 0), |acc, (i, _)| {
-                            if acc.0.is_some() {
-                                (acc.0, i)
-                            } else {
-                                (Some(i), i)
-                            }
-                        });
-
-                    // Format text with margins
-                    text[range.0.expect("One character at line")..range.1].fmt(f)?;
-
-                    if cut_right {
-                        // We have stripped some code after the right-most span end, make it clear we did so.
-                        "...".fmt(f)?;
-                    }
-                    Ok(())
-                } else {
-                    text.fmt(f)
-                }
-            }
-            DisplaySourceLine::Annotation {
-                range,
-                annotation,
-                annotation_type,
-                annotation_part,
-            } => {
-                let indent_char = match annotation_part {
-                    DisplayAnnotationPart::Standalone => ' ',
-                    DisplayAnnotationPart::LabelContinuation => ' ',
-                    DisplayAnnotationPart::MultilineStart => '_',
-                    DisplayAnnotationPart::MultilineEnd => '_',
-                };
-                let mark = match annotation_type {
-                    DisplayAnnotationType::Error => '^',
-                    DisplayAnnotationType::Warning => '-',
-                    DisplayAnnotationType::Info => '-',
-                    DisplayAnnotationType::Note => '-',
-                    DisplayAnnotationType::Help => '-',
-                    DisplayAnnotationType::None => ' ',
-                };
-                let color = self.get_annotation_style(annotation_type);
-                let indent_length = match annotation_part {
-                    DisplayAnnotationPart::LabelContinuation => range.1,
-                    _ => range.0,
-                };
-
-                write!(f, "{}", color.render())?;
-                format_repeat_char(indent_char, indent_length + 1, f)?;
-                format_repeat_char(mark, range.1 - indent_length, f)?;
-                write!(f, "{}", color.render_reset())?;
-
-                if !is_annotation_empty(annotation) {
-                    f.write_char(' ')?;
-                    write!(f, "{}", color.render())?;
-                    self.format_annotation(
-                        annotation,
-                        annotation_part == &DisplayAnnotationPart::LabelContinuation,
-                        true,
-                        f,
-                    )?;
-                    write!(f, "{}", color.render_reset())?;
-                }
-
-                Ok(())
-            }
-        }
-    }
-
     #[inline]
     fn format_raw_line(
         &self,
         line: &DisplayRawLine<'_>,
         lineno_width: usize,
+        stylesheet: &Stylesheet,
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
         match line {
@@ -363,7 +241,7 @@ impl<'a> DisplayList<'a> {
                     DisplayHeaderType::Initial => "-->",
                     DisplayHeaderType::Continuation => ":::",
                 };
-                let lineno_color = self.stylesheet.line_no();
+                let lineno_color = stylesheet.line_no();
 
                 if let Some((col, row)) = pos {
                     format_repeat_char(' ', lineno_width, f)?;
@@ -402,7 +280,7 @@ impl<'a> DisplayList<'a> {
                     if *continuation {
                         format_repeat_char(' ', lineno_width + 3, f)?;
                     } else {
-                        let lineno_color = self.stylesheet.line_no();
+                        let lineno_color = stylesheet.line_no();
                         format_repeat_char(' ', lineno_width, f)?;
                         f.write_char(' ')?;
                         write!(
@@ -414,7 +292,7 @@ impl<'a> DisplayList<'a> {
                         f.write_char(' ')?;
                     }
                 }
-                self.format_annotation(annotation, *continuation, false, f)
+                self.format_annotation(annotation, *continuation, false, stylesheet, f)
             }
         }
     }
@@ -425,6 +303,8 @@ impl<'a> DisplayList<'a> {
         dl: &DisplayLine<'_>,
         lineno_width: usize,
         inline_marks_width: usize,
+        stylesheet: &Stylesheet,
+        anonymized_line_numbers: bool,
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
         match dl {
@@ -432,11 +312,12 @@ impl<'a> DisplayList<'a> {
                 lineno,
                 inline_marks,
                 line,
+                annotations,
             } => {
-                let lineno_color = self.stylesheet.line_no();
-                if self.anonymized_line_numbers && lineno.is_some() {
+                let lineno_color = stylesheet.line_no();
+                if anonymized_line_numbers && lineno.is_some() {
                     write!(f, "{}", lineno_color.render())?;
-                    f.write_str(Self::ANONYMIZED_LINE_NUM)?;
+                    f.write_str(ANONYMIZED_LINE_NUM)?;
                     f.write_str(" |")?;
                     write!(f, "{}", lineno_color.render_reset())?;
                 } else {
@@ -448,15 +329,82 @@ impl<'a> DisplayList<'a> {
                     f.write_str(" |")?;
                     write!(f, "{}", lineno_color.render_reset())?;
                 }
-                if *line != DisplaySourceLine::Empty {
+
+                if let DisplaySourceLine::Content { text, .. } = line {
                     if !inline_marks.is_empty() || 0 < inline_marks_width {
                         f.write_char(' ')?;
-                        self.format_inline_marks(inline_marks, inline_marks_width, f)?;
+                        self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?;
+                    }
+                    f.write_char(' ')?;
+
+                    let text = normalize_whitespace(text);
+                    let line_len = text.as_bytes().len();
+                    let mut left = self.margin.left(line_len);
+                    let right = self.margin.right(line_len);
+
+                    if self.margin.was_cut_left() {
+                        "...".fmt(f)?;
+                        left += 3;
+                    }
+                    // On long lines, we strip the source line, accounting for unicode.
+                    let mut taken = 0;
+                    let code: String = text
+                        .chars()
+                        .skip(left)
+                        .take_while(|ch| {
+                            // Make sure that the trimming on the right will fall within the terminal width.
+                            // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char`
+                            // is. For now, just accept that sometimes the code line will be longer than
+                            // desired.
+                            let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
+                            if taken + next > right - left {
+                                return false;
+                            }
+                            taken += next;
+                            true
+                        })
+                        .collect();
+
+                    if self.margin.was_cut_right(line_len) {
+                        code[..taken.saturating_sub(3)].fmt(f)?;
+                        "...".fmt(f)?;
+                    } else {
+                        code.fmt(f)?;
+                    }
+
+                    let mut left: usize = text
+                        .chars()
+                        .take(left)
+                        .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
+                        .sum();
+
+                    if self.margin.was_cut_left() {
+                        left = left.saturating_sub(3);
+                    }
+
+                    for annotation in annotations {
+                        // Each annotation should be on its own line
+                        f.write_char('\n')?;
+                        // Add the line number and the line number delimiter
+                        write!(f, "{}", stylesheet.line_no.render())?;
+                        format_repeat_char(' ', lineno_width, f)?;
+                        f.write_str(" |")?;
+                        write!(f, "{}", stylesheet.line_no.render_reset())?;
+
+                        if !inline_marks.is_empty() || 0 < inline_marks_width {
+                            f.write_char(' ')?;
+                            self.format_inline_marks(
+                                inline_marks,
+                                inline_marks_width,
+                                stylesheet,
+                                f,
+                            )?;
+                        }
+                        self.format_source_annotation(annotation, left, stylesheet, f)?;
                     }
-                    self.format_source_line(line, f)?;
                 } else if !inline_marks.is_empty() {
                     f.write_char(' ')?;
-                    self.format_inline_marks(inline_marks, inline_marks_width, f)?;
+                    self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?;
                 }
                 Ok(())
             }
@@ -464,11 +412,11 @@ impl<'a> DisplayList<'a> {
                 f.write_str("...")?;
                 if !inline_marks.is_empty() || 0 < inline_marks_width {
                     format_repeat_char(' ', lineno_width, f)?;
-                    self.format_inline_marks(inline_marks, inline_marks_width, f)?;
+                    self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?;
                 }
                 Ok(())
             }
-            DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f),
+            DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, stylesheet, f),
         }
     }
 
@@ -476,11 +424,12 @@ impl<'a> DisplayList<'a> {
         &self,
         inline_marks: &[DisplayMark],
         inline_marks_width: usize,
+        stylesheet: &Stylesheet,
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
         format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?;
         for mark in inline_marks {
-            let annotation_style = self.get_annotation_style(&mark.annotation_type);
+            let annotation_style = get_annotation_style(&mark.annotation_type, stylesheet);
             write!(f, "{}", annotation_style.render())?;
             f.write_char(match mark.mark_type {
                 DisplayMarkType::AnnotationThrough => '|',
@@ -490,6 +439,56 @@ impl<'a> DisplayList<'a> {
         }
         Ok(())
     }
+
+    fn format_source_annotation(
+        &self,
+        annotation: &DisplaySourceAnnotation<'_>,
+        left: usize,
+        stylesheet: &Stylesheet,
+        f: &mut fmt::Formatter<'_>,
+    ) -> fmt::Result {
+        let indent_char = match annotation.annotation_part {
+            DisplayAnnotationPart::Standalone => ' ',
+            DisplayAnnotationPart::LabelContinuation => ' ',
+            DisplayAnnotationPart::MultilineStart => '_',
+            DisplayAnnotationPart::MultilineEnd => '_',
+        };
+        let mark = match annotation.annotation_type {
+            DisplayAnnotationType::Error => '^',
+            DisplayAnnotationType::Warning => '-',
+            DisplayAnnotationType::Info => '-',
+            DisplayAnnotationType::Note => '-',
+            DisplayAnnotationType::Help => '-',
+            DisplayAnnotationType::None => ' ',
+        };
+        let color = get_annotation_style(&annotation.annotation_type, stylesheet);
+        let range = (
+            annotation.range.0.saturating_sub(left),
+            annotation.range.1.saturating_sub(left),
+        );
+        let indent_length = match annotation.annotation_part {
+            DisplayAnnotationPart::LabelContinuation => range.1,
+            _ => range.0,
+        };
+        write!(f, "{}", color.render())?;
+        format_repeat_char(indent_char, indent_length + 1, f)?;
+        format_repeat_char(mark, range.1 - indent_length, f)?;
+        write!(f, "{}", color.render_reset())?;
+
+        if !is_annotation_empty(&annotation.annotation) {
+            f.write_char(' ')?;
+            write!(f, "{}", color.render())?;
+            self.format_annotation(
+                &annotation.annotation,
+                annotation.annotation_part == DisplayAnnotationPart::LabelContinuation,
+                true,
+                stylesheet,
+                f,
+            )?;
+            write!(f, "{}", color.render_reset())?;
+        }
+        Ok(())
+    }
 }
 
 /// Inline annotation which can be used in either Raw or Source line.
@@ -508,6 +507,7 @@ pub enum DisplayLine<'a> {
         lineno: Option<usize>,
         inline_marks: Vec<DisplayMark>,
         line: DisplaySourceLine<'a>,
+        annotations: Vec<DisplaySourceAnnotation<'a>>,
     },
 
     /// A line indicating a folded part of the slice.
@@ -525,19 +525,18 @@ pub enum DisplaySourceLine<'a> {
         text: &'a str,
         range: (usize, usize), // meta information for annotation placement.
     },
-
-    /// An annotation line which is displayed in context of the slice.
-    Annotation {
-        annotation: Annotation<'a>,
-        range: (usize, usize),
-        annotation_type: DisplayAnnotationType,
-        annotation_part: DisplayAnnotationPart,
-    },
-
     /// An empty source line.
     Empty,
 }
 
+#[derive(Debug, PartialEq)]
+pub struct DisplaySourceAnnotation<'a> {
+    pub annotation: Annotation<'a>,
+    pub range: (usize, usize),
+    pub annotation_type: DisplayAnnotationType,
+    pub annotation_part: DisplayAnnotationPart,
+}
+
 /// Raw line - a line which does not have the `lineno` part and is not considered
 /// a part of the snippet.
 #[derive(Debug, PartialEq)]
@@ -704,31 +703,48 @@ fn format_message(
         footer,
         snippets,
     }: snippet::Message<'_>,
-    margin: Option<Margin>,
+    term_width: usize,
+    anonymized_line_numbers: bool,
     primary: bool,
-) -> Vec<DisplayLine<'_>> {
-    let mut body = vec![];
-
-    if !snippets.is_empty() || primary {
-        body.push(format_title(level, id, title));
+) -> Vec<DisplaySet<'_>> {
+    let mut sets = vec![];
+    let body = if !snippets.is_empty() || primary {
+        vec![format_title(level, id, title)]
     } else {
-        body.extend(format_footer(level, id, title));
-    }
+        format_footer(level, id, title)
+    };
 
     for (idx, snippet) in snippets.into_iter().enumerate() {
-        body.extend(format_snippet(
+        sets.push(format_snippet(
             snippet,
             idx == 0,
             !footer.is_empty(),
-            margin,
+            term_width,
+            anonymized_line_numbers,
         ));
     }
 
+    if let Some(first) = sets.first_mut() {
+        body.into_iter().for_each(|line| {
+            first.display_lines.insert(0, line);
+        });
+    } else {
+        sets.push(DisplaySet {
+            display_lines: body,
+            margin: Margin::new(0, 0, 0, 0, DEFAULT_TERM_WIDTH, 0),
+        });
+    }
+
     for annotation in footer {
-        body.extend(format_message(annotation, margin, false));
+        sets.extend(format_message(
+            annotation,
+            term_width,
+            anonymized_line_numbers,
+            false,
+        ));
     }
 
-    body
+    sets
 }
 
 fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> {
@@ -782,20 +798,26 @@ fn format_snippet(
     snippet: snippet::Snippet<'_>,
     is_first: bool,
     has_footer: bool,
-    margin: Option<Margin>,
-) -> Vec<DisplayLine<'_>> {
+    term_width: usize,
+    anonymized_line_numbers: bool,
+) -> DisplaySet<'_> {
     let main_range = snippet.annotations.first().map(|x| x.range.start);
     let origin = snippet.origin;
     let need_empty_header = origin.is_some() || is_first;
-    let body = format_body(snippet, need_empty_header, has_footer, margin);
-    let header = format_header(origin, main_range, &body, is_first);
-    let mut result = vec![];
+    let mut body = format_body(
+        snippet,
+        need_empty_header,
+        has_footer,
+        term_width,
+        anonymized_line_numbers,
+    );
+    let header = format_header(origin, main_range, &body.display_lines, is_first);
 
     if let Some(header) = header {
-        result.push(header);
+        body.display_lines.insert(0, header);
     }
-    result.extend(body);
-    result
+
+    body
 }
 
 #[inline]
@@ -862,49 +884,54 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
 
     let mut lines = vec![];
     let mut no_annotation_lines_counter = 0;
-
     for (idx, line) in body.iter().enumerate() {
         match line {
             DisplayLine::Source {
-                line: DisplaySourceLine::Annotation { .. },
+                line: DisplaySourceLine::Content { .. },
+                annotations,
                 ..
             } => {
-                let fold_start = idx - no_annotation_lines_counter;
-                if no_annotation_lines_counter > 2 {
-                    let fold_end = idx;
-                    let pre_len = if no_annotation_lines_counter > 8 {
-                        4
-                    } else {
-                        0
-                    };
-                    let post_len = if no_annotation_lines_counter > 8 {
-                        2
-                    } else {
-                        1
-                    };
-                    for (i, _) in body
-                        .iter()
-                        .enumerate()
-                        .take(fold_start + pre_len)
-                        .skip(fold_start)
-                    {
-                        lines.push(Line::Source(i));
-                    }
-                    lines.push(Line::Fold(idx));
-                    for (i, _) in body
-                        .iter()
-                        .enumerate()
-                        .take(fold_end)
-                        .skip(fold_end - post_len)
-                    {
-                        lines.push(Line::Source(i));
-                    }
+                if annotations.is_empty() {
+                    no_annotation_lines_counter += 1;
+                    continue;
                 } else {
-                    for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) {
-                        lines.push(Line::Source(i));
+                    let fold_start = idx - no_annotation_lines_counter;
+                    if no_annotation_lines_counter >= 2 {
+                        let fold_end = idx;
+                        let pre_len = if no_annotation_lines_counter > 8 {
+                            4
+                        } else {
+                            0
+                        };
+                        let post_len = if no_annotation_lines_counter > 8 {
+                            2
+                        } else {
+                            1
+                        };
+                        for (i, _) in body
+                            .iter()
+                            .enumerate()
+                            .take(fold_start + pre_len)
+                            .skip(fold_start)
+                        {
+                            lines.push(Line::Source(i));
+                        }
+                        lines.push(Line::Fold(idx));
+                        for (i, _) in body
+                            .iter()
+                            .enumerate()
+                            .take(fold_end)
+                            .skip(fold_end + 1 - post_len)
+                        {
+                            lines.push(Line::Source(i));
+                        }
+                    } else {
+                        for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) {
+                            lines.push(Line::Source(i));
+                        }
                     }
+                    no_annotation_lines_counter = 0;
                 }
-                no_annotation_lines_counter = 0;
             }
             DisplayLine::Source { .. } => {
                 no_annotation_lines_counter += 1;
@@ -927,14 +954,19 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
             }
             Line::Fold(i) => {
                 if let DisplayLine::Source {
-                    line: DisplaySourceLine::Annotation { .. },
+                    line: DisplaySourceLine::Content { .. },
                     ref inline_marks,
+                    ref annotations,
                     ..
                 } = body.get(i - removed).unwrap()
                 {
-                    new_body.push(DisplayLine::Fold {
-                        inline_marks: inline_marks.clone(),
-                    })
+                    if !annotations.is_empty() {
+                        new_body.push(DisplayLine::Fold {
+                            inline_marks: inline_marks.clone(),
+                        });
+                    } else {
+                        unreachable!()
+                    }
                 } else {
                     unreachable!()
                 }
@@ -949,8 +981,9 @@ fn format_body(
     snippet: snippet::Snippet<'_>,
     need_empty_header: bool,
     has_footer: bool,
-    margin: Option<Margin>,
-) -> Vec<DisplayLine<'_>> {
+    term_width: usize,
+    anonymized_line_numbers: bool,
+) -> DisplaySet<'_> {
     let source_len = snippet.source.len();
     if let Some(bigger) = snippet.annotations.iter().find_map(|x| {
         // Allow highlighting one past the last character in the source.
@@ -970,7 +1003,12 @@ fn format_body(
     let mut current_line = snippet.line_start;
     let mut current_index = 0;
 
-    let mut annotation_line_count = 0;
+    let mut whitespace_margin = usize::MAX;
+    let mut span_left_margin = usize::MAX;
+    let mut span_right_margin = 0;
+    let mut label_right_margin = 0;
+    let mut max_line_len = 0;
+
     let mut annotations = snippet.annotations;
     for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() {
         let line_length: usize = line.len();
@@ -982,62 +1020,80 @@ fn format_body(
                 text: line,
                 range: line_range,
             },
+            annotations: vec![],
         });
+
+        let leading_whitespace = line
+            .chars()
+            .take_while(|c| c.is_whitespace())
+            .map(|c| {
+                match c {
+                    // Tabs are displayed as 4 spaces
+                    '\t' => 4,
+                    _ => 1,
+                }
+            })
+            .sum();
+        if line.chars().any(|c| !c.is_whitespace()) {
+            whitespace_margin = min(whitespace_margin, leading_whitespace);
+        }
+        max_line_len = max(max_line_len, line_length);
+
         let line_start_index = line_range.0;
         let line_end_index = line_range.1;
         current_line += 1;
         current_index += line_length + end_line as usize;
 
-        let margin_left = margin
-            .map(|m| m.left(line_end_index - line_start_index))
-            .unwrap_or_default();
         // It would be nice to use filter_drain here once it's stable.
         annotations.retain(|annotation| {
-            let body_idx = idx + annotation_line_count;
+            let body_idx = idx;
             let annotation_type = match annotation.level {
                 snippet::Level::Error => DisplayAnnotationType::None,
                 snippet::Level::Warning => DisplayAnnotationType::None,
                 _ => DisplayAnnotationType::from(annotation.level),
             };
+            let label_right = annotation.label.map_or(0, |label| label.len() + 1);
             match annotation.range {
                 Range { start, .. } if start > line_end_index => true,
                 Range { start, end }
                     if start >= line_start_index && end <= line_end_index
                         || start == line_end_index && end - start <= 1 =>
                 {
-                    let annotation_start_col = line[0..(start - line_start_index)]
-                        .chars()
-                        .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                        .sum::<usize>()
-                        - margin_left;
-                    // This allows for annotations to be placed one past the
-                    // last character
-                    let safe_end = (end - line_start_index).saturating_sub(line_length);
-                    let annotation_end_col = line[0..(end - line_start_index) - safe_end]
-                        .chars()
-                        .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                        .sum::<usize>()
-                        + safe_end
-                        - margin_left;
-                    let range = (annotation_start_col, annotation_end_col);
-                    body.insert(
-                        body_idx + 1,
-                        DisplayLine::Source {
-                            lineno: None,
-                            inline_marks: vec![],
-                            line: DisplaySourceLine::Annotation {
-                                annotation: Annotation {
-                                    annotation_type,
-                                    id: None,
-                                    label: format_label(annotation.label, None),
-                                },
-                                range,
-                                annotation_type: DisplayAnnotationType::from(annotation.level),
-                                annotation_part: DisplayAnnotationPart::Standalone,
+                    if let DisplayLine::Source {
+                        ref mut annotations,
+                        ..
+                    } = body[body_idx]
+                    {
+                        let annotation_start_col = line[0..(start - line_start_index)]
+                            .chars()
+                            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
+                            .sum::<usize>();
+                        // This allows for annotations to be placed one past the
+                        // last character
+                        let safe_end = (end - line_start_index).saturating_sub(line_length);
+                        let annotation_end_col = line[0..(end - line_start_index) - safe_end]
+                            .chars()
+                            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
+                            .sum::<usize>()
+                            + safe_end;
+
+                        span_left_margin = min(span_left_margin, annotation_start_col);
+                        span_right_margin = max(span_right_margin, annotation_end_col);
+                        label_right_margin =
+                            max(label_right_margin, annotation_end_col + label_right);
+
+                        let range = (annotation_start_col, annotation_end_col);
+                        annotations.push(DisplaySourceAnnotation {
+                            annotation: Annotation {
+                                annotation_type,
+                                id: None,
+                                label: format_label(annotation.label, None),
                             },
-                        },
-                    );
-                    annotation_line_count += 1;
+                            range,
+                            annotation_type: DisplayAnnotationType::from(annotation.level),
+                            annotation_part: DisplayAnnotationPart::Standalone,
+                        });
+                    }
                     false
                 }
                 Range { start, end }
@@ -1056,30 +1112,33 @@ fn format_body(
                                 annotation_type: DisplayAnnotationType::from(annotation.level),
                             });
                         }
-                    } else {
+                    } else if let DisplayLine::Source {
+                        ref mut annotations,
+                        ..
+                    } = body[body_idx]
+                    {
                         let annotation_start_col = line[0..(start - line_start_index)]
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
                             .sum::<usize>();
-                        let range = (annotation_start_col, annotation_start_col + 1);
-                        body.insert(
-                            body_idx + 1,
-                            DisplayLine::Source {
-                                lineno: None,
-                                inline_marks: vec![],
-                                line: DisplaySourceLine::Annotation {
-                                    annotation: Annotation {
-                                        annotation_type: DisplayAnnotationType::None,
-                                        id: None,
-                                        label: vec![],
-                                    },
-                                    range,
-                                    annotation_type: DisplayAnnotationType::from(annotation.level),
-                                    annotation_part: DisplayAnnotationPart::MultilineStart,
-                                },
+                        let annotation_end_col = annotation_start_col + 1;
+
+                        span_left_margin = min(span_left_margin, annotation_start_col);
+                        span_right_margin = max(span_right_margin, annotation_end_col);
+                        label_right_margin =
+                            max(label_right_margin, annotation_end_col + label_right);
+
+                        let range = (annotation_start_col, annotation_end_col);
+                        annotations.push(DisplaySourceAnnotation {
+                            annotation: Annotation {
+                                annotation_type,
+                                id: None,
+                                label: vec![],
                             },
-                        );
-                        annotation_line_count += 1;
+                            range,
+                            annotation_type: DisplayAnnotationType::from(annotation.level),
+                            annotation_part: DisplayAnnotationPart::MultilineStart,
+                        });
                     }
                     true
                 }
@@ -1103,6 +1162,7 @@ fn format_body(
                 {
                     if let DisplayLine::Source {
                         ref mut inline_marks,
+                        ref mut annotations,
                         ..
                     } = body[body_idx]
                     {
@@ -1110,35 +1170,30 @@ fn format_body(
                             mark_type: DisplayMarkType::AnnotationThrough,
                             annotation_type: DisplayAnnotationType::from(annotation.level),
                         });
-                    }
+                        let end_mark = line[0..(end - line_start_index)]
+                            .chars()
+                            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
+                            .sum::<usize>()
+                            .saturating_sub(1);
 
-                    let end_mark = line[0..(end - line_start_index)]
-                        .chars()
-                        .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                        .sum::<usize>()
-                        .saturating_sub(1);
-                    let range = (end_mark - margin_left, (end_mark + 1) - margin_left);
-                    body.insert(
-                        body_idx + 1,
-                        DisplayLine::Source {
-                            lineno: None,
-                            inline_marks: vec![DisplayMark {
-                                mark_type: DisplayMarkType::AnnotationThrough,
-                                annotation_type: DisplayAnnotationType::from(annotation.level),
-                            }],
-                            line: DisplaySourceLine::Annotation {
-                                annotation: Annotation {
-                                    annotation_type,
-                                    id: None,
-                                    label: format_label(annotation.label, None),
-                                },
-                                range,
-                                annotation_type: DisplayAnnotationType::from(annotation.level),
-                                annotation_part: DisplayAnnotationPart::MultilineEnd,
+                        let end_plus_one = end_mark + 1;
+
+                        span_left_margin = min(span_left_margin, end_mark);
+                        span_right_margin = max(span_right_margin, end_plus_one);
+                        label_right_margin = max(label_right_margin, end_plus_one + label_right);
+
+                        let range = (end_mark, end_plus_one);
+                        annotations.push(DisplaySourceAnnotation {
+                            annotation: Annotation {
+                                annotation_type,
+                                id: None,
+                                label: format_label(annotation.label, None),
                             },
-                        },
-                    );
-                    annotation_line_count += 1;
+                            range,
+                            annotation_type: DisplayAnnotationType::from(annotation.level),
+                            annotation_part: DisplayAnnotationPart::MultilineEnd,
+                        });
+                    }
                     false
                 }
                 _ => true,
@@ -1157,6 +1212,7 @@ fn format_body(
                 lineno: None,
                 inline_marks: vec![],
                 line: DisplaySourceLine::Empty,
+                annotations: vec![],
             },
         );
     }
@@ -1166,15 +1222,41 @@ fn format_body(
             lineno: None,
             inline_marks: vec![],
             line: DisplaySourceLine::Empty,
+            annotations: vec![],
         });
     } else if let Some(DisplayLine::Source { .. }) = body.last() {
         body.push(DisplayLine::Source {
             lineno: None,
             inline_marks: vec![],
             line: DisplaySourceLine::Empty,
+            annotations: vec![],
         });
     }
-    body
+    let max_line_num_len = if anonymized_line_numbers {
+        ANONYMIZED_LINE_NUM.len()
+    } else {
+        current_line.to_string().len()
+    };
+
+    let width_offset = 3 + max_line_num_len;
+
+    if span_left_margin == usize::MAX {
+        span_left_margin = 0;
+    }
+
+    let margin = Margin::new(
+        whitespace_margin,
+        span_left_margin,
+        span_right_margin,
+        label_right_margin,
+        term_width.saturating_sub(width_offset),
+        max_line_len,
+    );
+
+    DisplaySet {
+        display_lines: body,
+        margin,
+    }
 }
 
 fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -1184,6 +1266,46 @@ fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Res
     Ok(())
 }
 
+#[inline]
+fn format_annotation_type(
+    annotation_type: &DisplayAnnotationType,
+    f: &mut fmt::Formatter<'_>,
+) -> fmt::Result {
+    match annotation_type {
+        DisplayAnnotationType::Error => f.write_str(ERROR_TXT),
+        DisplayAnnotationType::Help => f.write_str(HELP_TXT),
+        DisplayAnnotationType::Info => f.write_str(INFO_TXT),
+        DisplayAnnotationType::Note => f.write_str(NOTE_TXT),
+        DisplayAnnotationType::Warning => f.write_str(WARNING_TXT),
+        DisplayAnnotationType::None => Ok(()),
+    }
+}
+
+fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize {
+    match annotation_type {
+        DisplayAnnotationType::Error => ERROR_TXT.len(),
+        DisplayAnnotationType::Help => HELP_TXT.len(),
+        DisplayAnnotationType::Info => INFO_TXT.len(),
+        DisplayAnnotationType::Note => NOTE_TXT.len(),
+        DisplayAnnotationType::Warning => WARNING_TXT.len(),
+        DisplayAnnotationType::None => 0,
+    }
+}
+
+fn get_annotation_style<'a>(
+    annotation_type: &DisplayAnnotationType,
+    stylesheet: &'a Stylesheet,
+) -> &'a Style {
+    match annotation_type {
+        DisplayAnnotationType::Error => stylesheet.error(),
+        DisplayAnnotationType::Warning => stylesheet.warning(),
+        DisplayAnnotationType::Info => stylesheet.info(),
+        DisplayAnnotationType::Note => stylesheet.note(),
+        DisplayAnnotationType::Help => stylesheet.help(),
+        DisplayAnnotationType::None => stylesheet.none(),
+    }
+}
+
 #[inline]
 fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
     annotation
@@ -1191,3 +1313,26 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
         .iter()
         .all(|fragment| fragment.content.is_empty())
 }
+
+// We replace some characters so the CLI output is always consistent and underlines aligned.
+const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
+    ('\t', "    "),   // We do our own tab replacement
+    ('\u{200D}', ""), // Replace ZWJ with nothing for consistent terminal output of grapheme clusters.
+    ('\u{202A}', ""), // The following unicode text flow control characters are inconsistently
+    ('\u{202B}', ""), // supported across CLIs and can cause confusion due to the bytes on disk
+    ('\u{202D}', ""), // not corresponding to the visible source code, so we replace them always.
+    ('\u{202E}', ""),
+    ('\u{2066}', ""),
+    ('\u{2067}', ""),
+    ('\u{2068}', ""),
+    ('\u{202C}', ""),
+    ('\u{2069}', ""),
+];
+
+fn normalize_whitespace(str: &str) -> String {
+    let mut s = str.to_string();
+    for (c, replacement) in OUTPUT_REPLACEMENTS {
+        s = s.replace(*c, replacement);
+    }
+    s
+}
diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs
index 361f5f3..3f1b28b 100644
--- a/src/renderer/margin.rs
+++ b/src/renderer/margin.rs
@@ -4,7 +4,7 @@ const ELLIPSIS_PASSING: usize = 6;
 const LONG_WHITESPACE: usize = 20;
 const LONG_WHITESPACE_PADDING: usize = 4;
 
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq)]
 pub struct Margin {
     /// The available whitespace in the left that can be consumed when centering.
     whitespace_left: usize,
@@ -17,7 +17,7 @@ pub struct Margin {
     /// The end of the line to be displayed.
     computed_right: usize,
     /// The current width of the terminal. 140 by default and in tests.
-    column_width: usize,
+    term_width: usize,
     /// The end column of a span label, including the span. Doesn't account for labels not in the
     /// same line as the span.
     label_right: usize,
@@ -29,7 +29,7 @@ impl Margin {
         span_left: usize,
         span_right: usize,
         label_right: usize,
-        column_width: usize,
+        term_width: usize,
         max_line_len: usize,
     ) -> Self {
         // The 6 is padding to give a bit of room for `...` when displaying:
@@ -47,7 +47,7 @@ impl Margin {
             span_right: span_right + ELLIPSIS_PASSING,
             computed_left: 0,
             computed_right: 0,
-            column_width,
+            term_width,
             label_right: label_right + ELLIPSIS_PASSING,
         };
         m.compute(max_line_len);
@@ -67,7 +67,7 @@ impl Margin {
             } else {
                 self.computed_right
             };
-        right < line_len && self.computed_left + self.column_width < line_len
+        right < line_len && self.computed_left + self.term_width < line_len
     }
 
     fn compute(&mut self, max_line_len: usize) {
@@ -81,22 +81,22 @@ impl Margin {
         // relevant code.
         self.computed_right = max(max_line_len, self.computed_left);
 
-        if self.computed_right - self.computed_left > self.column_width {
+        if self.computed_right - self.computed_left > self.term_width {
             // Trimming only whitespace isn't enough, let's get craftier.
-            if self.label_right - self.whitespace_left <= self.column_width {
+            if self.label_right - self.whitespace_left <= self.term_width {
                 // Attempt to fit the code window only trimming whitespace.
                 self.computed_left = self.whitespace_left;
-                self.computed_right = self.computed_left + self.column_width;
-            } else if self.label_right - self.span_left <= self.column_width {
+                self.computed_right = self.computed_left + self.term_width;
+            } else if self.label_right - self.span_left <= self.term_width {
                 // Attempt to fit the code window considering only the spans and labels.
-                let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
+                let padding_left = (self.term_width - (self.label_right - self.span_left)) / 2;
                 self.computed_left = self.span_left.saturating_sub(padding_left);
-                self.computed_right = self.computed_left + self.column_width;
-            } else if self.span_right - self.span_left <= self.column_width {
+                self.computed_right = self.computed_left + self.term_width;
+            } else if self.span_right - self.span_left <= self.term_width {
                 // Attempt to fit the code window considering the spans and labels plus padding.
-                let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
+                let padding_left = (self.term_width - (self.span_right - self.span_left)) / 5 * 2;
                 self.computed_left = self.span_left.saturating_sub(padding_left);
-                self.computed_right = self.computed_left + self.column_width;
+                self.computed_right = self.computed_left + self.term_width;
             } else {
                 // Mostly give up but still don't show the full line.
                 self.computed_left = self.span_left;
@@ -110,7 +110,7 @@ impl Margin {
     }
 
     pub(crate) fn right(&self, line_len: usize) -> usize {
-        if line_len.saturating_sub(self.computed_left) <= self.column_width {
+        if line_len.saturating_sub(self.computed_left) <= self.term_width {
             line_len
         } else {
             min(line_len, self.computed_right)
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 5f9394d..ee63da9 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -17,15 +17,17 @@ pub(crate) mod stylesheet;
 use crate::snippet::Message;
 pub use anstyle::*;
 use display_list::DisplayList;
-pub use margin::Margin;
+use margin::Margin;
 use std::fmt::Display;
 use stylesheet::Stylesheet;
 
+pub const DEFAULT_TERM_WIDTH: usize = 140;
+
 /// A renderer for [`Message`]s
 #[derive(Clone)]
 pub struct Renderer {
     anonymized_line_numbers: bool,
-    margin: Option<Margin>,
+    term_width: usize,
     stylesheet: Stylesheet,
 }
 
@@ -34,7 +36,7 @@ impl Renderer {
     pub const fn plain() -> Self {
         Self {
             anonymized_line_numbers: false,
-            margin: None,
+            term_width: DEFAULT_TERM_WIDTH,
             stylesheet: Stylesheet::plain(),
         }
     }
@@ -94,25 +96,9 @@ impl Renderer {
         self
     }
 
-    /// Set the margin for the output
-    ///
-    /// This controls the various margins of the output.
-    ///
-    /// # Example
-    ///
-    /// ```text
-    /// error: expected type, found `22`
-    ///   --> examples/footer.rs:29:25
-    ///    |
-    /// 26 | ...         annotations: vec![SourceAnnotation {
-    ///    |                               ---------------- info: while parsing this struct
-    /// ...
-    /// 29 | ...         range: <22, 25>,
-    ///    |                     ^^
-    ///    |
-    /// ```
-    pub const fn margin(mut self, margin: Option<Margin>) -> Self {
-        self.margin = margin;
+    // Set the terminal width
+    pub const fn term_width(mut self, term_width: usize) -> Self {
+        self.term_width = term_width;
         self
     }
 
@@ -170,7 +156,7 @@ impl Renderer {
             msg,
             &self.stylesheet,
             self.anonymized_line_numbers,
-            self.margin,
+            self.term_width,
         )
     }
 }


Test patch:
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 165c341..2d1452b 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -1,7 +1,8 @@
 use serde::{Deserialize, Deserializer, Serialize};
 use std::ops::Range;
 
-use annotate_snippets::{renderer::Margin, Annotation, Level, Message, Renderer, Snippet};
+use annotate_snippets::renderer::DEFAULT_TERM_WIDTH;
+use annotate_snippets::{Annotation, Level, Message, Renderer, Snippet};
 
 #[derive(Deserialize)]
 pub struct Fixture<'a> {
@@ -148,55 +149,18 @@ enum LevelDef {
 pub struct RendererDef {
     #[serde(default)]
     anonymized_line_numbers: bool,
-    #[serde(deserialize_with = "deserialize_margin")]
     #[serde(default)]
-    margin: Option<Margin>,
+    term_width: Option<usize>,
 }
 
 impl From<RendererDef> for Renderer {
     fn from(val: RendererDef) -> Self {
         let RendererDef {
             anonymized_line_numbers,
-            margin,
+            term_width,
         } = val;
         Renderer::plain()
             .anonymized_line_numbers(anonymized_line_numbers)
-            .margin(margin)
+            .term_width(term_width.unwrap_or(DEFAULT_TERM_WIDTH))
     }
 }
-
-fn deserialize_margin<'de, D>(deserializer: D) -> Result<Option<Margin>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    #[derive(Deserialize)]
-    struct Wrapper {
-        whitespace_left: usize,
-        span_left: usize,
-        span_right: usize,
-        label_right: usize,
-        column_width: usize,
-        max_line_len: usize,
-    }
-
-    Option::<Wrapper>::deserialize(deserializer).map(|opt_wrapped: Option<Wrapper>| {
-        opt_wrapped.map(|wrapped: Wrapper| {
-            let Wrapper {
-                whitespace_left,
-                span_left,
-                span_right,
-                label_right,
-                column_width,
-                max_line_len,
-            } = wrapped;
-            Margin::new(
-                whitespace_left,
-                span_left,
-                span_right,
-                label_right,
-                column_width,
-                max_line_len,
-            )
-        })
-    })
-}
diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml
index d44024b..459cbe1 100644
--- a/tests/fixtures/no-color/strip_line.toml
+++ b/tests/fixtures/no-color/strip_line.toml
@@ -16,10 +16,3 @@ range = [192, 194]
 [renderer]
 color = false
 anonymized_line_numbers = true
-[renderer.margin]
-whitespace_left = 180
-span_left = 192
-span_right = 194
-label_right = 221
-column_width = 140
-max_line_len = 195
diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml
index e3f7482..dedefd5 100644
--- a/tests/fixtures/no-color/strip_line_char.toml
+++ b/tests/fixtures/no-color/strip_line_char.toml
@@ -16,10 +16,3 @@ range = [192, 194]
 [renderer]
 color = false
 anonymized_line_numbers = true
-[renderer.margin]
-whitespace_left = 180
-span_left = 192
-span_right = 194
-label_right = 221
-column_width = 140
-max_line_len = 195
diff --git a/tests/fixtures/no-color/strip_line_non_ws.svg b/tests/fixtures/no-color/strip_line_non_ws.svg
index 6a72e7c..2be3890 100644
--- a/tests/fixtures/no-color/strip_line_non_ws.svg
+++ b/tests/fixtures/no-color/strip_line_non_ws.svg
@@ -1,4 +1,4 @@
-<svg width="1238px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="1196px" height="146px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -18,15 +18,17 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  --&gt; $DIR/non-whitespace-trimming.rs:4:241</tspan>
+    <tspan x="10px" y="46px"><tspan>  --&gt; $DIR/non-whitespace-trimming.rs:4:242</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   |</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();...</tspan>
+    <tspan x="10px" y="82px"><tspan>LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () ...</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   |                                                       ^^ expected (), found integer</tspan>
+    <tspan x="10px" y="100px"><tspan>   |                                                       ^^ expected `()`, found integer</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   |</tspan>
+    <tspan x="10px" y="118px"><tspan>   |                                                  ^^ expected due to this</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>   |</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml
index 2985177..06ecad8 100644
--- a/tests/fixtures/no-color/strip_line_non_ws.toml
+++ b/tests/fixtures/no-color/strip_line_non_ws.toml
@@ -4,21 +4,22 @@ id = "E0308"
 title = "mismatched types"
 
 [[message.snippets]]
-source = "    let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();"
+source = """
+	let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();
+"""
 line_start = 4
 origin = "$DIR/non-whitespace-trimming.rs"
 
 [[message.snippets.annotations]]
-label = "expected (), found integer"
+label = "expected `()`, found integer"
 level = "Error"
-range = [240, 242]
+range = [241, 243]
+
+[[message.snippets.annotations]]
+label = "expected due to this"
+level = "Error"
+range = [236, 238]
+
 
 [renderer]
 anonymized_line_numbers = true
-[renderer.margin]
-whitespace_left = 4
-span_left = 240
-span_right = 242
-label_right = 271
-column_width = 140
-max_line_len = 371