From e7b7555d1516d0b274e7269961fce9ec9b30bc98 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 14:33:22 -0500
Subject: [PATCH 001/302] chore: First step

---
 .cargo/config                    |   5 +
 .clippy.toml                     |  12 ++
 .github/renovate.json5           |  71 +++++++++++
 .github/settings.yml             |  52 ++++++++
 .github/workflows/audit.yml      |  49 ++++++++
 .github/workflows/ci.yml         | 128 ++++++++++++++++++++
 .github/workflows/committed.yml  |  24 ++++
 .github/workflows/pre-commit.yml |  23 ++++
 .github/workflows/rust-next.yml  |  88 ++++++++++++++
 .github/workflows/spelling.yml   |  21 ++++
 .gitignore                       |   1 +
 .pre-commit-config.yaml          |  26 ++++
 CHANGELOG.md                     |  11 ++
 CONTRIBUTING.md                  |  70 +++++++++++
 Cargo.lock                       |   7 ++
 Cargo.toml                       |  38 ++++++
 LICENSE-APACHE                   | 202 +++++++++++++++++++++++++++++++
 LICENSE-MIT                      |  19 +++
 README.md                        |  26 ++++
 committed.toml                   |   3 +
 deny.toml                        | 135 +++++++++++++++++++++
 release.toml                     |   6 +
 src/lib.rs                       |   2 +
 23 files changed, 1019 insertions(+)
 create mode 100644 .cargo/config
 create mode 100644 .clippy.toml
 create mode 100644 .github/renovate.json5
 create mode 100644 .github/settings.yml
 create mode 100644 .github/workflows/audit.yml
 create mode 100644 .github/workflows/ci.yml
 create mode 100644 .github/workflows/committed.yml
 create mode 100644 .github/workflows/pre-commit.yml
 create mode 100644 .github/workflows/rust-next.yml
 create mode 100644 .github/workflows/spelling.yml
 create mode 100644 .gitignore
 create mode 100644 .pre-commit-config.yaml
 create mode 100644 CHANGELOG.md
 create mode 100644 CONTRIBUTING.md
 create mode 100644 Cargo.lock
 create mode 100644 Cargo.toml
 create mode 100644 LICENSE-APACHE
 create mode 100644 LICENSE-MIT
 create mode 100644 README.md
 create mode 100644 committed.toml
 create mode 100644 deny.toml
 create mode 100644 release.toml
 create mode 100644 src/lib.rs

diff --git a/.cargo/config b/.cargo/config
new file mode 100644
index 0000000..ba32123
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,5 @@
+[target.x86_64-pc-windows-msvc]
+rustflags = ["-Ctarget-feature=+crt-static"]
+
+[target.i686-pc-windows-msvc]
+rustflags = ["-Ctarget-feature=+crt-static"]
diff --git a/.clippy.toml b/.clippy.toml
new file mode 100644
index 0000000..16749ab
--- /dev/null
+++ b/.clippy.toml
@@ -0,0 +1,12 @@
+msrv = "1.64.0"  # MSRV
+warn-on-all-wildcard-imports = true
+allow-expect-in-tests = true
+allow-unwrap-in-tests = true
+allow-dbg-in-tests = true
+allow-print-in-tests = true
+disallowed-methods = [
+    { path = "std::option::Option::map_or", reason = "use `map(..).unwrap_or(..)`" },
+    { path = "std::option::Option::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" },
+    { path = "std::result::Result::map_or", reason = "use `map(..).unwrap_or(..)`" },
+    { path = "std::result::Result::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" },
+]
diff --git a/.github/renovate.json5 b/.github/renovate.json5
new file mode 100644
index 0000000..51faa75
--- /dev/null
+++ b/.github/renovate.json5
@@ -0,0 +1,71 @@
+{
+  "schedule": [
+    "before 3am on the first day of the month"
+  ],
+  "semanticCommits": "enabled",
+  "configMigration": true,
+  "dependencyDashboard": true,
+  "regexManagers": [
+    {
+      "fileMatch": [
+        "^rust-toolchain\\.toml$",
+        "Cargo.toml$",
+        "clippy.toml$",
+        "\.clippy.toml$",
+        "^\.github/workflows/ci.yml$",
+        "^\.github/workflows/rust-next.yml$",
+      ],
+      "matchStrings": [
+        "MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)",
+        "(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV",
+      ],
+      "depNameTemplate": "rust",
+      "packageNameTemplate": "rust-lang/rust",
+      "datasourceTemplate": "github-releases",
+    }
+  ],
+  "packageRules": [
+    {
+      "commitMessageTopic": "MSRV",
+      "matchManagers": ["regex"],
+      "matchPackageNames": ["rust"],
+      "stabilityDays": 126,  // 3 releases * 6 weeks per release * 7 days per week
+    },
+    // Goals:
+    // - Keep version reqs low, ignoring compatible normal/build dependencies
+    // - Take advantage of latest dev-dependencies
+    // - Rollup safe upgrades to reduce CI runner load
+    // - Help keep number of versions down by always using latest breaking change
+    // - Have lockfile and manifest in-sync
+    {
+      "matchManagers": ["cargo"],
+      "matchDepTypes": ["build-dependencies", "dependencies"],
+      "matchCurrentVersion": ">=0.1.0",
+      "matchUpdateTypes": ["patch"],
+      "enabled": false,
+    },
+    {
+      "matchManagers": ["cargo"],
+      "matchDepTypes": ["build-dependencies", "dependencies"],
+      "matchCurrentVersion": ">=1.0.0",
+      "matchUpdateTypes": ["minor"],
+      "enabled": false,
+    },
+    {
+      "matchManagers": ["cargo"],
+      "matchDepTypes": ["dev-dependencies"],
+      "matchCurrentVersion": ">=0.1.0",
+      "matchUpdateTypes": ["patch"],
+      "automerge": true,
+      "groupName": "compatible (dev)",
+    },
+    {
+      "matchManagers": ["cargo"],
+      "matchDepTypes": ["dev-dependencies"],
+      "matchCurrentVersion": ">=1.0.0",
+      "matchUpdateTypes": ["minor"],
+      "automerge": true,
+      "groupName": "compatible (dev)",
+    },
+  ],
+}
diff --git a/.github/settings.yml b/.github/settings.yml
new file mode 100644
index 0000000..0469378
--- /dev/null
+++ b/.github/settings.yml
@@ -0,0 +1,52 @@
+# These settings are synced to GitHub by https://probot.github.io/apps/settings/
+
+repository:
+  description: "DESCRIPTION"
+  homepage: "https://docs.rs/PROJECT"
+  topics: ""
+  has_issues: true
+  has_projects: false
+  has_wiki: false
+  has_downloads: true
+  default_branch: main
+
+  allow_squash_merge: true
+  allow_merge_commit: true
+  allow_rebase_merge: true
+
+  allow_auto_merge: true
+  delete_branch_on_merge: true
+
+  squash_merge_commit_title: "PR_TITLE"
+  squash_merge_commit_message: "PR_BODY"
+  merge_commit_message: "PR_BODY"
+
+labels:
+  # Type
+  - name: bug
+    color: '#b60205'
+    description: Not as expected
+  - name: enhancement
+    color: '#1d76db'
+    description: Improve the expected
+  # Flavor
+  - name: question
+    color: "#cc317c"
+    description: Uncertainty is involved
+  - name: breaking-change
+    color: "#e99695"
+  - name: good first issue
+    color: '#c2e0c6'
+    description: Help wanted!
+
+branches:
+  - name: main
+    protection:
+      required_pull_request_reviews: null
+      required_conversation_resolution: true
+      required_status_checks:
+        # Required. Require branches to be up to date before merging.
+        strict: false
+        contexts: ["CI", "Lint Commits", "Spell Check with Typos"]
+      enforce_admins: false
+      restrictions: null
diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
new file mode 100644
index 0000000..5b7e83a
--- /dev/null
+++ b/.github/workflows/audit.yml
@@ -0,0 +1,49 @@
+name: Security audit
+
+permissions:
+  contents: read
+
+on:
+  pull_request:
+    paths:
+      - '**/Cargo.toml'
+      - '**/Cargo.lock'
+  push:
+    branches:
+    - main
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  security_audit:
+    permissions:
+      issues: write # to create issues (actions-rs/audit-check)
+      checks: write # to create check (actions-rs/audit-check)
+    runs-on: ubuntu-latest
+    # Prevent sudden announcement of a new advisory from failing ci:
+    continue-on-error: true
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - uses: actions-rs/audit-check@v1
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+
+  cargo_deny:
+    permissions:
+      issues: write # to create issues (actions-rs/audit-check)
+      checks: write # to create check (actions-rs/audit-check)
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        checks:
+          - bans licenses sources
+    steps:
+    - uses: actions/checkout@v3
+    - uses: EmbarkStudios/cargo-deny-action@v1
+      with:
+        command: check ${{ matrix.checks }}
+        rust-version: stable
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..783247c
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,128 @@
+name: CI
+
+permissions:
+  contents: read
+
+on:
+  pull_request:
+  push:
+    branches:
+    - main
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  ci:
+    permissions:
+      contents: none
+    name: CI
+    needs: [test, msrv, docs, rustfmt, clippy]
+    runs-on: ubuntu-latest
+    steps:
+      - name: Done
+        run: exit 0
+  test:
+    name: Test
+    strategy:
+      matrix:
+        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
+        rust: ["stable"]
+    continue-on-error: ${{ matrix.rust != 'stable' }}
+    runs-on: ${{ matrix.os }}
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: ${{ matrix.rust }}
+    - uses: Swatinem/rust-cache@v2
+    - name: Build
+      run: cargo test --no-run --workspace --all-features
+    - name: Default features
+      run: cargo test --workspace
+    - name: All features
+      run: cargo test --workspace --all-features
+    - name: No-default features
+      run: cargo test --workspace --no-default-features
+  msrv:
+    name: "Check MSRV: 1.64.0"
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: 1.64.0  # MSRV
+    - uses: Swatinem/rust-cache@v2
+    - name: Default features
+      run: cargo check --workspace --all-targets
+    - name: All features
+      run: cargo check --workspace --all-targets --all-features
+    - name: No-default features
+      run: cargo check --workspace --all-targets --no-default-features
+  docs:
+    name: Docs
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: stable
+    - uses: Swatinem/rust-cache@v2
+    - name: Check documentation
+      env:
+        RUSTDOCFLAGS: -D warnings
+      run: cargo doc --workspace --all-features --no-deps --document-private-items
+  rustfmt:
+    name: rustfmt
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        # Not MSRV because its harder to jump between versions and people are
+        # more likely to have stable
+        toolchain: stable
+        components: rustfmt
+    - uses: Swatinem/rust-cache@v2
+    - name: Check formatting
+      run: cargo fmt --all -- --check
+  clippy:
+    name: clippy
+    runs-on: ubuntu-latest
+    permissions:
+      security-events: write # to upload sarif results
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: 1.64.0  # MSRV
+        components: clippy
+    - uses: Swatinem/rust-cache@v2
+    - name: Install SARIF tools
+      run: cargo install clippy-sarif --version 0.3.4 --locked  # Held back due to msrv
+    - name: Install SARIF tools
+      run: cargo install sarif-fmt --version 0.3.4 --locked # Held back due to msrv
+    - name: Check
+      run: >
+        cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated
+        | clippy-sarif
+        | tee clippy-results.sarif
+        | sarif-fmt
+      continue-on-error: true
+    - name: Upload
+      uses: github/codeql-action/upload-sarif@v2
+      with:
+        sarif_file: clippy-results.sarif
+        wait-for-processing: true
diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml
new file mode 100644
index 0000000..509be08
--- /dev/null
+++ b/.github/workflows/committed.yml
@@ -0,0 +1,24 @@
+# Not run as part of pre-commit checks because they don't handle sending the correct commit
+# range to `committed`
+name: Lint Commits
+on: [pull_request]
+
+permissions:
+  contents: read
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  committed:
+    name: Lint Commits
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout Actions Repository
+      uses: actions/checkout@v3
+      with:
+        fetch-depth: 0
+    - name: Lint Commits
+      uses: crate-ci/committed@master
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 0000000..d4b0f84
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,23 @@
+name: pre-commit
+
+permissions: {} # none
+
+on:
+  pull_request:
+  push:
+    branches: [main]
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  pre-commit:
+    permissions:
+      contents: read
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+    - uses: actions/setup-python@v4
+    - uses: pre-commit/action@v3.0.0
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
new file mode 100644
index 0000000..8faba30
--- /dev/null
+++ b/.github/workflows/rust-next.yml
@@ -0,0 +1,88 @@
+name: rust-next
+
+permissions:
+  contents: read
+
+on:
+  schedule:
+  - cron: '1 1 1 * *'
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  test:
+    name: Test
+    strategy:
+      matrix:
+        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
+        rust: ["stable", "beta"]
+        include:
+        - os: ubuntu-latest
+          rust: "nightly"
+    continue-on-error: ${{ matrix.rust != 'stable' }}
+    runs-on: ${{ matrix.os }}
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: ${{ matrix.rust }}
+    - uses: Swatinem/rust-cache@v2
+    - name: Default features
+      run: cargo test --workspace
+    - name: All features
+      run: cargo test --workspace --all-features
+    - name: No-default features
+      run: cargo test --workspace --no-default-features
+  rustfmt:
+    name: rustfmt
+    strategy:
+      matrix:
+        rust:
+        - stable
+        - beta
+    continue-on-error: ${{ matrix.rust != 'stable' }}
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: ${{ matrix.rust }}
+        components: rustfmt
+    - uses: Swatinem/rust-cache@v2
+    - name: Check formatting
+      run: cargo fmt --all -- --check
+  clippy:
+    name: clippy
+    runs-on: ubuntu-latest
+    permissions:
+      security-events: write # to upload sarif results
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: stable
+        components: clippy
+    - uses: Swatinem/rust-cache@v2
+    - name: Install SARIF tools
+      run: cargo install clippy-sarif sarif-fmt
+    - name: Check
+      run: >
+        cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated
+        | clippy-sarif
+        | tee clippy-results.sarif
+        | sarif-fmt
+      continue-on-error: true
+    - name: Upload
+      uses: github/codeql-action/upload-sarif@v2
+      with:
+        sarif_file: clippy-results.sarif
+        wait-for-processing: true
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
new file mode 100644
index 0000000..f31c7ed
--- /dev/null
+++ b/.github/workflows/spelling.yml
@@ -0,0 +1,21 @@
+name: Spelling
+
+permissions:
+  contents: read
+
+on: [pull_request]
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  spelling:
+    name: Spell Check with Typos
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout Actions Repository
+      uses: actions/checkout@v3
+    - name: Spell Check Repo
+      uses: crate-ci/typos@master
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eb5a316
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..f751dec
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,26 @@
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.3.0
+    hooks:
+    - id: check-yaml
+      stages: [commit]
+    - id: check-json
+      stages: [commit]
+    - id: check-toml
+      stages: [commit]
+    - id: check-merge-conflict
+      stages: [commit]
+    - id: check-case-conflict
+      stages: [commit]
+    - id: detect-private-key
+      stages: [commit]
+  - repo: https://github.com/crate-ci/typos
+    rev: v1.11.1
+    hooks:
+    - id: typos
+      stages: [commit]
+  - repo: https://github.com/crate-ci/committed
+    rev: v1.0.4
+    hooks:
+    - id: committed
+      stages: [commit-msg]
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..23a247b
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/)
+and this project adheres to [Semantic Versioning](http://semver.org/).
+
+<!-- next-header -->
+## [Unreleased] - ReleaseDate
+
+<!-- next-url -->
+[Unreleased]: https://github.com/rust-cli/argfile/compare/v0.1.5...HEAD
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ce840a9
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,70 @@
+# Contributing to PROJECT
+
+Thanks for wanting to contribute! There are many ways to contribute and we
+appreciate any level you're willing to do.
+
+## Feature Requests
+
+Need some new functionality to help?  You can let us know by opening an
+[issue][new issue]. It's helpful to look through [all issues][all issues] in
+case its already being talked about.
+
+## Bug Reports
+
+Please let us know about what problems you run into, whether in behavior or
+ergonomics of API.  You can do this by opening an [issue][new issue]. It's
+helpful to look through [all issues][all issues] in case its already being
+talked about.
+
+## Pull Requests
+
+Looking for an idea? Check our [issues][issues]. If it's look more open ended,
+it is probably best to post on the issue how you are thinking of resolving the
+issue so you can get feedback early in the process. We want you to be
+successful and it can be discouraging to find out a lot of re-work is needed.
+
+Already have an idea?  It might be good to first [create an issue][new issue]
+to propose it so we can make sure we are aligned and lower the risk of having
+to re-work some of it and the discouragement that goes along with that.
+
+### Process
+
+Before posting a PR, we request that the commit history get cleaned up.
+However, we recommend avoiding this during the review to make it easier to
+check how feedback was handled. Once the PR is ready, we'll ask you to clean up
+the commit history from the review.  Once you let us know this is done, we can
+move forward with merging!  If you are uncomfortable with these parts of git,
+let us know and we can help.
+
+For commit messages, we use [Conventional](https://www.conventionalcommits.org)
+style.  If you already wrote your commits and don't feel comfortable changing
+them, don't worry and go ahead and create your PR.  We'll work with you on the
+best route forward. You can check your branch locally with
+[`committed`](https://github.com/crate-ci/committed).
+
+As a heads up, we'll be running your PR through the following gauntlet:
+- warnings turned to compile errors
+- `cargo test`
+- `rustfmt`
+- `clippy`
+- `rustdoc`
+- [`committed`](https://github.com/crate-ci/committed)
+- [`typos`](https://github.com/crate-ci/typos)
+
+## Releasing
+
+Pre-requisites
+- Running `cargo login`
+- A member of `ORG:Maintainers`
+- Push permission to the repo
+- [`cargo-release`](https://github.com/crate-ci/cargo-release/)
+
+When we're ready to release, a project owner should do the following
+1. Update the changelog (see `cargo release changes` for ideas)
+2. Determine what the next version is, according to semver
+3. Run [`cargo release -x <level>`](https://github.com/crate-ci/cargo-release)
+
+[issues]: https://github.com/ORG/PROJECT/issues
+[new issue]: https://github.com/ORG/PROJECT/issues/new
+[all issues]: https://github.com/ORG/PROJECT/issues?utf8=%E2%9C%93&q=is%3Aissue
+[travis]: https://github.com/ORG/PROJECT/blob/master/.travis.yml
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..49c1f2d
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "PROJECT"
+version = "0.0.1"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..55dc855
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "PROJECT"
+version = "0.0.1"
+description = "DESCRIPTION"
+license = "MIT OR Apache-2.0"
+categories = []
+keywords = []
+edition = "2021"
+rust-version = "1.64.0"  # MSRV
+include = [
+  "build.rs",
+  "src/**/*",
+  "Cargo.toml",
+  "LICENSE*",
+  "README.md",
+  "benches/**/*",
+  "examples/**/*"
+]
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
+[package.metadata.release]
+pre-release-replacements = [
+  {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
+  {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1},
+  {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
+  {file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## [Unreleased] - ReleaseDate\n", exactly=1},
+  {file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\n[Unreleased]: https://github.com/ORG/PROJECT/compare/{{tag_name}}...HEAD", exactly=1},
+]
+
+[features]
+default = []
+
+[dependencies]
+
+[dev-dependencies]
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..8f71f43
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,202 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..a2d0108
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright (c) Individual contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..41d5a97
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# PROJECT
+
+> DESCRIPTION
+
+[![Documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation]
+![License](https://img.shields.io/crates/l/PROJECT.svg)
+[![Crates Status](https://img.shields.io/crates/v/PROJECT.svg)](https://crates.io/crates/PROJECT)
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion in the work by you, as defined in the Apache-2.0
+license, shall be dual licensed as above, without any additional terms or
+conditions.
+
+[Crates.io]: https://crates.io/crates/PROJECT
+[Documentation]: https://docs.rs/PROJECT
diff --git a/committed.toml b/committed.toml
new file mode 100644
index 0000000..4211ae3
--- /dev/null
+++ b/committed.toml
@@ -0,0 +1,3 @@
+style="conventional"
+ignore_author_re="(dependabot|renovate)"
+merge_commit = false
diff --git a/deny.toml b/deny.toml
new file mode 100644
index 0000000..ad23fbb
--- /dev/null
+++ b/deny.toml
@@ -0,0 +1,135 @@
+# Note that all fields that take a lint level have these possible values:
+# * deny - An error will be produced and the check will fail
+# * warn - A warning will be produced, but the check will not fail
+# * allow - No warning or error will be produced, though in some cases a note
+# will be
+
+# This section is considered when running `cargo deny check advisories`
+# More documentation for the advisories section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
+[advisories]
+# The lint level for security vulnerabilities
+vulnerability = "deny"
+# The lint level for unmaintained crates
+unmaintained = "warn"
+# The lint level for crates that have been yanked from their source registry
+yanked = "warn"
+# The lint level for crates with security notices. Note that as of
+# 2019-12-17 there are no security notice advisories in
+# https://github.com/rustsec/advisory-db
+notice = "warn"
+# A list of advisory IDs to ignore. Note that ignored advisories will still
+# output a note when they are encountered.
+#
+# e.g. "RUSTSEC-0000-0000",
+ignore = [
+]
+
+# This section is considered when running `cargo deny check licenses`
+# More documentation for the licenses section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
+[licenses]
+unlicensed = "deny"
+# List of explicitly allowed licenses
+# See https://spdx.org/licenses/ for list of possible licenses
+# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
+allow = [
+    "MIT",
+    "Apache-2.0",
+    #"Apache-2.0 WITH LLVM-exception",
+]
+# List of explicitly disallowed licenses
+# See https://spdx.org/licenses/ for list of possible licenses
+# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
+deny = [
+]
+# Lint level for licenses considered copyleft
+copyleft = "deny"
+# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
+# * both - The license will be approved if it is both OSI-approved *AND* FSF
+# * either - The license will be approved if it is either OSI-approved *OR* FSF
+# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
+# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
+# * neither - This predicate is ignored and the default lint level is used
+allow-osi-fsf-free = "neither"
+# Lint level used when no other predicates are matched
+# 1. License isn't in the allow or deny lists
+# 2. License isn't copyleft
+# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
+default = "deny"
+# The confidence threshold for detecting a license from license text.
+# The higher the value, the more closely the license text must be to the
+# canonical license text of a valid SPDX license file.
+# [possible values: any between 0.0 and 1.0].
+confidence-threshold = 0.8
+# Allow 1 or more licenses on a per-crate basis, so that particular licenses
+# aren't accepted for every possible crate as with the normal allow list
+exceptions = [
+    # Each entry is the crate and version constraint, and its specific allow
+    # list
+    #{ allow = ["Zlib"], name = "adler32", version = "*" },
+]
+
+[licenses.private]
+# If true, ignores workspace crates that aren't published, or are only
+# published to private registries.
+# To see how to mark a crate as unpublished (to the official registry),
+# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
+ignore = true
+
+# This section is considered when running `cargo deny check bans`.
+# More documentation about the 'bans' section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
+[bans]
+# Lint level for when multiple versions of the same crate are detected
+multiple-versions = "warn"
+# Lint level for when a crate version requirement is `*`
+wildcards = "deny"
+# The graph highlighting used when creating dotgraphs for crates
+# with multiple versions
+# * lowest-version - The path to the lowest versioned duplicate is highlighted
+# * simplest-path - The path to the version with the fewest edges is highlighted
+# * all - Both lowest-version and simplest-path are used
+highlight = "all"
+# The default lint level for `default` features for crates that are members of
+# the workspace that is being checked. This can be overridden by allowing/denying
+# `default` on a crate-by-crate basis if desired.
+workspace-default-features = "allow"
+# The default lint level for `default` features for external crates that are not
+# members of the workspace. This can be overridden by allowing/denying `default`
+# on a crate-by-crate basis if desired.
+external-default-features = "allow"
+# List of crates that are allowed. Use with care!
+allow = [
+    #{ name = "ansi_term", version = "=0.11.0" },
+]
+# List of crates to deny
+deny = [
+    # Each entry the name of a crate and a version range. If version is
+    # not specified, all versions will be matched.
+    #{ name = "ansi_term", version = "=0.11.0" },
+    #
+    # Wrapper crates can optionally be specified to allow the crate when it
+    # is a direct dependency of the otherwise banned crate
+    #{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
+]
+
+# This section is considered when running `cargo deny check sources`.
+# More documentation about the 'sources' section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
+[sources]
+# Lint level for what to happen when a crate from a crate registry that is not
+# in the allow list is encountered
+unknown-registry = "deny"
+# Lint level for what to happen when a crate from a git repository that is not
+# in the allow list is encountered
+unknown-git = "deny"
+# List of URLs for allowed crate registries. Defaults to the crates.io index
+# if not specified. If it is specified but empty, no registries are allowed.
+allow-registry = ["https://github.com/rust-lang/crates.io-index"]
+# List of URLs for allowed Git repositories
+allow-git = []
+
+[sources.allow-org]
+# 1 or more github.com organizations to allow git sources for
+github = []
diff --git a/release.toml b/release.toml
new file mode 100644
index 0000000..16df989
--- /dev/null
+++ b/release.toml
@@ -0,0 +1,6 @@
+pre-release-commit-message = "chore: Release"
+tag-message = "{{tag_name}}"
+tag-name = "{{prefix}}v{{version}}"
+consolidate-commits = true
+consolidate-pushes = true
+allow-branch = ["main"]
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..45bf577
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,2 @@
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![allow(non_snake_case)] // TODO: Delete me

From d6b4446cd761d82313a0e69cf0da82ebfc4084cb Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 14:33:42 -0500
Subject: [PATCH 002/302] docs: Set changelog base

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23a247b..e378dd7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,4 +8,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ## [Unreleased] - ReleaseDate
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-cli/argfile/compare/v0.1.5...HEAD
+[Unreleased]: https://github.com/rust-cli/argfile/compare/e7b7555...HEAD

From fbaab420b9e4e01e60522f87e89e2e0a28250c73 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 1 Apr 2023 00:32:08 +0000
Subject: [PATCH 003/302] chore(deps): update msrv to v1.65.0

---
 .clippy.toml             | 2 +-
 .github/workflows/ci.yml | 6 +++---
 Cargo.toml               | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.clippy.toml b/.clippy.toml
index 16749ab..5c6f984 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -1,4 +1,4 @@
-msrv = "1.64.0"  # MSRV
+msrv = "1.65.0"  # MSRV
 warn-on-all-wildcard-imports = true
 allow-expect-in-tests = true
 allow-unwrap-in-tests = true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 783247c..017d45e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -49,7 +49,7 @@ jobs:
     - name: No-default features
       run: cargo test --workspace --no-default-features
   msrv:
-    name: "Check MSRV: 1.64.0"
+    name: "Check MSRV: 1.65.0"
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
@@ -57,7 +57,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: 1.64.0  # MSRV
+        toolchain: 1.65.0  # MSRV
     - uses: Swatinem/rust-cache@v2
     - name: Default features
       run: cargo check --workspace --all-targets
@@ -107,7 +107,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: 1.64.0  # MSRV
+        toolchain: 1.65.0  # MSRV
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools
diff --git a/Cargo.toml b/Cargo.toml
index 55dc855..1c84a5e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0"
 categories = []
 keywords = []
 edition = "2021"
-rust-version = "1.64.0"  # MSRV
+rust-version = "1.65.0"  # MSRV
 include = [
   "build.rs",
   "src/**/*",

From 614b0a2376b9ae6d95a1b768b93d06057f4b82d6 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 14:40:57 -0500
Subject: [PATCH 004/302] docs(contrib): Remove reference to travis

---
 CONTRIBUTING.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ce840a9..e9d7079 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -67,4 +67,3 @@ When we're ready to release, a project owner should do the following
 [issues]: https://github.com/ORG/PROJECT/issues
 [new issue]: https://github.com/ORG/PROJECT/issues/new
 [all issues]: https://github.com/ORG/PROJECT/issues?utf8=%E2%9C%93&q=is%3Aissue
-[travis]: https://github.com/ORG/PROJECT/blob/master/.travis.yml

From afeff23549a05cd0e5997f129e5d7a564ec41866 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 14:41:29 -0500
Subject: [PATCH 005/302] chore(ci): Quote strings in yaml

---
 .github/settings.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/settings.yml b/.github/settings.yml
index 0469378..8ead1ba 100644
--- a/.github/settings.yml
+++ b/.github/settings.yml
@@ -25,19 +25,19 @@ labels:
   # Type
   - name: bug
     color: '#b60205'
-    description: Not as expected
+    description: "Not as expected"
   - name: enhancement
     color: '#1d76db'
-    description: Improve the expected
+    description: "Improve the expected"
   # Flavor
   - name: question
     color: "#cc317c"
-    description: Uncertainty is involved
+    description: "Uncertainty is involved"
   - name: breaking-change
     color: "#e99695"
   - name: good first issue
     color: '#c2e0c6'
-    description: Help wanted!
+    description: "Help wanted!"
 
 branches:
   - name: main

From 2768727452315929d88dda7d0686440d8e668736 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 14:46:23 -0500
Subject: [PATCH 006/302] chore: Don't set rustflags by default

Doing so can cause unnecessary recompilation
---
 .cargo/config | 5 -----
 1 file changed, 5 deletions(-)
 delete mode 100644 .cargo/config

diff --git a/.cargo/config b/.cargo/config
deleted file mode 100644
index ba32123..0000000
--- a/.cargo/config
+++ /dev/null
@@ -1,5 +0,0 @@
-[target.x86_64-pc-windows-msvc]
-rustflags = ["-Ctarget-feature=+crt-static"]
-
-[target.i686-pc-windows-msvc]
-rustflags = ["-Ctarget-feature=+crt-static"]

From 083884043cc08394c6f91df81e6407721b2dc19e Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 14:51:13 -0500
Subject: [PATCH 007/302] chore: Update release process

---
 release.toml | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/release.toml b/release.toml
index 16df989..160b061 100644
--- a/release.toml
+++ b/release.toml
@@ -1,6 +1,2 @@
-pre-release-commit-message = "chore: Release"
-tag-message = "{{tag_name}}"
-tag-name = "{{prefix}}v{{version}}"
-consolidate-commits = true
-consolidate-pushes = true
+dependent-version = "fix"
 allow-branch = ["main"]

From afd6a45ef73201bf5d5f3d4f0317f432b17c60d0 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 14:53:08 -0500
Subject: [PATCH 008/302] chore: Use workspace inheritance

---
 Cargo.toml | 21 +++++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 1c84a5e..6e698fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,10 +1,8 @@
-[package]
-name = "PROJECT"
-version = "0.0.1"
-description = "DESCRIPTION"
+[workspace]
+resolver = "2"
+
+[workspace.package]
 license = "MIT OR Apache-2.0"
-categories = []
-keywords = []
 edition = "2021"
 rust-version = "1.65.0"  # MSRV
 include = [
@@ -17,6 +15,17 @@ include = [
   "examples/**/*"
 ]
 
+[package]
+name = "PROJECT"
+version = "0.0.1"
+description = "DESCRIPTION"
+categories = []
+keywords = []
+license.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+include.workspace = true
+
 [package.metadata.docs.rs]
 all-features = true
 rustdoc-args = ["--cfg", "docsrs"]

From 037f37906dad6d39f9fad371bc9a8ab76e8bd5c4 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 15:07:09 -0500
Subject: [PATCH 009/302] chore(ci): Remove rustfmt/clippy next jobs

---
 .github/workflows/rust-next.yml | 48 ---------------------------------
 1 file changed, 48 deletions(-)

diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index 8faba30..e90121b 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -38,51 +38,3 @@ jobs:
       run: cargo test --workspace --all-features
     - name: No-default features
       run: cargo test --workspace --no-default-features
-  rustfmt:
-    name: rustfmt
-    strategy:
-      matrix:
-        rust:
-        - stable
-        - beta
-    continue-on-error: ${{ matrix.rust != 'stable' }}
-    runs-on: ubuntu-latest
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v3
-    - name: Install Rust
-      uses: dtolnay/rust-toolchain@stable
-      with:
-        toolchain: ${{ matrix.rust }}
-        components: rustfmt
-    - uses: Swatinem/rust-cache@v2
-    - name: Check formatting
-      run: cargo fmt --all -- --check
-  clippy:
-    name: clippy
-    runs-on: ubuntu-latest
-    permissions:
-      security-events: write # to upload sarif results
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v3
-    - name: Install Rust
-      uses: dtolnay/rust-toolchain@stable
-      with:
-        toolchain: stable
-        components: clippy
-    - uses: Swatinem/rust-cache@v2
-    - name: Install SARIF tools
-      run: cargo install clippy-sarif sarif-fmt
-    - name: Check
-      run: >
-        cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated
-        | clippy-sarif
-        | tee clippy-results.sarif
-        | sarif-fmt
-      continue-on-error: true
-    - name: Upload
-      uses: github/codeql-action/upload-sarif@v2
-      with:
-        sarif_file: clippy-results.sarif
-        wait-for-processing: true

From d1dd4ae94067be2f3158fa46b0e78504705dfb26 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 15:28:54 -0500
Subject: [PATCH 010/302] chore(ci): Expand approved licenses

---
 deny.toml | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/deny.toml b/deny.toml
index ad23fbb..942e08d 100644
--- a/deny.toml
+++ b/deny.toml
@@ -35,8 +35,12 @@ unlicensed = "deny"
 # [possible values: any SPDX 3.11 short identifier (+ optional exception)].
 allow = [
     "MIT",
+    "MIT-0",
     "Apache-2.0",
-    #"Apache-2.0 WITH LLVM-exception",
+    "BSD-3-Clause",
+    "MPL-2.0",
+    "Unicode-DFS-2016",
+    "CC0-1.0",
 ]
 # List of explicitly disallowed licenses
 # See https://spdx.org/licenses/ for list of possible licenses

From 6c8df60dc4015279cef303cab8f4760efb5ebea8 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 Mar 2023 22:38:45 -0500
Subject: [PATCH 011/302] chore: Include Cargo.lock

---
 Cargo.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Cargo.toml b/Cargo.toml
index 6e698fb..b8ecde1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@ include = [
   "build.rs",
   "src/**/*",
   "Cargo.toml",
+  "Cargo.lock",
   "LICENSE*",
   "README.md",
   "benches/**/*",

From f7b990b803a4aa448e81a323df3a54e66d2d8df4 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 08:50:19 -0500
Subject: [PATCH 012/302] fix(ci): Fix Renovate regexes

---
 .github/renovate.json5 | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 51faa75..5e8e7e2 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -11,9 +11,9 @@
         "^rust-toolchain\\.toml$",
         "Cargo.toml$",
         "clippy.toml$",
-        "\.clippy.toml$",
-        "^\.github/workflows/ci.yml$",
-        "^\.github/workflows/rust-next.yml$",
+        "\\.clippy.toml$",
+        "^\\.github/workflows/ci.yml$",
+        "^\\.github/workflows/rust-next.yml$",
       ],
       "matchStrings": [
         "MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)",

From 4163ad78c72df3a993bea6084fc05c6a2a44b9c2 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 08:51:48 -0500
Subject: [PATCH 013/302] style(ci): Match auto-generated style

This will make reviewing auto-update PRs easier
---
 .github/renovate.json5 | 126 +++++++++++++++++++++++++----------------
 1 file changed, 78 insertions(+), 48 deletions(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 5e8e7e2..0393074 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -1,35 +1,39 @@
 {
-  "schedule": [
-    "before 3am on the first day of the month"
+  schedule: [
+    'before 3am on the first day of the month'
   ],
-  "semanticCommits": "enabled",
-  "configMigration": true,
-  "dependencyDashboard": true,
-  "regexManagers": [
+  semanticCommits: 'enabled',
+  configMigration: true,
+  dependencyDashboard: true,
+  regexManagers: [
     {
-      "fileMatch": [
-        "^rust-toolchain\\.toml$",
-        "Cargo.toml$",
-        "clippy.toml$",
-        "\\.clippy.toml$",
-        "^\\.github/workflows/ci.yml$",
-        "^\\.github/workflows/rust-next.yml$",
-      ],
-      "matchStrings": [
-        "MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)",
-        "(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV",
-      ],
-      "depNameTemplate": "rust",
-      "packageNameTemplate": "rust-lang/rust",
-      "datasourceTemplate": "github-releases",
+      fileMatch: [
+        '^rust-toolchain\\.toml$',
+        'Cargo.toml$',
+        'clippy.toml$',
+        '\\.clippy.toml$',
+        '^\\.github/workflows/ci.yml$',
+        '^\\.github/workflows/rust-next.yml$',
+      ],
+      matchStrings: [
+        'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)',
+        '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV',
+      ],
+      depNameTemplate: 'rust',
+      packageNameTemplate: 'rust-lang/rust',
+      datasourceTemplate: 'github-releases',
     }
   ],
-  "packageRules": [
+  packageRules: [
     {
-      "commitMessageTopic": "MSRV",
-      "matchManagers": ["regex"],
-      "matchPackageNames": ["rust"],
-      "stabilityDays": 126,  // 3 releases * 6 weeks per release * 7 days per week
+      commitMessageTopic: 'MSRV',
+      matchManagers: [
+        'regex',
+      ],
+      matchPackageNames: [
+        'rust',
+      ],
+      stabilityDays: 126,  // 3 releases * 6 weeks per release * 7 days per week
     },
     // Goals:
     // - Keep version reqs low, ignoring compatible normal/build dependencies
@@ -38,34 +42,60 @@
     // - Help keep number of versions down by always using latest breaking change
     // - Have lockfile and manifest in-sync
     {
-      "matchManagers": ["cargo"],
-      "matchDepTypes": ["build-dependencies", "dependencies"],
-      "matchCurrentVersion": ">=0.1.0",
-      "matchUpdateTypes": ["patch"],
-      "enabled": false,
+      matchManagers: [
+        'cargo',
+      ],
+      matchDepTypes: [
+        'build-dependencies',
+        'dependencies',
+      ],
+      matchCurrentVersion: '>=0.1.0',
+      matchUpdateTypes: [
+        'patch',
+      ],
+      enabled: false,
     },
     {
-      "matchManagers": ["cargo"],
-      "matchDepTypes": ["build-dependencies", "dependencies"],
-      "matchCurrentVersion": ">=1.0.0",
-      "matchUpdateTypes": ["minor"],
-      "enabled": false,
+      matchManagers: [
+        'cargo',
+      ],
+      matchDepTypes: [
+        'build-dependencies',
+        'dependencies',
+      ],
+      matchCurrentVersion: '>=1.0.0',
+      matchUpdateTypes: [
+        'minor',
+      ],
+      enabled: false,
     },
     {
-      "matchManagers": ["cargo"],
-      "matchDepTypes": ["dev-dependencies"],
-      "matchCurrentVersion": ">=0.1.0",
-      "matchUpdateTypes": ["patch"],
-      "automerge": true,
-      "groupName": "compatible (dev)",
+      matchManagers: [
+        'cargo',
+      ],
+      matchDepTypes: [
+        'dev-dependencies',
+      ],
+      matchCurrentVersion: '>=0.1.0',
+      matchUpdateTypes: [
+        'patch',
+      ],
+      automerge: true,
+      groupName: 'compatible (dev)',
     },
     {
-      "matchManagers": ["cargo"],
-      "matchDepTypes": ["dev-dependencies"],
-      "matchCurrentVersion": ">=1.0.0",
-      "matchUpdateTypes": ["minor"],
-      "automerge": true,
-      "groupName": "compatible (dev)",
+      matchManagers: [
+        'cargo',
+      ],
+      matchDepTypes: [
+        'dev-dependencies',
+      ],
+      matchCurrentVersion: '>=1.0.0',
+      matchUpdateTypes: [
+        'minor',
+      ],
+      automerge: true,
+      groupName: 'compatible (dev)',
     },
   ],
 }

From 563de12d25e777e7244a73308090adcfb8b90014 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 09:01:54 -0500
Subject: [PATCH 014/302] chore(ci): Update stabilidyDays to new syntax

---
 .github/renovate.json5 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 0393074..d5485d2 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -33,7 +33,7 @@
       matchPackageNames: [
         'rust',
       ],
-      stabilityDays: 126,  // 3 releases * 6 weeks per release * 7 days per week
+      stabilityDays: "126 days",  // 3 releases * 6 weeks per release * 7 days per week
     },
     // Goals:
     // - Keep version reqs low, ignoring compatible normal/build dependencies

From 2c4a7f574f6fed6655e8b2f25916c22d7bf08ad1 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 09:02:40 -0500
Subject: [PATCH 015/302] chore(ci): Delay Renovate PRs until ready

---
 .github/renovate.json5 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index d5485d2..0e8f1d6 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -34,6 +34,7 @@
         'rust',
       ],
       stabilityDays: "126 days",  // 3 releases * 6 weeks per release * 7 days per week
+      internalChecksFilter: "strict",
     },
     // Goals:
     // - Keep version reqs low, ignoring compatible normal/build dependencies

From 62401b8eafb71d8a928137f6f8dfc25340e39bbf Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 09:05:31 -0500
Subject: [PATCH 016/302] chore(ci): Lower the MSRV churn for template

---
 .github/renovate.json5 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 0e8f1d6..900feaf 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -33,7 +33,7 @@
       matchPackageNames: [
         'rust',
       ],
-      stabilityDays: "126 days",  // 3 releases * 6 weeks per release * 7 days per week
+      stabilityDays: "336 days",  // 8 releases * 6 weeks per release * 7 days per week
       internalChecksFilter: "strict",
     },
     // Goals:

From d99db2e632b25a8b020491c3e1d40bf2efd3472a Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 09:54:05 -0500
Subject: [PATCH 017/302] style(ci): Match auto-generated style

---
 .github/renovate.json5 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 900feaf..54bc593 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -1,6 +1,6 @@
 {
   schedule: [
-    'before 3am on the first day of the month'
+    'before 3am on the first day of the month',
   ],
   semanticCommits: 'enabled',
   configMigration: true,
@@ -22,7 +22,7 @@
       depNameTemplate: 'rust',
       packageNameTemplate: 'rust-lang/rust',
       datasourceTemplate: 'github-releases',
-    }
+    },
   ],
   packageRules: [
     {

From afaba35d39c75d13138e2928cddeb0b93601cee3 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 09:54:21 -0500
Subject: [PATCH 018/302] chore(ci): Use new minimumReleaseAge field

---
 .github/renovate.json5 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 54bc593..79e5152 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -33,7 +33,7 @@
       matchPackageNames: [
         'rust',
       ],
-      stabilityDays: "336 days",  // 8 releases * 6 weeks per release * 7 days per week
+      minimumReleaseAge: "336 days",  // 8 releases * 6 weeks per release * 7 days per week
       internalChecksFilter: "strict",
     },
     // Goals:

From 60a8ec89e3f97baad0dbe097e03dc0cd30899e02 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 20:03:56 -0500
Subject: [PATCH 019/302] chore(ci): Ban for_each

---
 .clippy.toml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.clippy.toml b/.clippy.toml
index 5c6f984..56d269a 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -9,4 +9,6 @@ disallowed-methods = [
     { path = "std::option::Option::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" },
     { path = "std::result::Result::map_or", reason = "use `map(..).unwrap_or(..)`" },
     { path = "std::result::Result::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" },
+    { path = "std::iter::Iterator::for_each", reason = "prefer `for` for side-effects" },
+    { path = "std::iter::Iterator::try_for_each", reason = "prefer `for` for side-effects" },
 ]

From 96297f038d8d931bb9d5ba4dfcdced18d7c81061 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 20:04:56 -0500
Subject: [PATCH 020/302] chore(ci): Clarify why map_or is banned

---
 .clippy.toml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.clippy.toml b/.clippy.toml
index 56d269a..22fe10b 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -5,10 +5,10 @@ allow-unwrap-in-tests = true
 allow-dbg-in-tests = true
 allow-print-in-tests = true
 disallowed-methods = [
-    { path = "std::option::Option::map_or", reason = "use `map(..).unwrap_or(..)`" },
-    { path = "std::option::Option::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" },
-    { path = "std::result::Result::map_or", reason = "use `map(..).unwrap_or(..)`" },
-    { path = "std::result::Result::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" },
+    { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" },
+    { path = "std::option::Option::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" },
+    { path = "std::result::Result::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" },
+    { path = "std::result::Result::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" },
     { path = "std::iter::Iterator::for_each", reason = "prefer `for` for side-effects" },
     { path = "std::iter::Iterator::try_for_each", reason = "prefer `for` for side-effects" },
 ]

From 716170eaa853ddf3032baa9b107eb3e44d6a4124 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 17 Apr 2023 20:13:36 -0500
Subject: [PATCH 021/302] chore(gh): Ban rebase merges

---
 .github/settings.yml | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/.github/settings.yml b/.github/settings.yml
index 8ead1ba..7d5e4fc 100644
--- a/.github/settings.yml
+++ b/.github/settings.yml
@@ -10,9 +10,12 @@ repository:
   has_downloads: true
   default_branch: main
 
-  allow_squash_merge: true
+  # Preference: people do clean commits
   allow_merge_commit: true
-  allow_rebase_merge: true
+  # Backup in case we need to clean up commits
+  allow_squash_merge: true
+  # Not really needed
+  allow_rebase_merge: false
 
   allow_auto_merge: true
   delete_branch_on_merge: true

From 80d4cdd688e88b897f384b770f9c13268ecb3793 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 18 May 2023 14:57:02 -0500
Subject: [PATCH 022/302] chore: Remove clippy lint past MSRV (needs 1.67)

---
 .clippy.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.clippy.toml b/.clippy.toml
index 22fe10b..090e2be 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -3,7 +3,6 @@ warn-on-all-wildcard-imports = true
 allow-expect-in-tests = true
 allow-unwrap-in-tests = true
 allow-dbg-in-tests = true
-allow-print-in-tests = true
 disallowed-methods = [
     { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" },
     { path = "std::option::Option::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" },

From 2b6bb28cd18916a6244a2632a6abcba9362b9fd0 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 18 May 2023 14:58:59 -0500
Subject: [PATCH 023/302] chore(ci): Catch clippy config failures

---
 .github/workflows/ci.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 017d45e..a7bb325 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -126,3 +126,5 @@ jobs:
       with:
         sarif_file: clippy-results.sarif
         wait-for-processing: true
+    - name: Report status
+      run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated

From 4d44cd7ca51f05fb06185677642d73c0ff0da079 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 19 May 2023 13:12:26 -0500
Subject: [PATCH 024/302] chore: Update precommit hooks

---
 .pre-commit-config.yaml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f751dec..fd77abb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.3.0
+    rev: v4.4.0
     hooks:
     - id: check-yaml
       stages: [commit]
@@ -15,12 +15,12 @@ repos:
     - id: detect-private-key
       stages: [commit]
   - repo: https://github.com/crate-ci/typos
-    rev: v1.11.1
+    rev: v1.14.10
     hooks:
     - id: typos
       stages: [commit]
   - repo: https://github.com/crate-ci/committed
-    rev: v1.0.4
+    rev: v1.0.17
     hooks:
     - id: committed
       stages: [commit-msg]

From d6075a44bff9073c811510e86d73216baa844a69 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 2 Aug 2023 11:11:52 -0500
Subject: [PATCH 025/302] chore: Expand update window so more likely to be hit

---
 .github/renovate.json5 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 79e5152..e5733ed 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -1,6 +1,6 @@
 {
   schedule: [
-    'before 3am on the first day of the month',
+    'before 5am on the first day of the month',
   ],
   semanticCommits: 'enabled',
   configMigration: true,

From 67eb1d9e3d396cc7f786d767e287d7e946ed3118 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 7 Aug 2023 16:16:17 -0500
Subject: [PATCH 026/302] chore(ci): Ensure lockfile isn't stale

---
 .github/workflows/ci.yml | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a7bb325..26c9b0f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -65,6 +65,18 @@ jobs:
       run: cargo check --workspace --all-targets --all-features
     - name: No-default features
       run: cargo check --workspace --all-targets --no-default-features
+  lockfile:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: stable
+    - uses: Swatinem/rust-cache@v2
+    - name: "Is lockfile updated?"
+      run: cargo fetch --locked
   docs:
     name: Docs
     runs-on: ubuntu-latest

From ba76b8bd911b98ab78fec3cf6c8e7ee679721a6f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 11 Aug 2023 13:29:06 -0500
Subject: [PATCH 027/302] chore(ci): Ensure latest deps are good

---
 .github/workflows/rust-next.yml | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index e90121b..a540ba5 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -38,3 +38,22 @@ jobs:
       run: cargo test --workspace --all-features
     - name: No-default features
       run: cargo test --workspace --no-default-features
+  latest:
+    name: "Check latest dependencies"
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v3
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: stable
+    - uses: Swatinem/rust-cache@v2
+    - name: Update dependencues
+      run: cargo update
+    - name: Default features
+      run: cargo test --workspace --all-targets
+    - name: All features
+      run: cargo test --workspace --all-targets --all-features
+    - name: No-default features
+      run: cargo test --workspace --all-targets --no-default-features

From 528638729492300730aebee283d2a837325b4a62 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 11 Aug 2023 16:04:07 -0500
Subject: [PATCH 028/302] chore: Update pre-commit hooks

---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index fd77abb..3d9e40f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,12 +15,12 @@ repos:
     - id: detect-private-key
       stages: [commit]
   - repo: https://github.com/crate-ci/typos
-    rev: v1.14.10
+    rev: v1.16.3
     hooks:
     - id: typos
       stages: [commit]
   - repo: https://github.com/crate-ci/committed
-    rev: v1.0.17
+    rev: v1.0.20
     hooks:
     - id: committed
       stages: [commit-msg]

From efe14d60899ec75c901c88b46174ccd3fc5e14d8 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 22 Aug 2023 11:06:55 -0500
Subject: [PATCH 029/302] chore(renovate): Make style consistent

---
 .github/renovate.json5 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index e5733ed..8e31ad0 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -33,8 +33,8 @@
       matchPackageNames: [
         'rust',
       ],
-      minimumReleaseAge: "336 days",  // 8 releases * 6 weeks per release * 7 days per week
-      internalChecksFilter: "strict",
+      minimumReleaseAge: '336 days',  // 8 releases * 6 weeks per release * 7 days per week
+      internalChecksFilter: 'strict',
     },
     // Goals:
     // - Keep version reqs low, ignoring compatible normal/build dependencies

From a6ecf92327e4c75e6545cdd238cc40171337c403 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 22 Aug 2023 11:07:34 -0500
Subject: [PATCH 030/302] chore(renovate): Update config

---
 .github/renovate.json5 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 8e31ad0..7b75c58 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -7,6 +7,7 @@
   dependencyDashboard: true,
   regexManagers: [
     {
+      customType: 'regex',
       fileMatch: [
         '^rust-toolchain\\.toml$',
         'Cargo.toml$',

From c8624f0538c30bb5498db489f456af6012988bdb Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 23 Aug 2023 09:24:15 -0500
Subject: [PATCH 031/302] chore(renovate): Update MSRV on release

---
 .github/renovate.json5 | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 7b75c58..a367a47 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -36,6 +36,9 @@
       ],
       minimumReleaseAge: '336 days',  // 8 releases * 6 weeks per release * 7 days per week
       internalChecksFilter: 'strict',
+      schedule: [
+        'every day',
+      ],
     },
     // Goals:
     // - Keep version reqs low, ignoring compatible normal/build dependencies

From 44604fc1d369e255c7edd0954979310131e24fa4 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 23 Aug 2023 09:35:47 -0500
Subject: [PATCH 032/302] chore(renovate): Try to fix schedule

---
 .github/renovate.json5 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index a367a47..28e23ee 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -37,7 +37,7 @@
       minimumReleaseAge: '336 days',  // 8 releases * 6 weeks per release * 7 days per week
       internalChecksFilter: 'strict',
       schedule: [
-        'every day',
+        '* * * * *',
       ],
     },
     // Goals:

From ff82d6960a9903e3c62414cdacc9b2fa0a7ce2cb Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 23 Aug 2023 10:43:57 -0500
Subject: [PATCH 033/302] chore(ci): Don't fail on wildcards

See EmbarkStudios/cargo-deny#241
---
 deny.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/deny.toml b/deny.toml
index 942e08d..58cc98d 100644
--- a/deny.toml
+++ b/deny.toml
@@ -88,7 +88,7 @@ ignore = true
 # Lint level for when multiple versions of the same crate are detected
 multiple-versions = "warn"
 # Lint level for when a crate version requirement is `*`
-wildcards = "deny"
+wildcards = "warn"
 # The graph highlighting used when creating dotgraphs for crates
 # with multiple versions
 # * lowest-version - The path to the lowest versioned duplicate is highlighted

From 5749aa0932d42cd0ee484b6cb9fcf6f6dd026749 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 7 Sep 2023 09:33:44 -0500
Subject: [PATCH 034/302] chore: Approve ISC

---
 deny.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/deny.toml b/deny.toml
index 58cc98d..21fa937 100644
--- a/deny.toml
+++ b/deny.toml
@@ -41,6 +41,7 @@ allow = [
     "MPL-2.0",
     "Unicode-DFS-2016",
     "CC0-1.0",
+    "ISC",
 ]
 # List of explicitly disallowed licenses
 # See https://spdx.org/licenses/ for list of possible licenses

From 4173c8f4767296f76a6eb96d70b7ca6c13bb38bd Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 20 Sep 2023 09:05:41 -0500
Subject: [PATCH 035/302] chore(ci): Don't set patch for MSRV

---
 .github/renovate.json5 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 28e23ee..56cf1e3 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -36,6 +36,7 @@
       ],
       minimumReleaseAge: '336 days',  // 8 releases * 6 weeks per release * 7 days per week
       internalChecksFilter: 'strict',
+      "extractVersion": "^(?<version>\\d+\\.\\d+)",  // Drop the patch version
       schedule: [
         '* * * * *',
       ],

From 86c29dea384c7392a2b682fa0150f52c0f4c7f00 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 26 Sep 2023 08:16:33 -0500
Subject: [PATCH 036/302] chore(ci): Updaet Renovate schema

---
 .github/renovate.json5 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 56cf1e3..4eaf67f 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -5,7 +5,7 @@
   semanticCommits: 'enabled',
   configMigration: true,
   dependencyDashboard: true,
-  regexManagers: [
+  customManagers: [
     {
       customType: 'regex',
       fileMatch: [

From ac51f0925003597dec21529538597dbd7872d1ac Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 26 Sep 2023 08:16:47 -0500
Subject: [PATCH 037/302] chore(ci): Normalize json5 syntax

---
 .github/renovate.json5 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 4eaf67f..72d0579 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -36,7 +36,7 @@
       ],
       minimumReleaseAge: '336 days',  // 8 releases * 6 weeks per release * 7 days per week
       internalChecksFilter: 'strict',
-      "extractVersion": "^(?<version>\\d+\\.\\d+)",  // Drop the patch version
+      extractVersion: '^(?<version>\\d+\\.\\d+)',  // Drop the patch version
       schedule: [
         '* * * * *',
       ],

From 305798083f34bb57951fb6351aa6b897790907eb Mon Sep 17 00:00:00 2001
From: Peter Kehl <peter.kehl@gmail.com>
Date: Fri, 29 Sep 2023 22:59:44 -0700
Subject: [PATCH 038/302] README.md 'Crates Status' icon link now uses the
 Markdown placeholder/substitution name 'Crates.io'

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 41d5a97..6958ee0 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 
 [![Documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation]
 ![License](https://img.shields.io/crates/l/PROJECT.svg)
-[![Crates Status](https://img.shields.io/crates/v/PROJECT.svg)](https://crates.io/crates/PROJECT)
+[![Crates Status](https://img.shields.io/crates/v/PROJECT.svg)][Crates.io]
 
 ## License
 

From cad9b4717162cc4dbd4253227fc9c5705a302758 Mon Sep 17 00:00:00 2001
From: Peter Kehl <peter.kehl@gmail.com>
Date: Fri, 29 Sep 2023 23:04:45 -0700
Subject: [PATCH 039/302] README.md list indentation and no bare URLs, as per
 Markdown Lint VS Code extension.

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 41d5a97..6f30768 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,8 @@
 
 Licensed under either of
 
- * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
+* MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
 
 at your option.
 

From 6d3f888975aedf79e10336c8090d6aab20751b10 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Oct 2023 01:37:07 +0000
Subject: [PATCH 040/302] chore(deps): update actions/checkout action to v4

---
 .github/workflows/audit.yml      |  4 ++--
 .github/workflows/ci.yml         | 12 ++++++------
 .github/workflows/committed.yml  |  2 +-
 .github/workflows/pre-commit.yml |  2 +-
 .github/workflows/rust-next.yml  |  4 ++--
 .github/workflows/spelling.yml   |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index 5b7e83a..ccee1fe 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -27,7 +27,7 @@ jobs:
     continue-on-error: true
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - uses: actions-rs/audit-check@v1
       with:
         token: ${{ secrets.GITHUB_TOKEN }}
@@ -42,7 +42,7 @@ jobs:
         checks:
           - bans licenses sources
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: EmbarkStudios/cargo-deny-action@v1
       with:
         command: check ${{ matrix.checks }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 26c9b0f..1a70372 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,7 +34,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
@@ -53,7 +53,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
@@ -69,7 +69,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
@@ -82,7 +82,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
@@ -97,7 +97,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
@@ -115,7 +115,7 @@ jobs:
       security-events: write # to upload sarif results
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml
index 509be08..0462558 100644
--- a/.github/workflows/committed.yml
+++ b/.github/workflows/committed.yml
@@ -17,7 +17,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout Actions Repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         fetch-depth: 0
     - name: Lint Commits
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index d4b0f84..8044750 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -18,6 +18,6 @@ jobs:
       contents: read
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/setup-python@v4
     - uses: pre-commit/action@v3.0.0
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index a540ba5..d8c2d25 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -26,7 +26,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
@@ -43,7 +43,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
index f31c7ed..12f7585 100644
--- a/.github/workflows/spelling.yml
+++ b/.github/workflows/spelling.yml
@@ -16,6 +16,6 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout Actions Repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Spell Check Repo
       uses: crate-ci/typos@master

From 5e3b324b5e6488667be2f00a424781030e37a277 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 5 Oct 2023 14:41:40 -0500
Subject: [PATCH 041/302] chore(ci): Ensure MSRV is quoted

Switching from specifying patch to not, with a minor version with a
trailing zero, is causing YAML to convert `1.70` to `1.7`.
---
 .github/workflows/ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1a70372..19efcf9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -57,7 +57,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: 1.65.0  # MSRV
+        toolchain: "1.65.0"  # MSRV
     - uses: Swatinem/rust-cache@v2
     - name: Default features
       run: cargo check --workspace --all-targets
@@ -119,7 +119,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: 1.65.0  # MSRV
+        toolchain: "1.65.0"  # MSRV
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From 5ebe30b9722ac700d414043ff099bad7f3978582 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 26 Oct 2023 09:14:51 -0500
Subject: [PATCH 042/302] chore(ci): Update pre-commit hooks

---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3d9e40f..68db968 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.4.0
+    rev: v4.5.0
     hooks:
     - id: check-yaml
       stages: [commit]
@@ -15,7 +15,7 @@ repos:
     - id: detect-private-key
       stages: [commit]
   - repo: https://github.com/crate-ci/typos
-    rev: v1.16.3
+    rev: v1.16.20
     hooks:
     - id: typos
       stages: [commit]

From c777fb52573071308399b7fa781b0daf110c0814 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 8 Nov 2023 09:51:18 -0600
Subject: [PATCH 043/302] style: Make clippy happy

---
 src/display_list/from_snippet.rs | 273 +++++++++++++++----------------
 tests/dl_from_snippet.rs         |   4 +-
 tests/fixtures_test.rs           |   4 +-
 tests/snippet/mod.rs             |   6 +-
 4 files changed, 142 insertions(+), 145 deletions(-)

diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs
index d7c1ac0..4734147 100644
--- a/src/display_list/from_snippet.rs
+++ b/src/display_list/from_snippet.rs
@@ -11,9 +11,9 @@ impl<'a> CursorLines<'a> {
 }
 
 enum EndLine {
-    EOF = 0,
-    CRLF = 1,
-    LF = 2,
+    Eof = 0,
+    Crlf = 1,
+    Lf = 2,
 }
 
 impl<'a> Iterator for CursorLines<'a> {
@@ -28,18 +28,18 @@ impl<'a> Iterator for CursorLines<'a> {
                 .map(|x| {
                     let ret = if 0 < x {
                         if self.0.as_bytes()[x - 1] == b'\r' {
-                            (&self.0[..x - 1], EndLine::LF)
+                            (&self.0[..x - 1], EndLine::Lf)
                         } else {
-                            (&self.0[..x], EndLine::CRLF)
+                            (&self.0[..x], EndLine::Crlf)
                         }
                     } else {
-                        ("", EndLine::CRLF)
+                        ("", EndLine::Crlf)
                     };
                     self.0 = &self.0[x + 1..];
                     ret
                 })
                 .or_else(|| {
-                    let ret = Some((self.0, EndLine::EOF));
+                    let ret = Some((self.0, EndLine::Eof));
                     self.0 = "";
                     ret
                 })
@@ -330,171 +330,168 @@ fn format_body(
             .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 = annotations
-            .into_iter()
-            .filter(|annotation| {
-                let body_idx = idx + annotation_line_count;
-                let annotation_type = match annotation.annotation_type {
-                    snippet::AnnotationType::Error => DisplayAnnotationType::None,
-                    snippet::AnnotationType::Warning => DisplayAnnotationType::None,
-                    _ => DisplayAnnotationType::from(annotation.annotation_type),
-                };
-                match annotation.range {
-                    (start, _) if start > line_end_index => true,
-                    (start, end)
-                        if start >= line_start_index && end <= line_end_index
-                            || start == line_end_index && end - start <= 1 =>
-                    {
-                        let annotation_start_col = char_widths
-                            .iter()
-                            .take(start - line_start_index)
-                            .sum::<usize>()
-                            - margin_left;
-                        let annotation_end_col = char_widths
-                            .iter()
-                            .take(end - line_start_index)
-                            .sum::<usize>()
-                            - 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(Some(annotation.label), None),
-                                    },
-                                    range,
-                                    annotation_type: DisplayAnnotationType::from(
-                                        annotation.annotation_type,
-                                    ),
-                                    annotation_part: DisplayAnnotationPart::Standalone,
-                                },
-                            },
-                        );
-                        annotation_line_count += 1;
-                        false
-                    }
-                    (start, end)
-                        if start >= line_start_index
-                            && start <= line_end_index
-                            && end > line_end_index =>
-                    {
-                        if start - line_start_index == 0 {
-                            if let DisplayLine::Source {
-                                ref mut inline_marks,
-                                ..
-                            } = body[body_idx]
-                            {
-                                inline_marks.push(DisplayMark {
-                                    mark_type: DisplayMarkType::AnnotationStart,
-                                    annotation_type: DisplayAnnotationType::from(
-                                        annotation.annotation_type,
-                                    ),
-                                });
-                            }
-                        } else {
-                            let annotation_start_col = char_widths
-                                .iter()
-                                .take(start - line_start_index)
-                                .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.annotation_type,
-                                        ),
-                                        annotation_part: DisplayAnnotationPart::MultilineStart,
-                                    },
+        annotations.retain(|annotation| {
+            let body_idx = idx + annotation_line_count;
+            let annotation_type = match annotation.annotation_type {
+                snippet::AnnotationType::Error => DisplayAnnotationType::None,
+                snippet::AnnotationType::Warning => DisplayAnnotationType::None,
+                _ => DisplayAnnotationType::from(annotation.annotation_type),
+            };
+            match annotation.range {
+                (start, _) if start > line_end_index => true,
+                (start, end)
+                    if start >= line_start_index && end <= line_end_index
+                        || start == line_end_index && end - start <= 1 =>
+                {
+                    let annotation_start_col = char_widths
+                        .iter()
+                        .take(start - line_start_index)
+                        .sum::<usize>()
+                        - margin_left;
+                    let annotation_end_col = char_widths
+                        .iter()
+                        .take(end - line_start_index)
+                        .sum::<usize>()
+                        - 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(Some(annotation.label), None),
                                 },
-                            );
-                            annotation_line_count += 1;
-                        }
-                        true
-                    }
-                    (start, end) if start < line_start_index && end > line_end_index => {
-                        if let DisplayLine::Source {
-                            ref mut inline_marks,
-                            ..
-                        } = body[body_idx]
-                        {
-                            inline_marks.push(DisplayMark {
-                                mark_type: DisplayMarkType::AnnotationThrough,
+                                range,
                                 annotation_type: DisplayAnnotationType::from(
                                     annotation.annotation_type,
                                 ),
-                            });
-                        }
-                        true
-                    }
-                    (start, end)
-                        if start < line_start_index
-                            && end >= line_start_index
-                            && end <= line_end_index =>
-                    {
+                                annotation_part: DisplayAnnotationPart::Standalone,
+                            },
+                        },
+                    );
+                    annotation_line_count += 1;
+                    false
+                }
+                (start, end)
+                    if start >= line_start_index
+                        && start <= line_end_index
+                        && end > line_end_index =>
+                {
+                    if start - line_start_index == 0 {
                         if let DisplayLine::Source {
                             ref mut inline_marks,
                             ..
                         } = body[body_idx]
                         {
                             inline_marks.push(DisplayMark {
-                                mark_type: DisplayMarkType::AnnotationThrough,
+                                mark_type: DisplayMarkType::AnnotationStart,
                                 annotation_type: DisplayAnnotationType::from(
                                     annotation.annotation_type,
                                 ),
                             });
                         }
-
-                        let end_mark = char_widths
+                    } else {
+                        let annotation_start_col = char_widths
                             .iter()
-                            .take(end - line_start_index)
-                            .sum::<usize>()
-                            .saturating_sub(1);
-                        let range = (end_mark - margin_left, (end_mark + 1) - margin_left);
+                            .take(start - line_start_index)
+                            .sum::<usize>();
+                        let range = (annotation_start_col, annotation_start_col + 1);
                         body.insert(
                             body_idx + 1,
                             DisplayLine::Source {
                                 lineno: None,
-                                inline_marks: vec![DisplayMark {
-                                    mark_type: DisplayMarkType::AnnotationThrough,
-                                    annotation_type: DisplayAnnotationType::from(
-                                        annotation.annotation_type,
-                                    ),
-                                }],
+                                inline_marks: vec![],
                                 line: DisplaySourceLine::Annotation {
                                     annotation: Annotation {
-                                        annotation_type,
+                                        annotation_type: DisplayAnnotationType::None,
                                         id: None,
-                                        label: format_label(Some(annotation.label), None),
+                                        label: vec![],
                                     },
                                     range,
                                     annotation_type: DisplayAnnotationType::from(
                                         annotation.annotation_type,
                                     ),
-                                    annotation_part: DisplayAnnotationPart::MultilineEnd,
+                                    annotation_part: DisplayAnnotationPart::MultilineStart,
                                 },
                             },
                         );
                         annotation_line_count += 1;
-                        false
                     }
-                    _ => true,
+                    true
+                }
+                (start, end) if start < line_start_index && end > line_end_index => {
+                    if let DisplayLine::Source {
+                        ref mut inline_marks,
+                        ..
+                    } = body[body_idx]
+                    {
+                        inline_marks.push(DisplayMark {
+                            mark_type: DisplayMarkType::AnnotationThrough,
+                            annotation_type: DisplayAnnotationType::from(
+                                annotation.annotation_type,
+                            ),
+                        });
+                    }
+                    true
                 }
-            })
-            .collect();
+                (start, end)
+                    if start < line_start_index
+                        && end >= line_start_index
+                        && end <= line_end_index =>
+                {
+                    if let DisplayLine::Source {
+                        ref mut inline_marks,
+                        ..
+                    } = body[body_idx]
+                    {
+                        inline_marks.push(DisplayMark {
+                            mark_type: DisplayMarkType::AnnotationThrough,
+                            annotation_type: DisplayAnnotationType::from(
+                                annotation.annotation_type,
+                            ),
+                        });
+                    }
+
+                    let end_mark = char_widths
+                        .iter()
+                        .take(end - line_start_index)
+                        .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.annotation_type,
+                                ),
+                            }],
+                            line: DisplaySourceLine::Annotation {
+                                annotation: Annotation {
+                                    annotation_type,
+                                    id: None,
+                                    label: format_label(Some(annotation.label), None),
+                                },
+                                range,
+                                annotation_type: DisplayAnnotationType::from(
+                                    annotation.annotation_type,
+                                ),
+                                annotation_part: DisplayAnnotationPart::MultilineEnd,
+                            },
+                        },
+                    );
+                    annotation_line_count += 1;
+                    false
+                }
+                _ => true,
+            }
+        });
     }
 
     if slice.fold {
diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs
index d6b79ff..06f2ff7 100644
--- a/tests/dl_from_snippet.rs
+++ b/tests/dl_from_snippet.rs
@@ -37,7 +37,7 @@ fn test_format_title() {
 fn test_format_slice() {
     let line_1 = "This is line 1";
     let line_2 = "This is line 2";
-    let source = vec![line_1, line_2].join("\n");
+    let source = [line_1, line_2].join("\n");
     let input = snippet::Snippet {
         title: None,
         footer: vec![],
@@ -173,7 +173,7 @@ fn test_format_slices_continuation() {
 fn test_format_slice_annotation_standalone() {
     let line_1 = "This is line 1";
     let line_2 = "This is line 2";
-    let source = vec![line_1, line_2].join("\n");
+    let source = [line_1, line_2].join("\n");
     // In line 2
     let range = (22, 24);
     let input = snippet::Snippet {
diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs
index e471521..ffc38f9 100644
--- a/tests/fixtures_test.rs
+++ b/tests/fixtures_test.rs
@@ -13,7 +13,7 @@ fn read_file(path: &str) -> Result<String, io::Error> {
     Ok(s.trim_end().to_string())
 }
 
-fn read_fixture<'de>(src: &'de str) -> Result<Snippet<'de>, Box<dyn Error>> {
+fn read_fixture(src: &str) -> Result<Snippet<'_>, Box<dyn Error>> {
     Ok(toml::from_str(src).map(|a: SnippetDef| a.into())?)
 }
 
@@ -25,7 +25,7 @@ fn test_fixtures() {
         let path_in = p.to_str().expect("Can't print path");
         let path_out = path_in.replace(".toml", ".txt");
 
-        let src = read_file(&path_in).expect("Failed to read file");
+        let src = read_file(path_in).expect("Failed to read file");
         let snippet = read_fixture(&src).expect("Failed to read file");
         let expected_out = read_file(&path_out).expect("Failed to read file");
 
diff --git a/tests/snippet/mod.rs b/tests/snippet/mod.rs
index 40249f4..92d272d 100644
--- a/tests/snippet/mod.rs
+++ b/tests/snippet/mod.rs
@@ -23,14 +23,14 @@ pub struct SnippetDef<'a> {
     pub slices: Vec<Slice<'a>>,
 }
 
-impl<'a> Into<Snippet<'a>> for SnippetDef<'a> {
-    fn into(self) -> Snippet<'a> {
+impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
+    fn from(val: SnippetDef<'a>) -> Self {
         let SnippetDef {
             title,
             footer,
             opt,
             slices,
-        } = self;
+        } = val;
         Snippet {
             title,
             footer,

From 807ac679b0d8cd425f664de5516968c00114508f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 8 Nov 2023 09:47:45 -0600
Subject: [PATCH 044/302] chore(ci): Start tracking golden set of deps

---
 .gitignore |   4 +-
 Cargo.lock | 600 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 601 insertions(+), 3 deletions(-)
 create mode 100644 Cargo.lock

diff --git a/.gitignore b/.gitignore
index 6936990..eb5a316 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1 @@
-/target
-**/*.rs.bk
-Cargo.lock
+target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..bd6a75d
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,600 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "annotate-snippets"
+version = "0.9.2"
+dependencies = [
+ "criterion",
+ "difference",
+ "glob",
+ "serde",
+ "toml",
+ "unicode-width",
+ "yansi-term",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "bitflags",
+ "textwrap",
+ "unicode-width",
+]
+
+[[package]]
+name = "criterion"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
+dependencies = [
+ "atty",
+ "cast",
+ "clap",
+ "criterion-plot",
+ "csv",
+ "itertools",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_cbor",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "csv"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "difference"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+
+[[package]]
+name = "js-sys"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
+[[package]]
+name = "plotters"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_cbor"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
+dependencies = [
+ "half",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
+
+[[package]]
+name = "web-sys"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "yansi-term"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
+dependencies = [
+ "winapi",
+]

From 5061ce2366932dcf26e1f6a4fba8290bf9c1c266 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 8 Nov 2023 09:49:42 -0600
Subject: [PATCH 045/302] chore(ci): Transition to epage's CI pipeline

---
 .github/workflows/ci.yml        | 142 +++++++++++++++++++++++++++++---
 .github/workflows/rust-next.yml |  59 +++++++++++++
 .travis.yml                     |  30 -------
 tests/fixtures_test.rs          |   1 +
 4 files changed, 190 insertions(+), 42 deletions(-)
 create mode 100644 .github/workflows/rust-next.yml
 delete mode 100644 .travis.yml

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f40ab58..50c2ddf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,24 +1,142 @@
 name: CI
 
+permissions:
+  contents: read
+
 on:
-  push:
-    branches: [ master ]
   pull_request:
-    branches: [ master ]
+  push:
+    branches:
+    - master
 
 env:
+  RUST_BACKTRACE: 1
   CARGO_TERM_COLOR: always
+  CLICOLOR: 1
 
 jobs:
-  build:
-
+  ci:
+    permissions:
+      contents: none
+    name: CI
+    needs: [test, msrv, docs, rustfmt, clippy]
     runs-on: ubuntu-latest
-
     steps:
-    - uses: actions/checkout@v2
-    - name: Run rustfmt
-      run: cargo fmt -- --check
+      - name: Done
+        run: exit 0
+  test:
+    name: Test
+    strategy:
+      matrix:
+        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
+        rust: ["stable"]
+    continue-on-error: ${{ matrix.rust != 'stable' }}
+    runs-on: ${{ matrix.os }}
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: ${{ matrix.rust }}
+    - uses: Swatinem/rust-cache@v2
     - name: Build
-      run: cargo build --verbose
-    - name: Run tests
-      run: cargo test --verbose
+      run: cargo test --no-run --workspace --all-features
+    - name: Default features
+      run: cargo test --workspace
+    - name: All features
+      run: cargo test --workspace --all-features
+    - name: No-default features
+      run: cargo test --workspace --no-default-features
+  msrv:
+    name: "Check MSRV: 1.70"
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: "1.70"  # MSRV
+    - uses: Swatinem/rust-cache@v2
+    - name: Default features
+      run: cargo check --workspace --all-targets
+    - name: All features
+      run: cargo check --workspace --all-targets --all-features
+    - name: No-default features
+      run: cargo check --workspace --all-targets --no-default-features
+  lockfile:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: stable
+    - uses: Swatinem/rust-cache@v2
+    - name: "Is lockfile updated?"
+      run: cargo fetch --locked
+  docs:
+    name: Docs
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: stable
+    - uses: Swatinem/rust-cache@v2
+    - name: Check documentation
+      env:
+        RUSTDOCFLAGS: -D warnings
+      run: cargo doc --workspace --all-features --no-deps --document-private-items
+  rustfmt:
+    name: rustfmt
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        # Not MSRV because its harder to jump between versions and people are
+        # more likely to have stable
+        toolchain: stable
+        components: rustfmt
+    - uses: Swatinem/rust-cache@v2
+    - name: Check formatting
+      run: cargo fmt --all -- --check
+  clippy:
+    name: clippy
+    runs-on: ubuntu-latest
+    permissions:
+      security-events: write # to upload sarif results
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: "1.70"  # MSRV
+        components: clippy
+    - uses: Swatinem/rust-cache@v2
+    - name: Install SARIF tools
+      run: cargo install clippy-sarif --version 0.3.4 --locked  # Held back due to msrv
+    - name: Install SARIF tools
+      run: cargo install sarif-fmt --version 0.3.4 --locked # Held back due to msrv
+    - name: Check
+      run: >
+        cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated
+        | clippy-sarif
+        | tee clippy-results.sarif
+        | sarif-fmt
+      continue-on-error: true
+    - name: Upload
+      uses: github/codeql-action/upload-sarif@v2
+      with:
+        sarif_file: clippy-results.sarif
+        wait-for-processing: true
+    - name: Report status
+      run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
new file mode 100644
index 0000000..d8c2d25
--- /dev/null
+++ b/.github/workflows/rust-next.yml
@@ -0,0 +1,59 @@
+name: rust-next
+
+permissions:
+  contents: read
+
+on:
+  schedule:
+  - cron: '1 1 1 * *'
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  test:
+    name: Test
+    strategy:
+      matrix:
+        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
+        rust: ["stable", "beta"]
+        include:
+        - os: ubuntu-latest
+          rust: "nightly"
+    continue-on-error: ${{ matrix.rust != 'stable' }}
+    runs-on: ${{ matrix.os }}
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: ${{ matrix.rust }}
+    - uses: Swatinem/rust-cache@v2
+    - name: Default features
+      run: cargo test --workspace
+    - name: All features
+      run: cargo test --workspace --all-features
+    - name: No-default features
+      run: cargo test --workspace --no-default-features
+  latest:
+    name: "Check latest dependencies"
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: stable
+    - uses: Swatinem/rust-cache@v2
+    - name: Update dependencues
+      run: cargo update
+    - name: Default features
+      run: cargo test --workspace --all-targets
+    - name: All features
+      run: cargo test --workspace --all-targets --all-features
+    - name: No-default features
+      run: cargo test --workspace --all-targets --no-default-features
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e186cc2..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-language: rust
-sudo: required
-dist: trusty
-addons:
-    apt:
-        packages:
-            - libssl-dev
-cache: cargo
-rust:
-  - stable
-  - beta
-  - nightly
-matrix:
-  allow_failures:
-    - rust: nightly
-
-before_cache: |
-  if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then
-    RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f
-  fi
-
-script:
-- cargo clean
-- cargo build
-- cargo test
-
-after_success: |
-  if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then
-    cargo tarpaulin --ciserver travis-ci --coveralls $TRAVIS_JOB_ID
-  fi
diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs
index ffc38f9..13f8543 100644
--- a/tests/fixtures_test.rs
+++ b/tests/fixtures_test.rs
@@ -18,6 +18,7 @@ fn read_fixture(src: &str) -> Result<Snippet<'_>, Box<dyn Error>> {
 }
 
 #[test]
+#[cfg(not(windows))] // HACK: Not working on windows due to a serde error
 fn test_fixtures() {
     for entry in glob("./tests/fixtures/no-color/**/*.toml").expect("Failed to read glob pattern") {
         let p = entry.expect("Error while getting an entry");

From 23e561537d795364e0fff729d2cdf6d97b214fe2 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 8 Nov 2023 09:56:18 -0600
Subject: [PATCH 046/302] chore(ci): Use cargo-deny

---
 .github/workflows/audit.yml |  49 +++++++++++++
 deny.toml                   | 140 ++++++++++++++++++++++++++++++++++++
 2 files changed, 189 insertions(+)
 create mode 100644 .github/workflows/audit.yml
 create mode 100644 deny.toml

diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
new file mode 100644
index 0000000..442e637
--- /dev/null
+++ b/.github/workflows/audit.yml
@@ -0,0 +1,49 @@
+name: Security audit
+
+permissions:
+  contents: read
+
+on:
+  pull_request:
+    paths:
+      - '**/Cargo.toml'
+      - '**/Cargo.lock'
+  push:
+    branches:
+    - master
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  security_audit:
+    permissions:
+      issues: write # to create issues (actions-rs/audit-check)
+      checks: write # to create check (actions-rs/audit-check)
+    runs-on: ubuntu-latest
+    # Prevent sudden announcement of a new advisory from failing ci:
+    continue-on-error: true
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - uses: actions-rs/audit-check@v1
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+
+  cargo_deny:
+    permissions:
+      issues: write # to create issues (actions-rs/audit-check)
+      checks: write # to create check (actions-rs/audit-check)
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        checks:
+          - bans licenses sources
+    steps:
+    - uses: actions/checkout@v4
+    - uses: EmbarkStudios/cargo-deny-action@v1
+      with:
+        command: check ${{ matrix.checks }}
+        rust-version: stable
diff --git a/deny.toml b/deny.toml
new file mode 100644
index 0000000..21fa937
--- /dev/null
+++ b/deny.toml
@@ -0,0 +1,140 @@
+# Note that all fields that take a lint level have these possible values:
+# * deny - An error will be produced and the check will fail
+# * warn - A warning will be produced, but the check will not fail
+# * allow - No warning or error will be produced, though in some cases a note
+# will be
+
+# This section is considered when running `cargo deny check advisories`
+# More documentation for the advisories section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
+[advisories]
+# The lint level for security vulnerabilities
+vulnerability = "deny"
+# The lint level for unmaintained crates
+unmaintained = "warn"
+# The lint level for crates that have been yanked from their source registry
+yanked = "warn"
+# The lint level for crates with security notices. Note that as of
+# 2019-12-17 there are no security notice advisories in
+# https://github.com/rustsec/advisory-db
+notice = "warn"
+# A list of advisory IDs to ignore. Note that ignored advisories will still
+# output a note when they are encountered.
+#
+# e.g. "RUSTSEC-0000-0000",
+ignore = [
+]
+
+# This section is considered when running `cargo deny check licenses`
+# More documentation for the licenses section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
+[licenses]
+unlicensed = "deny"
+# List of explicitly allowed licenses
+# See https://spdx.org/licenses/ for list of possible licenses
+# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
+allow = [
+    "MIT",
+    "MIT-0",
+    "Apache-2.0",
+    "BSD-3-Clause",
+    "MPL-2.0",
+    "Unicode-DFS-2016",
+    "CC0-1.0",
+    "ISC",
+]
+# List of explicitly disallowed licenses
+# See https://spdx.org/licenses/ for list of possible licenses
+# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
+deny = [
+]
+# Lint level for licenses considered copyleft
+copyleft = "deny"
+# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
+# * both - The license will be approved if it is both OSI-approved *AND* FSF
+# * either - The license will be approved if it is either OSI-approved *OR* FSF
+# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
+# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
+# * neither - This predicate is ignored and the default lint level is used
+allow-osi-fsf-free = "neither"
+# Lint level used when no other predicates are matched
+# 1. License isn't in the allow or deny lists
+# 2. License isn't copyleft
+# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
+default = "deny"
+# The confidence threshold for detecting a license from license text.
+# The higher the value, the more closely the license text must be to the
+# canonical license text of a valid SPDX license file.
+# [possible values: any between 0.0 and 1.0].
+confidence-threshold = 0.8
+# Allow 1 or more licenses on a per-crate basis, so that particular licenses
+# aren't accepted for every possible crate as with the normal allow list
+exceptions = [
+    # Each entry is the crate and version constraint, and its specific allow
+    # list
+    #{ allow = ["Zlib"], name = "adler32", version = "*" },
+]
+
+[licenses.private]
+# If true, ignores workspace crates that aren't published, or are only
+# published to private registries.
+# To see how to mark a crate as unpublished (to the official registry),
+# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
+ignore = true
+
+# This section is considered when running `cargo deny check bans`.
+# More documentation about the 'bans' section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
+[bans]
+# Lint level for when multiple versions of the same crate are detected
+multiple-versions = "warn"
+# Lint level for when a crate version requirement is `*`
+wildcards = "warn"
+# The graph highlighting used when creating dotgraphs for crates
+# with multiple versions
+# * lowest-version - The path to the lowest versioned duplicate is highlighted
+# * simplest-path - The path to the version with the fewest edges is highlighted
+# * all - Both lowest-version and simplest-path are used
+highlight = "all"
+# The default lint level for `default` features for crates that are members of
+# the workspace that is being checked. This can be overridden by allowing/denying
+# `default` on a crate-by-crate basis if desired.
+workspace-default-features = "allow"
+# The default lint level for `default` features for external crates that are not
+# members of the workspace. This can be overridden by allowing/denying `default`
+# on a crate-by-crate basis if desired.
+external-default-features = "allow"
+# List of crates that are allowed. Use with care!
+allow = [
+    #{ name = "ansi_term", version = "=0.11.0" },
+]
+# List of crates to deny
+deny = [
+    # Each entry the name of a crate and a version range. If version is
+    # not specified, all versions will be matched.
+    #{ name = "ansi_term", version = "=0.11.0" },
+    #
+    # Wrapper crates can optionally be specified to allow the crate when it
+    # is a direct dependency of the otherwise banned crate
+    #{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
+]
+
+# This section is considered when running `cargo deny check sources`.
+# More documentation about the 'sources' section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
+[sources]
+# Lint level for what to happen when a crate from a crate registry that is not
+# in the allow list is encountered
+unknown-registry = "deny"
+# Lint level for what to happen when a crate from a git repository that is not
+# in the allow list is encountered
+unknown-git = "deny"
+# List of URLs for allowed crate registries. Defaults to the crates.io index
+# if not specified. If it is specified but empty, no registries are allowed.
+allow-registry = ["https://github.com/rust-lang/crates.io-index"]
+# List of URLs for allowed Git repositories
+allow-git = []
+
+[sources.allow-org]
+# 1 or more github.com organizations to allow git sources for
+github = []

From 64f3433f97efc37a08f1e8deecafb4aa03fde806 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 8 Nov 2023 13:31:06 -0700
Subject: [PATCH 047/302] chore: Remove unneeded badges

---
 Cargo.toml | 3 ---
 README.md  | 1 -
 2 files changed, 4 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index e4a2351..c051c28 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,9 +10,6 @@ readme = "README.md"
 keywords = ["code", "analysis", "ascii", "errors", "debug"]
 
 [badges]
-travis-ci = { repository = "rust-lang/annotate-snippets-rs", branch = "master" }
-coveralls = { repository = "rust-lang/annotate-snippets-rs", branch = "master", service = "github" }
-
 maintenance = { status = "actively-developed" }
 
 [dependencies]
diff --git a/README.md b/README.md
index f60c96e..2e5a20f 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,6 @@
 
 [![crates.io](https://img.shields.io/crates/v/annotate-snippets.svg)](https://crates.io/crates/annotate-snippets)
 ![build status](https://github.com/rust-lang/annotate-snippets-rs/actions/workflows/ci.yml/badge.svg)
-[![Coverage Status](https://coveralls.io/repos/github/rust-lang/annotate-snippets-rs/badge.svg?branch=master)](https://coveralls.io/github/rust-lang/annotate-snippets-rs?branch=master)
 
 The library helps visualize meta information annotating source code slices.
 It takes a data structure called `Snippet` on the input and produces a `String`

From fba7bfef0f0ea5c47fdc6d640b4b615b001f85e4 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 8 Nov 2023 13:35:03 -0700
Subject: [PATCH 048/302] chore: Update to edition 2021

---
 Cargo.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index c051c28..e9bb689 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "annotate-snippets"
 version = "0.9.2"
-edition = "2018"
+edition = "2021"
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]
 description = "Library for building code annotations"
 license = "Apache-2.0/MIT"

From bac76bfc809f1d3b975e34c3438c2950ffebaa2e Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 8 Nov 2023 15:29:57 -0700
Subject: [PATCH 049/302] chore: Set MSRV to `1.70.0`

---
 Cargo.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Cargo.toml b/Cargo.toml
index e9bb689..904c442 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
 name = "annotate-snippets"
 version = "0.9.2"
 edition = "2021"
+rust-version = "1.70.0"
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]
 description = "Library for building code annotations"
 license = "Apache-2.0/MIT"

From 9770283a6d3bfc5957f76eb565f6cce43338fb3d Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 8 Nov 2023 15:30:48 -0700
Subject: [PATCH 050/302] chore: Sort dependencies alphabetically

---
 Cargo.toml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 904c442..79348d6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,12 +18,12 @@ unicode-width = "0.1"
 yansi-term = { version = "0.1", optional = true }
 
 [dev-dependencies]
+criterion = "0.3"
+difference = "2.0"
 glob = "0.3"
-toml = "0.5"
 serde = { version = "1.0", features = ["derive"] }
-difference = "2.0"
+toml = "0.5"
 yansi-term = "0.1"
-criterion = "0.3"
 
 [[bench]]
 name = "simple"

From 9bb9cadbb01bef952ed7c615bdd1ffd4de8b6dc5 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 8 Nov 2023 15:32:17 -0700
Subject: [PATCH 051/302] chore: Fully specify dependency versions

---
 Cargo.toml | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 79348d6..44c7f2d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,16 +14,16 @@ keywords = ["code", "analysis", "ascii", "errors", "debug"]
 maintenance = { status = "actively-developed" }
 
 [dependencies]
-unicode-width = "0.1"
-yansi-term = { version = "0.1", optional = true }
+unicode-width = "0.1.11"
+yansi-term = { version = "0.1.2", optional = true }
 
 [dev-dependencies]
-criterion = "0.3"
-difference = "2.0"
-glob = "0.3"
-serde = { version = "1.0", features = ["derive"] }
-toml = "0.5"
-yansi-term = "0.1"
+criterion = "0.3.6"
+difference = "2.0.0"
+glob = "0.3.1"
+serde = { version = "1.0.192", features = ["derive"] }
+toml = "0.5.11"
+yansi-term = "0.1.2"
 
 [[bench]]
 name = "simple"

From 7f8322ca8f1e359d558968382d8c37c6a9f62839 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 8 Nov 2023 15:34:39 -0700
Subject: [PATCH 052/302] chore: Update criterion version

---
 Cargo.lock | 243 +++++++++++++++++++++++++++++++++++++----------------
 Cargo.toml |   2 +-
 2 files changed, 172 insertions(+), 73 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index bd6a75d..c15507c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,6 +11,12 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
 [[package]]
 name = "annotate-snippets"
 version = "0.9.2"
@@ -25,15 +31,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "atty"
-version = "0.2.14"
+name = "anstyle"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi",
- "libc",
- "winapi",
-]
+checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
 
 [[package]]
 name = "autocfg"
@@ -43,9 +44,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "bitflags"
-version = "1.3.2"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
 
 [[package]]
 name = "bumpalo"
@@ -65,37 +66,78 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "ciborium"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
 [[package]]
 name = "clap"
-version = "2.34.0"
+version = "4.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
 dependencies = [
- "bitflags",
- "textwrap",
- "unicode-width",
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
+dependencies = [
+ "anstyle",
+ "clap_lex",
 ]
 
+[[package]]
+name = "clap_lex"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+
 [[package]]
 name = "criterion"
-version = "0.3.6"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
+checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
 dependencies = [
- "atty",
+ "anes",
  "cast",
+ "ciborium",
  "clap",
  "criterion-plot",
- "csv",
+ "is-terminal",
  "itertools",
- "lazy_static",
  "num-traits",
+ "once_cell",
  "oorandom",
  "plotters",
  "rayon",
  "regex",
  "serde",
- "serde_cbor",
  "serde_derive",
  "serde_json",
  "tinytemplate",
@@ -104,9 +146,9 @@ dependencies = [
 
 [[package]]
 name = "criterion-plot"
-version = "0.4.5"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
 dependencies = [
  "cast",
  "itertools",
@@ -145,27 +187,6 @@ dependencies = [
  "cfg-if",
 ]
 
-[[package]]
-name = "csv"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
-dependencies = [
- "csv-core",
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "csv-core"
-version = "0.1.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
-dependencies = [
- "memchr",
-]
-
 [[package]]
 name = "difference"
 version = "2.0.0"
@@ -178,6 +199,16 @@ version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
 
+[[package]]
+name = "errno"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
 [[package]]
 name = "glob"
 version = "0.3.1"
@@ -192,11 +223,19 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
 
 [[package]]
 name = "hermit-abi"
-version = "0.1.19"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
 dependencies = [
- "libc",
+ "hermit-abi",
+ "rustix",
+ "windows-sys",
 ]
 
 [[package]]
@@ -223,18 +262,18 @@ dependencies = [
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-
 [[package]]
 name = "libc"
 version = "0.2.150"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
 
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+
 [[package]]
 name = "log"
 version = "0.4.20"
@@ -372,6 +411,19 @@ version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
+[[package]]
+name = "rustix"
+version = "0.38.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.15"
@@ -402,16 +454,6 @@ dependencies = [
  "serde_derive",
 ]
 
-[[package]]
-name = "serde_cbor"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
-dependencies = [
- "half",
- "serde",
-]
-
 [[package]]
 name = "serde_derive"
 version = "1.0.192"
@@ -445,15 +487,6 @@ dependencies = [
  "unicode-ident",
 ]
 
-[[package]]
-name = "textwrap"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
-dependencies = [
- "unicode-width",
-]
-
 [[package]]
 name = "tinytemplate"
 version = "1.2.1"
@@ -590,6 +623,72 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
 [[package]]
 name = "yansi-term"
 version = "0.1.2"
diff --git a/Cargo.toml b/Cargo.toml
index 44c7f2d..497c1cd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,7 @@ unicode-width = "0.1.11"
 yansi-term = { version = "0.1.2", optional = true }
 
 [dev-dependencies]
-criterion = "0.3.6"
+criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
 serde = { version = "1.0.192", features = ["derive"] }

From e0083cd1debe1a70590e5ba2aef78f9ce7d5a3c9 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 9 Nov 2023 15:59:17 -0600
Subject: [PATCH 053/302] chore(ci): Validate basics in CI / pre-commit

---
 .github/workflows/pre-commit.yml | 23 +++++++++++++++++++++++
 .pre-commit-config.yaml          | 16 ++++++++++++++++
 2 files changed, 39 insertions(+)
 create mode 100644 .github/workflows/pre-commit.yml
 create mode 100644 .pre-commit-config.yaml

diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 0000000..8044750
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,23 @@
+name: pre-commit
+
+permissions: {} # none
+
+on:
+  pull_request:
+  push:
+    branches: [main]
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  pre-commit:
+    permissions:
+      contents: read
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v4
+    - uses: actions/setup-python@v4
+    - uses: pre-commit/action@v3.0.0
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..7d2fdd9
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,16 @@
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.5.0
+    hooks:
+    - id: check-yaml
+      stages: [commit]
+    - id: check-json
+      stages: [commit]
+    - id: check-toml
+      stages: [commit]
+    - id: check-merge-conflict
+      stages: [commit]
+    - id: check-case-conflict
+      stages: [commit]
+    - id: detect-private-key
+      stages: [commit]

From 0da8be6b27b0837c512125c923d45648679a9479 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 9 Nov 2023 16:01:54 -0600
Subject: [PATCH 054/302] docs: Fix typos

---
 src/display_list/structs.rs | 4 ++--
 tests/formatter.rs          | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs
index 7941d5f..5a31d1e 100644
--- a/src/display_list/structs.rs
+++ b/src/display_list/structs.rs
@@ -225,7 +225,7 @@ pub enum DisplayRawLine<'a> {
         source_aligned: bool,
         /// If set to `true`, only the label of the `Annotation` will be
         /// displayed. It allows for a multiline annotation to be aligned
-        /// without displaing the meta information (`type` and `id`) to be
+        /// without displaying the meta information (`type` and `id`) to be
         /// displayed on each line.
         continuation: bool,
     },
@@ -283,7 +283,7 @@ pub enum DisplayMarkType {
 /// There are several ways to uses this information when formatting the `DisplayList`:
 ///
 /// * An annotation may display the name of the type like `error` or `info`.
-/// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`.
+/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`.
 /// * `ColorStylesheet` may use different colors for different annotations.
 #[derive(Debug, Clone, PartialEq)]
 pub enum DisplayAnnotationType {
diff --git a/tests/formatter.rs b/tests/formatter.rs
index b1392a1..f95b002 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -513,7 +513,7 @@ fn test_raw_origin_initial_pos_anon_lines() {
         header_type: DisplayHeaderType::Initial,
     })]);
 
-    // Using anonymized_line_numbers should not affect the inital position
+    // Using anonymized_line_numbers should not affect the initial position
     dl.anonymized_line_numbers = true;
     assert_eq!(dl.to_string(), "--> src/test.rs:23:15");
 }

From cf579ff37061f25d6d95d75ae4d4e059e26a990b Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 9 Nov 2023 16:01:25 -0600
Subject: [PATCH 055/302] chore(ci): Check typos

---
 .github/workflows/spelling.yml | 21 +++++++++++++++++++++
 .pre-commit-config.yaml        |  5 +++++
 2 files changed, 26 insertions(+)
 create mode 100644 .github/workflows/spelling.yml

diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
new file mode 100644
index 0000000..12f7585
--- /dev/null
+++ b/.github/workflows/spelling.yml
@@ -0,0 +1,21 @@
+name: Spelling
+
+permissions:
+  contents: read
+
+on: [pull_request]
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  spelling:
+    name: Spell Check with Typos
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout Actions Repository
+      uses: actions/checkout@v4
+    - name: Spell Check Repo
+      uses: crate-ci/typos@master
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7d2fdd9..5340e09 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -14,3 +14,8 @@ repos:
       stages: [commit]
     - id: detect-private-key
       stages: [commit]
+  - repo: https://github.com/crate-ci/typos
+    rev: v1.16.20
+    hooks:
+    - id: typos
+      stages: [commit]

From e4e731c25c24dc124cd086d32c8d9ed2871bff5e Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 9 Nov 2023 16:05:16 -0600
Subject: [PATCH 056/302] chore(ci): Encourage conventional commits

This would help with writing changelogs
---
 .github/workflows/committed.yml | 24 ++++++++++++++++++++++++
 .pre-commit-config.yaml         |  5 +++++
 committed.toml                  |  3 +++
 3 files changed, 32 insertions(+)
 create mode 100644 .github/workflows/committed.yml
 create mode 100644 committed.toml

diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml
new file mode 100644
index 0000000..0462558
--- /dev/null
+++ b/.github/workflows/committed.yml
@@ -0,0 +1,24 @@
+# Not run as part of pre-commit checks because they don't handle sending the correct commit
+# range to `committed`
+name: Lint Commits
+on: [pull_request]
+
+permissions:
+  contents: read
+
+env:
+  RUST_BACKTRACE: 1
+  CARGO_TERM_COLOR: always
+  CLICOLOR: 1
+
+jobs:
+  committed:
+    name: Lint Commits
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout Actions Repository
+      uses: actions/checkout@v4
+      with:
+        fetch-depth: 0
+    - name: Lint Commits
+      uses: crate-ci/committed@master
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5340e09..68db968 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -19,3 +19,8 @@ repos:
     hooks:
     - id: typos
       stages: [commit]
+  - repo: https://github.com/crate-ci/committed
+    rev: v1.0.20
+    hooks:
+    - id: committed
+      stages: [commit-msg]
diff --git a/committed.toml b/committed.toml
new file mode 100644
index 0000000..4211ae3
--- /dev/null
+++ b/committed.toml
@@ -0,0 +1,3 @@
+style="conventional"
+ignore_author_re="(dependabot|renovate)"
+merge_commit = false

From d384abe77dbf19e1c5206944ef68f29f1d63272d Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 9 Nov 2023 16:15:18 -0600
Subject: [PATCH 057/302] chore(ci): Auto-update versions

Once infra enables RenovateBot, this will
- Create individual PRs for breaking changes
- Combined PRs for compatible changes

This also auto-updates MSRV, like cargo and clap do, to avoid stagnation
and people getting the wrong impression of the MSRV policy from that
stagnation.  This also avoids contributors being hesitant about what
justifies updating MSRV.
---
 .github/renovate.json5   | 107 +++++++++++++++++++++++++++++++++++++++
 .github/workflows/ci.yml |   2 +-
 Cargo.toml               |   2 +-
 3 files changed, 109 insertions(+), 2 deletions(-)
 create mode 100644 .github/renovate.json5

diff --git a/.github/renovate.json5 b/.github/renovate.json5
new file mode 100644
index 0000000..21d082f
--- /dev/null
+++ b/.github/renovate.json5
@@ -0,0 +1,107 @@
+{
+  schedule: [
+    'before 5am on the first day of the month',
+  ],
+  semanticCommits: 'enabled',
+  configMigration: true,
+  dependencyDashboard: true,
+  customManagers: [
+    {
+      customType: 'regex',
+      fileMatch: [
+        '^rust-toolchain\\.toml$',
+        'Cargo.toml$',
+        'clippy.toml$',
+        '\\.clippy.toml$',
+        '^\\.github/workflows/ci.yml$',
+        '^\\.github/workflows/rust-next.yml$',
+      ],
+      matchStrings: [
+        'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)',
+        '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV',
+      ],
+      depNameTemplate: 'rust',
+      packageNameTemplate: 'rust-lang/rust',
+      datasourceTemplate: 'github-releases',
+    },
+  ],
+  packageRules: [
+    {
+      commitMessageTopic: 'MSRV',
+      matchManagers: [
+        'regex',
+      ],
+      matchPackageNames: [
+        'rust',
+      ],
+      minimumReleaseAge: '84 days',  // 2 releases back * 6 weeks per release * 7 days per week
+      internalChecksFilter: 'strict',
+      extractVersion: '^(?<version>\\d+\\.\\d+)',  // Drop the patch version
+      schedule: [
+        '* * * * *',
+      ],
+    },
+    // Goals:
+    // - Keep version reqs low, ignoring compatible normal/build dependencies
+    // - Take advantage of latest dev-dependencies
+    // - Rollup safe upgrades to reduce CI runner load
+    // - Help keep number of versions down by always using latest breaking change
+    // - Have lockfile and manifest in-sync
+    {
+      matchManagers: [
+        'cargo',
+      ],
+      matchDepTypes: [
+        'build-dependencies',
+        'dependencies',
+      ],
+      matchCurrentVersion: '>=0.1.0',
+      matchUpdateTypes: [
+        'patch',
+      ],
+      enabled: false,
+    },
+    {
+      matchManagers: [
+        'cargo',
+      ],
+      matchDepTypes: [
+        'build-dependencies',
+        'dependencies',
+      ],
+      matchCurrentVersion: '>=1.0.0',
+      matchUpdateTypes: [
+        'minor',
+      ],
+      enabled: false,
+    },
+    {
+      matchManagers: [
+        'cargo',
+      ],
+      matchDepTypes: [
+        'dev-dependencies',
+      ],
+      matchCurrentVersion: '>=0.1.0',
+      matchUpdateTypes: [
+        'patch',
+      ],
+      automerge: true,
+      groupName: 'compatible (dev)',
+    },
+    {
+      matchManagers: [
+        'cargo',
+      ],
+      matchDepTypes: [
+        'dev-dependencies',
+      ],
+      matchCurrentVersion: '>=1.0.0',
+      matchUpdateTypes: [
+        'minor',
+      ],
+      automerge: true,
+      groupName: 'compatible (dev)',
+    },
+  ],
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 50c2ddf..3a3d812 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -49,7 +49,7 @@ jobs:
     - name: No-default features
       run: cargo test --workspace --no-default-features
   msrv:
-    name: "Check MSRV: 1.70"
+    name: "Check MSRV: 1.70"  # MSRV
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
diff --git a/Cargo.toml b/Cargo.toml
index 497c1cd..8666651 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
 name = "annotate-snippets"
 version = "0.9.2"
 edition = "2021"
-rust-version = "1.70.0"
+rust-version = "1.70"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]
 description = "Library for building code annotations"
 license = "Apache-2.0/MIT"

From 7f0986fae45864d4836ed6b21783841d234c46d1 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 9 Nov 2023 16:27:00 -0600
Subject: [PATCH 058/302] docs: Match clap/cargo for changelog style

---
 CHANGELOG.md | 74 +++++++++++++++++++++++++++++++---------------------
 1 file changed, 44 insertions(+), 30 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c66ad41..c1ab962 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,44 +1,58 @@
-# Changelog
- 
-## Unreleased
- 
-  - …
+# Change Log
+All notable changes to this project will be documented in this file.
 
-## annotate-snippets 0.9.2 (October 30, 2023)
+The format is based on [Keep a Changelog](http://keepachangelog.com/)
+and this project adheres to [Semantic Versioning](http://semver.org/).
+
+## [Unreleased] - ReleaseDate
+ 
+## [0.9.2] - October 30, 2023
 
-  - Remove parsing of __ in title strings, fixes (#53)
-  - Origin line number is not correct when using a slice with fold: true (#52)
+- Remove parsing of __ in title strings, fixes (#53)
+- Origin line number is not correct when using a slice with fold: true (#52)
 
-## annotate-snippets 0.9.1 (September 4, 2021)
+## [0.9.1] - September 4, 2021
 
-  - Fix character split when strip code. (#37)
-  - Fix off by one error in multiline highlighting. (#42)
-  - Fix display of annotation for double width characters. (#46)
+- Fix character split when strip code. (#37)
+- Fix off by one error in multiline highlighting. (#42)
+- Fix display of annotation for double width characters. (#46)
 
-## annotate-snippets 0.9.0 (June 28, 2020)
+## [0.9.0] - June 28, 2020
 
-  - Add strip code to the left and right of long lines. (#36)
+- Add strip code to the left and right of long lines. (#36)
 
-## annotate-snippets 0.8.0 (April 14, 2020)
+## [0.8.0] - April 14, 2020
 
-  - Replace `ansi_term` with `yansi-term` for improved performance. (#30)
-  - Turn `Snippet` and `Slice` to work on borrowed slices, rather than Strings. (#32)
-  - Fix `\r\n` end of lines. (#29)
+- Replace `ansi_term` with `yansi-term` for improved performance. (#30)
+- Turn `Snippet` and `Slice` to work on borrowed slices, rather than Strings. (#32)
+- Fix `\r\n` end of lines. (#29)
 
-## annotate-snippets 0.7.0 (March 30, 2020)
+## [0.7.0] - March 30, 2020
 
-  - Refactor API to use `fmt::Display` (#27)
-  - Fix SourceAnnotation range (#27)
-  - Fix column numbers (#22)
-  - Derive `PartialEq` for `AnnotationType` (#19)
-  - Update `ansi_term` to 0.12.
+- Refactor API to use `fmt::Display` (#27)
+- Fix SourceAnnotation range (#27)
+- Fix column numbers (#22)
+- Derive `PartialEq` for `AnnotationType` (#19)
+- Update `ansi_term` to 0.12.
 
-## annotate-snippets 0.6.1 (July 23, 2019)
+## [0.6.1] - July 23, 2019
 
-  - Fix too many anonymized line numbers (#5)
+- Fix too many anonymized line numbers (#5)
  
-## annotate-snippets 0.6.0 (June 26, 2019)
+## [0.6.0] - June 26, 2019
  
-  - Add an option to anonymize line numbers (#3)
-  - Transition the crate to rust-lang org.
-  - Update the syntax to Rust 2018 idioms. (#4)
+- Add an option to anonymize line numbers (#3)
+- Transition the crate to rust-lang org.
+- Update the syntax to Rust 2018 idioms. (#4)
+
+<!-- next-url -->
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...HEAD
+[0.9.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.1...0.9.2
+[0.9.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.0...0.9.1
+[0.9.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.8.0...0.9.0
+[0.8.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.7.0...0.8.0
+[0.7.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.6.1...0.7.0
+[0.6.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.6.0...0.6.1
+[0.6.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.5.0...0.6.0
+[0.5.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.1.0...0.5.0
+[0.1.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/6015d08d7d10151c126c6a70c14f234c0c01b50e...0.1.0

From bc59c126ea08bb01c0425bb6e82377b8a3bc9e6e Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sun, 12 Nov 2023 12:30:48 -0700
Subject: [PATCH 059/302] refactor: Move impls near definitions

---
 src/display_list/from_snippet.rs |  580 -------------
 src/display_list/mod.rs          | 1317 +++++++++++++++++++++++++++++-
 src/display_list/structs.rs      |  308 -------
 src/formatter/mod.rs             |  437 +---------
 4 files changed, 1316 insertions(+), 1326 deletions(-)
 delete mode 100644 src/display_list/from_snippet.rs
 delete mode 100644 src/display_list/structs.rs

diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs
deleted file mode 100644
index 4734147..0000000
--- a/src/display_list/from_snippet.rs
+++ /dev/null
@@ -1,580 +0,0 @@
-//! Trait for converting `Snippet` to `DisplayList`.
-use super::*;
-use crate::{formatter::get_term_style, snippet};
-
-struct CursorLines<'a>(&'a str);
-
-impl<'a> CursorLines<'a> {
-    fn new(src: &str) -> CursorLines<'_> {
-        CursorLines(src)
-    }
-}
-
-enum EndLine {
-    Eof = 0,
-    Crlf = 1,
-    Lf = 2,
-}
-
-impl<'a> Iterator for CursorLines<'a> {
-    type Item = (&'a str, EndLine);
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.0.is_empty() {
-            None
-        } else {
-            self.0
-                .find('\n')
-                .map(|x| {
-                    let ret = if 0 < x {
-                        if self.0.as_bytes()[x - 1] == b'\r' {
-                            (&self.0[..x - 1], EndLine::Lf)
-                        } else {
-                            (&self.0[..x], EndLine::Crlf)
-                        }
-                    } else {
-                        ("", EndLine::Crlf)
-                    };
-                    self.0 = &self.0[x + 1..];
-                    ret
-                })
-                .or_else(|| {
-                    let ret = Some((self.0, EndLine::Eof));
-                    self.0 = "";
-                    ret
-                })
-        }
-    }
-}
-
-fn format_label(
-    label: Option<&str>,
-    style: Option<DisplayTextStyle>,
-) -> Vec<DisplayTextFragment<'_>> {
-    let mut result = vec![];
-    if let Some(label) = label {
-        let element_style = style.unwrap_or(DisplayTextStyle::Regular);
-        result.push(DisplayTextFragment {
-            content: label,
-            style: element_style,
-        });
-    }
-    result
-}
-
-fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> {
-    let label = annotation.label.unwrap_or_default();
-    DisplayLine::Raw(DisplayRawLine::Annotation {
-        annotation: Annotation {
-            annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
-            id: annotation.id,
-            label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
-        },
-        source_aligned: false,
-        continuation: false,
-    })
-}
-
-fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> {
-    let mut result = vec![];
-    let label = annotation.label.unwrap_or_default();
-    for (i, line) in label.lines().enumerate() {
-        result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
-                id: None,
-                label: format_label(Some(line), None),
-            },
-            source_aligned: true,
-            continuation: i != 0,
-        }));
-    }
-    result
-}
-
-fn format_slice(
-    slice: snippet::Slice<'_>,
-    is_first: bool,
-    has_footer: bool,
-    margin: Option<Margin>,
-) -> Vec<DisplayLine<'_>> {
-    let main_range = slice.annotations.get(0).map(|x| x.range.0);
-    let origin = slice.origin;
-    let need_empty_header = origin.is_some() || is_first;
-    let mut body = format_body(slice, need_empty_header, has_footer, margin);
-    let header = format_header(origin, main_range, &body, is_first);
-    let mut result = vec![];
-
-    if let Some(header) = header {
-        result.push(header);
-    }
-    result.append(&mut body);
-    result
-}
-
-#[inline]
-// TODO: option_zip
-fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> {
-    a.and_then(|a| b.map(|b| (a, b)))
-}
-
-fn format_header<'a>(
-    origin: Option<&'a str>,
-    main_range: Option<usize>,
-    body: &[DisplayLine<'_>],
-    is_first: bool,
-) -> Option<DisplayLine<'a>> {
-    let display_header = if is_first {
-        DisplayHeaderType::Initial
-    } else {
-        DisplayHeaderType::Continuation
-    };
-
-    if let Some((main_range, path)) = zip_opt(main_range, origin) {
-        let mut col = 1;
-        let mut line_offset = 1;
-
-        for item in body {
-            if let DisplayLine::Source {
-                line: DisplaySourceLine::Content { range, .. },
-                lineno,
-                ..
-            } = item
-            {
-                if main_range >= range.0 && main_range <= range.1 {
-                    col = main_range - range.0 + 1;
-                    line_offset = lineno.unwrap_or(1);
-                    break;
-                }
-            }
-        }
-
-        return Some(DisplayLine::Raw(DisplayRawLine::Origin {
-            path,
-            pos: Some((line_offset, col)),
-            header_type: display_header,
-        }));
-    }
-
-    if let Some(path) = origin {
-        return Some(DisplayLine::Raw(DisplayRawLine::Origin {
-            path,
-            pos: None,
-            header_type: display_header,
-        }));
-    }
-
-    None
-}
-
-fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
-    enum Line {
-        Fold(usize),
-        Source(usize),
-    }
-
-    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 { .. },
-                ..
-            } => {
-                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));
-                    }
-                } else {
-                    for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) {
-                        lines.push(Line::Source(i));
-                    }
-                }
-                no_annotation_lines_counter = 0;
-            }
-            DisplayLine::Source { .. } => {
-                no_annotation_lines_counter += 1;
-                continue;
-            }
-            _ => {
-                no_annotation_lines_counter += 1;
-            }
-        }
-        lines.push(Line::Source(idx));
-    }
-
-    let mut new_body = vec![];
-    let mut removed = 0;
-    for line in lines {
-        match line {
-            Line::Source(i) => {
-                new_body.push(body.remove(i - removed));
-                removed += 1;
-            }
-            Line::Fold(i) => {
-                if let DisplayLine::Source {
-                    line: DisplaySourceLine::Annotation { .. },
-                    ref inline_marks,
-                    ..
-                } = body.get(i - removed).unwrap()
-                {
-                    new_body.push(DisplayLine::Fold {
-                        inline_marks: inline_marks.clone(),
-                    })
-                } else {
-                    unreachable!()
-                }
-            }
-        }
-    }
-
-    new_body
-}
-
-fn format_body(
-    slice: snippet::Slice<'_>,
-    need_empty_header: bool,
-    has_footer: bool,
-    margin: Option<Margin>,
-) -> Vec<DisplayLine<'_>> {
-    let source_len = slice.source.chars().count();
-    if let Some(bigger) = slice.annotations.iter().find_map(|x| {
-        if source_len < x.range.1 {
-            Some(x.range)
-        } else {
-            None
-        }
-    }) {
-        panic!(
-            "SourceAnnotation range `{:?}` is bigger than source length `{}`",
-            bigger, source_len
-        )
-    }
-
-    let mut body = vec![];
-    let mut current_line = slice.line_start;
-    let mut current_index = 0;
-    let mut line_info = vec![];
-
-    struct LineInfo {
-        line_start_index: usize,
-        line_end_index: usize,
-        // How many spaces each character in the line take up when displayed
-        char_widths: Vec<usize>,
-    }
-
-    for (line, end_line) in CursorLines::new(slice.source) {
-        let line_length = line.chars().count();
-        let line_range = (current_index, current_index + line_length);
-        let char_widths = line
-            .chars()
-            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-            .chain(std::iter::once(1)) // treat the end of line as single-width
-            .collect::<Vec<_>>();
-        body.push(DisplayLine::Source {
-            lineno: Some(current_line),
-            inline_marks: vec![],
-            line: DisplaySourceLine::Content {
-                text: line,
-                range: line_range,
-            },
-        });
-        line_info.push(LineInfo {
-            line_start_index: line_range.0,
-            line_end_index: line_range.1,
-            char_widths,
-        });
-        current_line += 1;
-        current_index += line_length + end_line as usize;
-    }
-
-    let mut annotation_line_count = 0;
-    let mut annotations = slice.annotations;
-    for (
-        idx,
-        LineInfo {
-            line_start_index,
-            line_end_index,
-            char_widths,
-        },
-    ) in line_info.into_iter().enumerate()
-    {
-        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 annotation_type = match annotation.annotation_type {
-                snippet::AnnotationType::Error => DisplayAnnotationType::None,
-                snippet::AnnotationType::Warning => DisplayAnnotationType::None,
-                _ => DisplayAnnotationType::from(annotation.annotation_type),
-            };
-            match annotation.range {
-                (start, _) if start > line_end_index => true,
-                (start, end)
-                    if start >= line_start_index && end <= line_end_index
-                        || start == line_end_index && end - start <= 1 =>
-                {
-                    let annotation_start_col = char_widths
-                        .iter()
-                        .take(start - line_start_index)
-                        .sum::<usize>()
-                        - margin_left;
-                    let annotation_end_col = char_widths
-                        .iter()
-                        .take(end - line_start_index)
-                        .sum::<usize>()
-                        - 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(Some(annotation.label), None),
-                                },
-                                range,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
-                                annotation_part: DisplayAnnotationPart::Standalone,
-                            },
-                        },
-                    );
-                    annotation_line_count += 1;
-                    false
-                }
-                (start, end)
-                    if start >= line_start_index
-                        && start <= line_end_index
-                        && end > line_end_index =>
-                {
-                    if start - line_start_index == 0 {
-                        if let DisplayLine::Source {
-                            ref mut inline_marks,
-                            ..
-                        } = body[body_idx]
-                        {
-                            inline_marks.push(DisplayMark {
-                                mark_type: DisplayMarkType::AnnotationStart,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
-                            });
-                        }
-                    } else {
-                        let annotation_start_col = char_widths
-                            .iter()
-                            .take(start - line_start_index)
-                            .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.annotation_type,
-                                    ),
-                                    annotation_part: DisplayAnnotationPart::MultilineStart,
-                                },
-                            },
-                        );
-                        annotation_line_count += 1;
-                    }
-                    true
-                }
-                (start, end) if start < line_start_index && end > line_end_index => {
-                    if let DisplayLine::Source {
-                        ref mut inline_marks,
-                        ..
-                    } = body[body_idx]
-                    {
-                        inline_marks.push(DisplayMark {
-                            mark_type: DisplayMarkType::AnnotationThrough,
-                            annotation_type: DisplayAnnotationType::from(
-                                annotation.annotation_type,
-                            ),
-                        });
-                    }
-                    true
-                }
-                (start, end)
-                    if start < line_start_index
-                        && end >= line_start_index
-                        && end <= line_end_index =>
-                {
-                    if let DisplayLine::Source {
-                        ref mut inline_marks,
-                        ..
-                    } = body[body_idx]
-                    {
-                        inline_marks.push(DisplayMark {
-                            mark_type: DisplayMarkType::AnnotationThrough,
-                            annotation_type: DisplayAnnotationType::from(
-                                annotation.annotation_type,
-                            ),
-                        });
-                    }
-
-                    let end_mark = char_widths
-                        .iter()
-                        .take(end - line_start_index)
-                        .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.annotation_type,
-                                ),
-                            }],
-                            line: DisplaySourceLine::Annotation {
-                                annotation: Annotation {
-                                    annotation_type,
-                                    id: None,
-                                    label: format_label(Some(annotation.label), None),
-                                },
-                                range,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
-                                annotation_part: DisplayAnnotationPart::MultilineEnd,
-                            },
-                        },
-                    );
-                    annotation_line_count += 1;
-                    false
-                }
-                _ => true,
-            }
-        });
-    }
-
-    if slice.fold {
-        body = fold_body(body);
-    }
-
-    if need_empty_header {
-        body.insert(
-            0,
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-        );
-    }
-
-    if has_footer {
-        body.push(DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Empty,
-        });
-    } else if let Some(DisplayLine::Source { .. }) = body.last() {
-        body.push(DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Empty,
-        });
-    }
-    body
-}
-
-impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
-    fn from(
-        snippet::Snippet {
-            title,
-            footer,
-            slices,
-            opt,
-        }: snippet::Snippet<'a>,
-    ) -> DisplayList<'a> {
-        let mut body = vec![];
-        if let Some(annotation) = title {
-            body.push(format_title(annotation));
-        }
-
-        for (idx, slice) in slices.into_iter().enumerate() {
-            body.append(&mut format_slice(
-                slice,
-                idx == 0,
-                !footer.is_empty(),
-                opt.margin,
-            ));
-        }
-
-        for annotation in footer {
-            body.append(&mut format_annotation(annotation));
-        }
-
-        let FormatOptions {
-            color,
-            anonymized_line_numbers,
-            margin,
-        } = opt;
-
-        Self {
-            body,
-            stylesheet: get_term_style(color),
-            anonymized_line_numbers,
-            margin,
-        }
-    }
-}
-
-impl From<snippet::AnnotationType> for DisplayAnnotationType {
-    fn from(at: snippet::AnnotationType) -> Self {
-        match at {
-            snippet::AnnotationType::Error => DisplayAnnotationType::Error,
-            snippet::AnnotationType::Warning => DisplayAnnotationType::Warning,
-            snippet::AnnotationType::Info => DisplayAnnotationType::Info,
-            snippet::AnnotationType::Note => DisplayAnnotationType::Note,
-            snippet::AnnotationType::Help => DisplayAnnotationType::Help,
-        }
-    }
-}
diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs
index 224a9f5..1ad328a 100644
--- a/src/display_list/mod.rs
+++ b/src/display_list/mod.rs
@@ -31,7 +31,1318 @@
 //! styling.
 //!
 //! The above snippet has been built out of the following structure:
-mod from_snippet;
-mod structs;
+use std::cmp::{max, min};
+use std::fmt::{Display, Write};
+use std::{cmp, fmt};
 
-pub use self::structs::*;
+use crate::formatter::style::{Style, StyleClass};
+use crate::formatter::{get_term_style, style::Stylesheet};
+use crate::snippet;
+
+/// List of lines to be displayed.
+pub struct DisplayList<'a> {
+    pub body: Vec<DisplayLine<'a>>,
+    pub stylesheet: Box<dyn Stylesheet>,
+    pub anonymized_line_numbers: bool,
+    pub margin: Option<Margin>,
+}
+
+impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> {
+    fn from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a> {
+        Self {
+            body,
+            anonymized_line_numbers: false,
+            stylesheet: get_term_style(false),
+            margin: None,
+        }
+    }
+}
+
+impl<'a> PartialEq for DisplayList<'a> {
+    fn eq(&self, other: &Self) -> bool {
+        self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
+    }
+}
+
+impl<'a> fmt::Debug for DisplayList<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("DisplayList")
+            .field("body", &self.body)
+            .field("anonymized_line_numbers", &self.anonymized_line_numbers)
+            .finish()
+    }
+}
+
+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 = if lineno_width == 0 {
+            lineno_width
+        } else if self.anonymized_line_numbers {
+            Self::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,
+        });
+
+        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')?;
+            }
+        }
+        Ok(())
+    }
+}
+
+impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
+    fn from(
+        snippet::Snippet {
+            title,
+            footer,
+            slices,
+            opt,
+        }: snippet::Snippet<'a>,
+    ) -> DisplayList<'a> {
+        let mut body = vec![];
+        if let Some(annotation) = title {
+            body.push(format_title(annotation));
+        }
+
+        for (idx, slice) in slices.into_iter().enumerate() {
+            body.append(&mut format_slice(
+                slice,
+                idx == 0,
+                !footer.is_empty(),
+                opt.margin,
+            ));
+        }
+
+        for annotation in footer {
+            body.append(&mut format_annotation(annotation));
+        }
+
+        let FormatOptions {
+            color,
+            anonymized_line_numbers,
+            margin,
+        } = opt;
+
+        Self {
+            body,
+            stylesheet: get_term_style(color),
+            anonymized_line_numbers,
+            margin,
+        }
+    }
+}
+
+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";
+
+    #[inline]
+    fn format_annotation_type(
+        annotation_type: &DisplayAnnotationType,
+        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,
+        }
+    }
+
+    fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> {
+        self.stylesheet.get_style(match annotation_type {
+            DisplayAnnotationType::Error => StyleClass::Error,
+            DisplayAnnotationType::Warning => StyleClass::Warning,
+            DisplayAnnotationType::Info => StyleClass::Info,
+            DisplayAnnotationType::Note => StyleClass::Note,
+            DisplayAnnotationType::Help => StyleClass::Help,
+            DisplayAnnotationType::None => StyleClass::None,
+        })
+    }
+
+    fn format_label(
+        &self,
+        label: &[DisplayTextFragment<'_>],
+        f: &mut fmt::Formatter<'_>,
+    ) -> fmt::Result {
+        let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis);
+
+        for fragment in label {
+            match fragment.style {
+                DisplayTextStyle::Regular => fragment.content.fmt(f)?,
+                DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?,
+            }
+        }
+        Ok(())
+    }
+
+    fn format_annotation(
+        &self,
+        annotation: &Annotation<'_>,
+        continuation: bool,
+        in_source: bool,
+        f: &mut fmt::Formatter<'_>,
+    ) -> fmt::Result {
+        let color = self.get_annotation_style(&annotation.annotation_type);
+        let formatted_len = if let Some(id) = &annotation.id {
+            2 + id.len() + Self::annotation_type_len(&annotation.annotation_type)
+        } else {
+            Self::annotation_type_len(&annotation.annotation_type)
+        };
+
+        if continuation {
+            format_repeat_char(' ', formatted_len + 2, f)?;
+            return self.format_label(&annotation.label, f);
+        }
+        if formatted_len == 0 {
+            self.format_label(&annotation.label, f)
+        } else {
+            color.paint_fn(
+                Box::new(|f| {
+                    Self::format_annotation_type(&annotation.annotation_type, f)?;
+                    if let Some(id) = &annotation.id {
+                        f.write_char('[')?;
+                        f.write_str(id)?;
+                        f.write_char(']')?;
+                    }
+                    Ok(())
+                }),
+                f,
+            )?;
+            if !is_annotation_empty(annotation) {
+                if in_source {
+                    color.paint_fn(
+                        Box::new(|f| {
+                            f.write_str(": ")?;
+                            self.format_label(&annotation.label, f)
+                        }),
+                        f,
+                    )?;
+                } else {
+                    f.write_str(": ")?;
+                    self.format_label(&annotation.label, 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::Consequitive => ' ',
+                    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,
+                    DisplayAnnotationPart::Consequitive => range.1,
+                    _ => range.0,
+                };
+
+                color.paint_fn(
+                    Box::new(|f| {
+                        format_repeat_char(indent_char, indent_length + 1, f)?;
+                        format_repeat_char(mark, range.1 - indent_length, f)
+                    }),
+                    f,
+                )?;
+
+                if !is_annotation_empty(annotation) {
+                    f.write_char(' ')?;
+                    color.paint_fn(
+                        Box::new(|f| {
+                            self.format_annotation(
+                                annotation,
+                                annotation_part == &DisplayAnnotationPart::LabelContinuation,
+                                true,
+                                f,
+                            )
+                        }),
+                        f,
+                    )?;
+                }
+
+                Ok(())
+            }
+        }
+    }
+
+    #[inline]
+    fn format_raw_line(
+        &self,
+        line: &DisplayRawLine<'_>,
+        lineno_width: usize,
+        f: &mut fmt::Formatter<'_>,
+    ) -> fmt::Result {
+        match line {
+            DisplayRawLine::Origin {
+                path,
+                pos,
+                header_type,
+            } => {
+                let header_sigil = match header_type {
+                    DisplayHeaderType::Initial => "-->",
+                    DisplayHeaderType::Continuation => ":::",
+                };
+                let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
+
+                if let Some((col, row)) = pos {
+                    format_repeat_char(' ', lineno_width, f)?;
+                    lineno_color.paint(header_sigil, f)?;
+                    f.write_char(' ')?;
+                    path.fmt(f)?;
+                    f.write_char(':')?;
+                    col.fmt(f)?;
+                    f.write_char(':')?;
+                    row.fmt(f)
+                } else {
+                    format_repeat_char(' ', lineno_width, f)?;
+                    lineno_color.paint(header_sigil, f)?;
+                    f.write_char(' ')?;
+                    path.fmt(f)
+                }
+            }
+            DisplayRawLine::Annotation {
+                annotation,
+                source_aligned,
+                continuation,
+            } => {
+                if *source_aligned {
+                    if *continuation {
+                        format_repeat_char(' ', lineno_width + 3, f)?;
+                    } else {
+                        let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
+                        format_repeat_char(' ', lineno_width, f)?;
+                        f.write_char(' ')?;
+                        lineno_color.paint("=", f)?;
+                        f.write_char(' ')?;
+                    }
+                }
+                self.format_annotation(annotation, *continuation, false, f)
+            }
+        }
+    }
+
+    #[inline]
+    fn format_line(
+        &self,
+        dl: &DisplayLine<'_>,
+        lineno_width: usize,
+        inline_marks_width: usize,
+        f: &mut fmt::Formatter<'_>,
+    ) -> fmt::Result {
+        match dl {
+            DisplayLine::Source {
+                lineno,
+                inline_marks,
+                line,
+            } => {
+                let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
+                if self.anonymized_line_numbers && lineno.is_some() {
+                    lineno_color.paint_fn(
+                        Box::new(|f| {
+                            f.write_str(Self::ANONYMIZED_LINE_NUM)?;
+                            f.write_str(" |")
+                        }),
+                        f,
+                    )?;
+                } else {
+                    lineno_color.paint_fn(
+                        Box::new(|f| {
+                            match lineno {
+                                Some(n) => write!(f, "{:>width$}", n, width = lineno_width),
+                                None => format_repeat_char(' ', lineno_width, f),
+                            }?;
+                            f.write_str(" |")
+                        }),
+                        f,
+                    )?;
+                }
+                if *line != DisplaySourceLine::Empty {
+                    if !inline_marks.is_empty() || 0 < inline_marks_width {
+                        f.write_char(' ')?;
+                        self.format_inline_marks(inline_marks, inline_marks_width, 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)?;
+                }
+                Ok(())
+            }
+            DisplayLine::Fold { inline_marks } => {
+                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)?;
+                }
+                Ok(())
+            }
+            DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f),
+        }
+    }
+
+    fn format_inline_marks(
+        &self,
+        inline_marks: &[DisplayMark],
+        inline_marks_width: usize,
+        f: &mut fmt::Formatter<'_>,
+    ) -> fmt::Result {
+        format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?;
+        for mark in inline_marks {
+            self.get_annotation_style(&mark.annotation_type).paint_fn(
+                Box::new(|f| {
+                    f.write_char(match mark.mark_type {
+                        DisplayMarkType::AnnotationThrough => '|',
+                        DisplayMarkType::AnnotationStart => '/',
+                    })
+                }),
+                f,
+            )?;
+        }
+        Ok(())
+    }
+}
+
+#[derive(Debug, Default, Copy, Clone)]
+pub struct FormatOptions {
+    pub color: bool,
+    pub anonymized_line_numbers: bool,
+    pub margin: Option<Margin>,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct Margin {
+    /// The available whitespace in the left that can be consumed when centering.
+    whitespace_left: usize,
+    /// The column of the beginning of left-most span.
+    span_left: usize,
+    /// The column of the end of right-most span.
+    span_right: usize,
+    /// The beginning of the line to be displayed.
+    computed_left: usize,
+    /// 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,
+    /// 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,
+}
+
+impl Margin {
+    pub fn new(
+        whitespace_left: usize,
+        span_left: usize,
+        span_right: usize,
+        label_right: usize,
+        column_width: usize,
+        max_line_len: usize,
+    ) -> Self {
+        // The 6 is padding to give a bit of room for `...` when displaying:
+        // ```
+        // error: message
+        //   --> file.rs:16:58
+        //    |
+        // 16 | ... fn foo(self) -> Self::Bar {
+        //    |                     ^^^^^^^^^
+        // ```
+
+        let mut m = Margin {
+            whitespace_left: whitespace_left.saturating_sub(6),
+            span_left: span_left.saturating_sub(6),
+            span_right: span_right + 6,
+            computed_left: 0,
+            computed_right: 0,
+            column_width,
+            label_right: label_right + 6,
+        };
+        m.compute(max_line_len);
+        m
+    }
+
+    pub(crate) fn was_cut_left(&self) -> bool {
+        self.computed_left > 0
+    }
+
+    pub(crate) fn was_cut_right(&self, line_len: usize) -> bool {
+        let right =
+            if self.computed_right == self.span_right || self.computed_right == self.label_right {
+                // Account for the "..." padding given above. Otherwise we end up with code lines that
+                // do fit but end in "..." as if they were trimmed.
+                self.computed_right - 6
+            } else {
+                self.computed_right
+            };
+        right < line_len && self.computed_left + self.column_width < line_len
+    }
+
+    fn compute(&mut self, max_line_len: usize) {
+        // When there's a lot of whitespace (>20), we want to trim it as it is useless.
+        self.computed_left = if self.whitespace_left > 20 {
+            self.whitespace_left - 16 // We want some padding.
+        } else {
+            0
+        };
+        // We want to show as much as possible, max_line_len is the right-most boundary for the
+        // relevant code.
+        self.computed_right = max(max_line_len, self.computed_left);
+
+        if self.computed_right - self.computed_left > self.column_width {
+            // Trimming only whitespace isn't enough, let's get craftier.
+            if self.label_right - self.whitespace_left <= self.column_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 {
+                // 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;
+                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 {
+                // 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;
+                self.computed_left = self.span_left.saturating_sub(padding_left);
+                self.computed_right = self.computed_left + self.column_width;
+            } else {
+                // Mostly give up but still don't show the full line.
+                self.computed_left = self.span_left;
+                self.computed_right = self.span_right;
+            }
+        }
+    }
+
+    pub(crate) fn left(&self, line_len: usize) -> usize {
+        min(self.computed_left, line_len)
+    }
+
+    pub(crate) fn right(&self, line_len: usize) -> usize {
+        if line_len.saturating_sub(self.computed_left) <= self.column_width {
+            line_len
+        } else {
+            min(line_len, self.computed_right)
+        }
+    }
+}
+
+/// Inline annotation which can be used in either Raw or Source line.
+#[derive(Debug, PartialEq)]
+pub struct Annotation<'a> {
+    pub annotation_type: DisplayAnnotationType,
+    pub id: Option<&'a str>,
+    pub label: Vec<DisplayTextFragment<'a>>,
+}
+
+/// A single line used in `DisplayList`.
+#[derive(Debug, PartialEq)]
+pub enum DisplayLine<'a> {
+    /// A line with `lineno` portion of the slice.
+    Source {
+        lineno: Option<usize>,
+        inline_marks: Vec<DisplayMark>,
+        line: DisplaySourceLine<'a>,
+    },
+
+    /// A line indicating a folded part of the slice.
+    Fold { inline_marks: Vec<DisplayMark> },
+
+    /// A line which is displayed outside of slices.
+    Raw(DisplayRawLine<'a>),
+}
+
+/// A source line.
+#[derive(Debug, PartialEq)]
+pub enum DisplaySourceLine<'a> {
+    /// A line with the content of the Slice.
+    Content {
+        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,
+}
+
+/// Raw line - a line which does not have the `lineno` part and is not considered
+/// a part of the snippet.
+#[derive(Debug, PartialEq)]
+pub enum DisplayRawLine<'a> {
+    /// A line which provides information about the location of the given
+    /// slice in the project structure.
+    Origin {
+        path: &'a str,
+        pos: Option<(usize, usize)>,
+        header_type: DisplayHeaderType,
+    },
+
+    /// An annotation line which is not part of any snippet.
+    Annotation {
+        annotation: Annotation<'a>,
+
+        /// If set to `true`, the annotation will be aligned to the
+        /// lineno delimiter of the snippet.
+        source_aligned: bool,
+        /// If set to `true`, only the label of the `Annotation` will be
+        /// displayed. It allows for a multiline annotation to be aligned
+        /// without displaying the meta information (`type` and `id`) to be
+        /// displayed on each line.
+        continuation: bool,
+    },
+}
+
+/// An inline text fragment which any label is composed of.
+#[derive(Debug, PartialEq)]
+pub struct DisplayTextFragment<'a> {
+    pub content: &'a str,
+    pub style: DisplayTextStyle,
+}
+
+/// A style for the `DisplayTextFragment` which can be visually formatted.
+///
+/// This information may be used to emphasis parts of the label.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum DisplayTextStyle {
+    Regular,
+    Emphasis,
+}
+
+/// An indicator of what part of the annotation a given `Annotation` is.
+#[derive(Debug, Clone, PartialEq)]
+pub enum DisplayAnnotationPart {
+    /// A standalone, single-line annotation.
+    Standalone,
+    /// A continuation of a multi-line label of an annotation.
+    LabelContinuation,
+    /// A consequitive annotation in case multiple annotations annotate a single line.
+    Consequitive,
+    /// A line starting a multiline annotation.
+    MultilineStart,
+    /// A line ending a multiline annotation.
+    MultilineEnd,
+}
+
+/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
+#[derive(Debug, Clone, PartialEq)]
+pub struct DisplayMark {
+    pub mark_type: DisplayMarkType,
+    pub annotation_type: DisplayAnnotationType,
+}
+
+/// A type of the `DisplayMark`.
+#[derive(Debug, Clone, PartialEq)]
+pub enum DisplayMarkType {
+    /// A mark indicating a multiline annotation going through the current line.
+    AnnotationThrough,
+    /// A mark indicating a multiline annotation starting on the given line.
+    AnnotationStart,
+}
+
+/// A type of the `Annotation` which may impact the sigils, style or text displayed.
+///
+/// There are several ways to uses this information when formatting the `DisplayList`:
+///
+/// * An annotation may display the name of the type like `error` or `info`.
+/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`.
+/// * `ColorStylesheet` may use different colors for different annotations.
+#[derive(Debug, Clone, PartialEq)]
+pub enum DisplayAnnotationType {
+    None,
+    Error,
+    Warning,
+    Info,
+    Note,
+    Help,
+}
+
+impl From<snippet::AnnotationType> for DisplayAnnotationType {
+    fn from(at: snippet::AnnotationType) -> Self {
+        match at {
+            snippet::AnnotationType::Error => DisplayAnnotationType::Error,
+            snippet::AnnotationType::Warning => DisplayAnnotationType::Warning,
+            snippet::AnnotationType::Info => DisplayAnnotationType::Info,
+            snippet::AnnotationType::Note => DisplayAnnotationType::Note,
+            snippet::AnnotationType::Help => DisplayAnnotationType::Help,
+        }
+    }
+}
+
+/// Information whether the header is the initial one or a consequitive one
+/// for multi-slice cases.
+// TODO: private
+#[derive(Debug, Clone, PartialEq)]
+pub enum DisplayHeaderType {
+    /// Initial header is the first header in the snippet.
+    Initial,
+
+    /// Continuation marks all headers of following slices in the snippet.
+    Continuation,
+}
+
+struct CursorLines<'a>(&'a str);
+
+impl<'a> CursorLines<'a> {
+    fn new(src: &str) -> CursorLines<'_> {
+        CursorLines(src)
+    }
+}
+
+enum EndLine {
+    Eof = 0,
+    Crlf = 1,
+    Lf = 2,
+}
+
+impl<'a> Iterator for CursorLines<'a> {
+    type Item = (&'a str, EndLine);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.0.is_empty() {
+            None
+        } else {
+            self.0
+                .find('\n')
+                .map(|x| {
+                    let ret = if 0 < x {
+                        if self.0.as_bytes()[x - 1] == b'\r' {
+                            (&self.0[..x - 1], EndLine::Lf)
+                        } else {
+                            (&self.0[..x], EndLine::Crlf)
+                        }
+                    } else {
+                        ("", EndLine::Crlf)
+                    };
+                    self.0 = &self.0[x + 1..];
+                    ret
+                })
+                .or_else(|| {
+                    let ret = Some((self.0, EndLine::Eof));
+                    self.0 = "";
+                    ret
+                })
+        }
+    }
+}
+
+fn format_label(
+    label: Option<&str>,
+    style: Option<DisplayTextStyle>,
+) -> Vec<DisplayTextFragment<'_>> {
+    let mut result = vec![];
+    if let Some(label) = label {
+        let element_style = style.unwrap_or(DisplayTextStyle::Regular);
+        result.push(DisplayTextFragment {
+            content: label,
+            style: element_style,
+        });
+    }
+    result
+}
+
+fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> {
+    let label = annotation.label.unwrap_or_default();
+    DisplayLine::Raw(DisplayRawLine::Annotation {
+        annotation: Annotation {
+            annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
+            id: annotation.id,
+            label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
+        },
+        source_aligned: false,
+        continuation: false,
+    })
+}
+
+fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> {
+    let mut result = vec![];
+    let label = annotation.label.unwrap_or_default();
+    for (i, line) in label.lines().enumerate() {
+        result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
+            annotation: Annotation {
+                annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
+                id: None,
+                label: format_label(Some(line), None),
+            },
+            source_aligned: true,
+            continuation: i != 0,
+        }));
+    }
+    result
+}
+
+fn format_slice(
+    slice: snippet::Slice<'_>,
+    is_first: bool,
+    has_footer: bool,
+    margin: Option<Margin>,
+) -> Vec<DisplayLine<'_>> {
+    let main_range = slice.annotations.get(0).map(|x| x.range.0);
+    let origin = slice.origin;
+    let need_empty_header = origin.is_some() || is_first;
+    let mut body = format_body(slice, need_empty_header, has_footer, margin);
+    let header = format_header(origin, main_range, &body, is_first);
+    let mut result = vec![];
+
+    if let Some(header) = header {
+        result.push(header);
+    }
+    result.append(&mut body);
+    result
+}
+
+#[inline]
+// TODO: option_zip
+fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> {
+    a.and_then(|a| b.map(|b| (a, b)))
+}
+
+fn format_header<'a>(
+    origin: Option<&'a str>,
+    main_range: Option<usize>,
+    body: &[DisplayLine<'_>],
+    is_first: bool,
+) -> Option<DisplayLine<'a>> {
+    let display_header = if is_first {
+        DisplayHeaderType::Initial
+    } else {
+        DisplayHeaderType::Continuation
+    };
+
+    if let Some((main_range, path)) = zip_opt(main_range, origin) {
+        let mut col = 1;
+        let mut line_offset = 1;
+
+        for item in body {
+            if let DisplayLine::Source {
+                line: DisplaySourceLine::Content { range, .. },
+                lineno,
+                ..
+            } = item
+            {
+                if main_range >= range.0 && main_range <= range.1 {
+                    col = main_range - range.0 + 1;
+                    line_offset = lineno.unwrap_or(1);
+                    break;
+                }
+            }
+        }
+
+        return Some(DisplayLine::Raw(DisplayRawLine::Origin {
+            path,
+            pos: Some((line_offset, col)),
+            header_type: display_header,
+        }));
+    }
+
+    if let Some(path) = origin {
+        return Some(DisplayLine::Raw(DisplayRawLine::Origin {
+            path,
+            pos: None,
+            header_type: display_header,
+        }));
+    }
+
+    None
+}
+
+fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
+    enum Line {
+        Fold(usize),
+        Source(usize),
+    }
+
+    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 { .. },
+                ..
+            } => {
+                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));
+                    }
+                } else {
+                    for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) {
+                        lines.push(Line::Source(i));
+                    }
+                }
+                no_annotation_lines_counter = 0;
+            }
+            DisplayLine::Source { .. } => {
+                no_annotation_lines_counter += 1;
+                continue;
+            }
+            _ => {
+                no_annotation_lines_counter += 1;
+            }
+        }
+        lines.push(Line::Source(idx));
+    }
+
+    let mut new_body = vec![];
+    let mut removed = 0;
+    for line in lines {
+        match line {
+            Line::Source(i) => {
+                new_body.push(body.remove(i - removed));
+                removed += 1;
+            }
+            Line::Fold(i) => {
+                if let DisplayLine::Source {
+                    line: DisplaySourceLine::Annotation { .. },
+                    ref inline_marks,
+                    ..
+                } = body.get(i - removed).unwrap()
+                {
+                    new_body.push(DisplayLine::Fold {
+                        inline_marks: inline_marks.clone(),
+                    })
+                } else {
+                    unreachable!()
+                }
+            }
+        }
+    }
+
+    new_body
+}
+
+fn format_body(
+    slice: snippet::Slice<'_>,
+    need_empty_header: bool,
+    has_footer: bool,
+    margin: Option<Margin>,
+) -> Vec<DisplayLine<'_>> {
+    let source_len = slice.source.chars().count();
+    if let Some(bigger) = slice.annotations.iter().find_map(|x| {
+        if source_len < x.range.1 {
+            Some(x.range)
+        } else {
+            None
+        }
+    }) {
+        panic!(
+            "SourceAnnotation range `{:?}` is bigger than source length `{}`",
+            bigger, source_len
+        )
+    }
+
+    let mut body = vec![];
+    let mut current_line = slice.line_start;
+    let mut current_index = 0;
+    let mut line_info = vec![];
+
+    struct LineInfo {
+        line_start_index: usize,
+        line_end_index: usize,
+        // How many spaces each character in the line take up when displayed
+        char_widths: Vec<usize>,
+    }
+
+    for (line, end_line) in CursorLines::new(slice.source) {
+        let line_length = line.chars().count();
+        let line_range = (current_index, current_index + line_length);
+        let char_widths = line
+            .chars()
+            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
+            .chain(std::iter::once(1)) // treat the end of line as single-width
+            .collect::<Vec<_>>();
+        body.push(DisplayLine::Source {
+            lineno: Some(current_line),
+            inline_marks: vec![],
+            line: DisplaySourceLine::Content {
+                text: line,
+                range: line_range,
+            },
+        });
+        line_info.push(LineInfo {
+            line_start_index: line_range.0,
+            line_end_index: line_range.1,
+            char_widths,
+        });
+        current_line += 1;
+        current_index += line_length + end_line as usize;
+    }
+
+    let mut annotation_line_count = 0;
+    let mut annotations = slice.annotations;
+    for (
+        idx,
+        LineInfo {
+            line_start_index,
+            line_end_index,
+            char_widths,
+        },
+    ) in line_info.into_iter().enumerate()
+    {
+        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 annotation_type = match annotation.annotation_type {
+                snippet::AnnotationType::Error => DisplayAnnotationType::None,
+                snippet::AnnotationType::Warning => DisplayAnnotationType::None,
+                _ => DisplayAnnotationType::from(annotation.annotation_type),
+            };
+            match annotation.range {
+                (start, _) if start > line_end_index => true,
+                (start, end)
+                    if start >= line_start_index && end <= line_end_index
+                        || start == line_end_index && end - start <= 1 =>
+                {
+                    let annotation_start_col = char_widths
+                        .iter()
+                        .take(start - line_start_index)
+                        .sum::<usize>()
+                        - margin_left;
+                    let annotation_end_col = char_widths
+                        .iter()
+                        .take(end - line_start_index)
+                        .sum::<usize>()
+                        - 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(Some(annotation.label), None),
+                                },
+                                range,
+                                annotation_type: DisplayAnnotationType::from(
+                                    annotation.annotation_type,
+                                ),
+                                annotation_part: DisplayAnnotationPart::Standalone,
+                            },
+                        },
+                    );
+                    annotation_line_count += 1;
+                    false
+                }
+                (start, end)
+                    if start >= line_start_index
+                        && start <= line_end_index
+                        && end > line_end_index =>
+                {
+                    if start - line_start_index == 0 {
+                        if let DisplayLine::Source {
+                            ref mut inline_marks,
+                            ..
+                        } = body[body_idx]
+                        {
+                            inline_marks.push(DisplayMark {
+                                mark_type: DisplayMarkType::AnnotationStart,
+                                annotation_type: DisplayAnnotationType::from(
+                                    annotation.annotation_type,
+                                ),
+                            });
+                        }
+                    } else {
+                        let annotation_start_col = char_widths
+                            .iter()
+                            .take(start - line_start_index)
+                            .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.annotation_type,
+                                    ),
+                                    annotation_part: DisplayAnnotationPart::MultilineStart,
+                                },
+                            },
+                        );
+                        annotation_line_count += 1;
+                    }
+                    true
+                }
+                (start, end) if start < line_start_index && end > line_end_index => {
+                    if let DisplayLine::Source {
+                        ref mut inline_marks,
+                        ..
+                    } = body[body_idx]
+                    {
+                        inline_marks.push(DisplayMark {
+                            mark_type: DisplayMarkType::AnnotationThrough,
+                            annotation_type: DisplayAnnotationType::from(
+                                annotation.annotation_type,
+                            ),
+                        });
+                    }
+                    true
+                }
+                (start, end)
+                    if start < line_start_index
+                        && end >= line_start_index
+                        && end <= line_end_index =>
+                {
+                    if let DisplayLine::Source {
+                        ref mut inline_marks,
+                        ..
+                    } = body[body_idx]
+                    {
+                        inline_marks.push(DisplayMark {
+                            mark_type: DisplayMarkType::AnnotationThrough,
+                            annotation_type: DisplayAnnotationType::from(
+                                annotation.annotation_type,
+                            ),
+                        });
+                    }
+
+                    let end_mark = char_widths
+                        .iter()
+                        .take(end - line_start_index)
+                        .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.annotation_type,
+                                ),
+                            }],
+                            line: DisplaySourceLine::Annotation {
+                                annotation: Annotation {
+                                    annotation_type,
+                                    id: None,
+                                    label: format_label(Some(annotation.label), None),
+                                },
+                                range,
+                                annotation_type: DisplayAnnotationType::from(
+                                    annotation.annotation_type,
+                                ),
+                                annotation_part: DisplayAnnotationPart::MultilineEnd,
+                            },
+                        },
+                    );
+                    annotation_line_count += 1;
+                    false
+                }
+                _ => true,
+            }
+        });
+    }
+
+    if slice.fold {
+        body = fold_body(body);
+    }
+
+    if need_empty_header {
+        body.insert(
+            0,
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+        );
+    }
+
+    if has_footer {
+        body.push(DisplayLine::Source {
+            lineno: None,
+            inline_marks: vec![],
+            line: DisplaySourceLine::Empty,
+        });
+    } else if let Some(DisplayLine::Source { .. }) = body.last() {
+        body.push(DisplayLine::Source {
+            lineno: None,
+            inline_marks: vec![],
+            line: DisplaySourceLine::Empty,
+        });
+    }
+    body
+}
+
+fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    for _ in 0..n {
+        f.write_char(c)?;
+    }
+    Ok(())
+}
+
+#[inline]
+fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
+    annotation
+        .label
+        .iter()
+        .all(|fragment| fragment.content.is_empty())
+}
diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs
deleted file mode 100644
index 5a31d1e..0000000
--- a/src/display_list/structs.rs
+++ /dev/null
@@ -1,308 +0,0 @@
-use std::cmp::{max, min};
-use std::fmt;
-
-use crate::formatter::{get_term_style, style::Stylesheet};
-
-/// List of lines to be displayed.
-pub struct DisplayList<'a> {
-    pub body: Vec<DisplayLine<'a>>,
-    pub stylesheet: Box<dyn Stylesheet>,
-    pub anonymized_line_numbers: bool,
-    pub margin: Option<Margin>,
-}
-
-impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> {
-    fn from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a> {
-        Self {
-            body,
-            anonymized_line_numbers: false,
-            stylesheet: get_term_style(false),
-            margin: None,
-        }
-    }
-}
-
-impl<'a> PartialEq for DisplayList<'a> {
-    fn eq(&self, other: &Self) -> bool {
-        self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
-    }
-}
-
-impl<'a> fmt::Debug for DisplayList<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("DisplayList")
-            .field("body", &self.body)
-            .field("anonymized_line_numbers", &self.anonymized_line_numbers)
-            .finish()
-    }
-}
-
-#[derive(Debug, Default, Copy, Clone)]
-pub struct FormatOptions {
-    pub color: bool,
-    pub anonymized_line_numbers: bool,
-    pub margin: Option<Margin>,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub struct Margin {
-    /// The available whitespace in the left that can be consumed when centering.
-    whitespace_left: usize,
-    /// The column of the beginning of left-most span.
-    span_left: usize,
-    /// The column of the end of right-most span.
-    span_right: usize,
-    /// The beginning of the line to be displayed.
-    computed_left: usize,
-    /// 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,
-    /// 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,
-}
-
-impl Margin {
-    pub fn new(
-        whitespace_left: usize,
-        span_left: usize,
-        span_right: usize,
-        label_right: usize,
-        column_width: usize,
-        max_line_len: usize,
-    ) -> Self {
-        // The 6 is padding to give a bit of room for `...` when displaying:
-        // ```
-        // error: message
-        //   --> file.rs:16:58
-        //    |
-        // 16 | ... fn foo(self) -> Self::Bar {
-        //    |                     ^^^^^^^^^
-        // ```
-
-        let mut m = Margin {
-            whitespace_left: whitespace_left.saturating_sub(6),
-            span_left: span_left.saturating_sub(6),
-            span_right: span_right + 6,
-            computed_left: 0,
-            computed_right: 0,
-            column_width,
-            label_right: label_right + 6,
-        };
-        m.compute(max_line_len);
-        m
-    }
-
-    pub(crate) fn was_cut_left(&self) -> bool {
-        self.computed_left > 0
-    }
-
-    pub(crate) fn was_cut_right(&self, line_len: usize) -> bool {
-        let right =
-            if self.computed_right == self.span_right || self.computed_right == self.label_right {
-                // Account for the "..." padding given above. Otherwise we end up with code lines that
-                // do fit but end in "..." as if they were trimmed.
-                self.computed_right - 6
-            } else {
-                self.computed_right
-            };
-        right < line_len && self.computed_left + self.column_width < line_len
-    }
-
-    fn compute(&mut self, max_line_len: usize) {
-        // When there's a lot of whitespace (>20), we want to trim it as it is useless.
-        self.computed_left = if self.whitespace_left > 20 {
-            self.whitespace_left - 16 // We want some padding.
-        } else {
-            0
-        };
-        // We want to show as much as possible, max_line_len is the right-most boundary for the
-        // relevant code.
-        self.computed_right = max(max_line_len, self.computed_left);
-
-        if self.computed_right - self.computed_left > self.column_width {
-            // Trimming only whitespace isn't enough, let's get craftier.
-            if self.label_right - self.whitespace_left <= self.column_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 {
-                // 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;
-                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 {
-                // 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;
-                self.computed_left = self.span_left.saturating_sub(padding_left);
-                self.computed_right = self.computed_left + self.column_width;
-            } else {
-                // Mostly give up but still don't show the full line.
-                self.computed_left = self.span_left;
-                self.computed_right = self.span_right;
-            }
-        }
-    }
-
-    pub(crate) fn left(&self, line_len: usize) -> usize {
-        min(self.computed_left, line_len)
-    }
-
-    pub(crate) fn right(&self, line_len: usize) -> usize {
-        if line_len.saturating_sub(self.computed_left) <= self.column_width {
-            line_len
-        } else {
-            min(line_len, self.computed_right)
-        }
-    }
-}
-
-/// Inline annotation which can be used in either Raw or Source line.
-#[derive(Debug, PartialEq)]
-pub struct Annotation<'a> {
-    pub annotation_type: DisplayAnnotationType,
-    pub id: Option<&'a str>,
-    pub label: Vec<DisplayTextFragment<'a>>,
-}
-
-/// A single line used in `DisplayList`.
-#[derive(Debug, PartialEq)]
-pub enum DisplayLine<'a> {
-    /// A line with `lineno` portion of the slice.
-    Source {
-        lineno: Option<usize>,
-        inline_marks: Vec<DisplayMark>,
-        line: DisplaySourceLine<'a>,
-    },
-
-    /// A line indicating a folded part of the slice.
-    Fold { inline_marks: Vec<DisplayMark> },
-
-    /// A line which is displayed outside of slices.
-    Raw(DisplayRawLine<'a>),
-}
-
-/// A source line.
-#[derive(Debug, PartialEq)]
-pub enum DisplaySourceLine<'a> {
-    /// A line with the content of the Slice.
-    Content {
-        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,
-}
-
-/// Raw line - a line which does not have the `lineno` part and is not considered
-/// a part of the snippet.
-#[derive(Debug, PartialEq)]
-pub enum DisplayRawLine<'a> {
-    /// A line which provides information about the location of the given
-    /// slice in the project structure.
-    Origin {
-        path: &'a str,
-        pos: Option<(usize, usize)>,
-        header_type: DisplayHeaderType,
-    },
-
-    /// An annotation line which is not part of any snippet.
-    Annotation {
-        annotation: Annotation<'a>,
-
-        /// If set to `true`, the annotation will be aligned to the
-        /// lineno delimiter of the snippet.
-        source_aligned: bool,
-        /// If set to `true`, only the label of the `Annotation` will be
-        /// displayed. It allows for a multiline annotation to be aligned
-        /// without displaying the meta information (`type` and `id`) to be
-        /// displayed on each line.
-        continuation: bool,
-    },
-}
-
-/// An inline text fragment which any label is composed of.
-#[derive(Debug, PartialEq)]
-pub struct DisplayTextFragment<'a> {
-    pub content: &'a str,
-    pub style: DisplayTextStyle,
-}
-
-/// A style for the `DisplayTextFragment` which can be visually formatted.
-///
-/// This information may be used to emphasis parts of the label.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum DisplayTextStyle {
-    Regular,
-    Emphasis,
-}
-
-/// An indicator of what part of the annotation a given `Annotation` is.
-#[derive(Debug, Clone, PartialEq)]
-pub enum DisplayAnnotationPart {
-    /// A standalone, single-line annotation.
-    Standalone,
-    /// A continuation of a multi-line label of an annotation.
-    LabelContinuation,
-    /// A consequitive annotation in case multiple annotations annotate a single line.
-    Consequitive,
-    /// A line starting a multiline annotation.
-    MultilineStart,
-    /// A line ending a multiline annotation.
-    MultilineEnd,
-}
-
-/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
-#[derive(Debug, Clone, PartialEq)]
-pub struct DisplayMark {
-    pub mark_type: DisplayMarkType,
-    pub annotation_type: DisplayAnnotationType,
-}
-
-/// A type of the `DisplayMark`.
-#[derive(Debug, Clone, PartialEq)]
-pub enum DisplayMarkType {
-    /// A mark indicating a multiline annotation going through the current line.
-    AnnotationThrough,
-    /// A mark indicating a multiline annotation starting on the given line.
-    AnnotationStart,
-}
-
-/// A type of the `Annotation` which may impact the sigils, style or text displayed.
-///
-/// There are several ways to uses this information when formatting the `DisplayList`:
-///
-/// * An annotation may display the name of the type like `error` or `info`.
-/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`.
-/// * `ColorStylesheet` may use different colors for different annotations.
-#[derive(Debug, Clone, PartialEq)]
-pub enum DisplayAnnotationType {
-    None,
-    Error,
-    Warning,
-    Info,
-    Note,
-    Help,
-}
-
-/// Information whether the header is the initial one or a consequitive one
-/// for multi-slice cases.
-// TODO: private
-#[derive(Debug, Clone, PartialEq)]
-pub enum DisplayHeaderType {
-    /// Initial header is the first header in the snippet.
-    Initial,
-
-    /// Continuation marks all headers of following slices in the snippet.
-    Continuation,
-}
diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs
index 16889ba..72bf9c7 100644
--- a/src/formatter/mod.rs
+++ b/src/formatter/mod.rs
@@ -1,31 +1,10 @@
-use std::{
-    cmp,
-    fmt::{self, Display, Write},
-    iter::once,
-};
-
 pub mod style;
 
-use self::style::{Style, StyleClass, Stylesheet};
+use self::style::Stylesheet;
 
 #[cfg(feature = "color")]
 use crate::stylesheets::color::AnsiTermStylesheet;
-use crate::{display_list::*, stylesheets::no_color::NoColorStylesheet};
-
-fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-    for _ in 0..n {
-        f.write_char(c)?;
-    }
-    Ok(())
-}
-
-#[inline]
-fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
-    annotation
-        .label
-        .iter()
-        .all(|fragment| fragment.content.is_empty())
-}
+use crate::stylesheets::no_color::NoColorStylesheet;
 
 #[cfg(feature = "color")]
 #[inline]
@@ -42,415 +21,3 @@ pub fn get_term_style(color: bool) -> Box<dyn Stylesheet> {
 pub fn get_term_style(_color: bool) -> Box<dyn Stylesheet> {
     Box::new(NoColorStylesheet)
 }
-
-impl<'a> fmt::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 = if lineno_width == 0 {
-            lineno_width
-        } else if self.anonymized_line_numbers {
-            Self::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,
-        });
-
-        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')?;
-            }
-        }
-        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";
-
-    #[inline]
-    fn format_annotation_type(
-        annotation_type: &DisplayAnnotationType,
-        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,
-        }
-    }
-
-    fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> {
-        self.stylesheet.get_style(match annotation_type {
-            DisplayAnnotationType::Error => StyleClass::Error,
-            DisplayAnnotationType::Warning => StyleClass::Warning,
-            DisplayAnnotationType::Info => StyleClass::Info,
-            DisplayAnnotationType::Note => StyleClass::Note,
-            DisplayAnnotationType::Help => StyleClass::Help,
-            DisplayAnnotationType::None => StyleClass::None,
-        })
-    }
-
-    fn format_label(
-        &self,
-        label: &[DisplayTextFragment<'_>],
-        f: &mut fmt::Formatter<'_>,
-    ) -> fmt::Result {
-        let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis);
-
-        for fragment in label {
-            match fragment.style {
-                DisplayTextStyle::Regular => fragment.content.fmt(f)?,
-                DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?,
-            }
-        }
-        Ok(())
-    }
-
-    fn format_annotation(
-        &self,
-        annotation: &Annotation<'_>,
-        continuation: bool,
-        in_source: bool,
-        f: &mut fmt::Formatter<'_>,
-    ) -> fmt::Result {
-        let color = self.get_annotation_style(&annotation.annotation_type);
-        let formatted_len = if let Some(id) = &annotation.id {
-            2 + id.len() + Self::annotation_type_len(&annotation.annotation_type)
-        } else {
-            Self::annotation_type_len(&annotation.annotation_type)
-        };
-
-        if continuation {
-            format_repeat_char(' ', formatted_len + 2, f)?;
-            return self.format_label(&annotation.label, f);
-        }
-        if formatted_len == 0 {
-            self.format_label(&annotation.label, f)
-        } else {
-            color.paint_fn(
-                Box::new(|f| {
-                    Self::format_annotation_type(&annotation.annotation_type, f)?;
-                    if let Some(id) = &annotation.id {
-                        f.write_char('[')?;
-                        f.write_str(id)?;
-                        f.write_char(']')?;
-                    }
-                    Ok(())
-                }),
-                f,
-            )?;
-            if !is_annotation_empty(annotation) {
-                if in_source {
-                    color.paint_fn(
-                        Box::new(|f| {
-                            f.write_str(": ")?;
-                            self.format_label(&annotation.label, f)
-                        }),
-                        f,
-                    )?;
-                } else {
-                    f.write_str(": ")?;
-                    self.format_label(&annotation.label, 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(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::Consequitive => ' ',
-                    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,
-                    DisplayAnnotationPart::Consequitive => range.1,
-                    _ => range.0,
-                };
-
-                color.paint_fn(
-                    Box::new(|f| {
-                        format_repeat_char(indent_char, indent_length + 1, f)?;
-                        format_repeat_char(mark, range.1 - indent_length, f)
-                    }),
-                    f,
-                )?;
-
-                if !is_annotation_empty(annotation) {
-                    f.write_char(' ')?;
-                    color.paint_fn(
-                        Box::new(|f| {
-                            self.format_annotation(
-                                annotation,
-                                annotation_part == &DisplayAnnotationPart::LabelContinuation,
-                                true,
-                                f,
-                            )
-                        }),
-                        f,
-                    )?;
-                }
-
-                Ok(())
-            }
-        }
-    }
-
-    #[inline]
-    fn format_raw_line(
-        &self,
-        line: &DisplayRawLine<'_>,
-        lineno_width: usize,
-        f: &mut fmt::Formatter<'_>,
-    ) -> fmt::Result {
-        match line {
-            DisplayRawLine::Origin {
-                path,
-                pos,
-                header_type,
-            } => {
-                let header_sigil = match header_type {
-                    DisplayHeaderType::Initial => "-->",
-                    DisplayHeaderType::Continuation => ":::",
-                };
-                let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
-
-                if let Some((col, row)) = pos {
-                    format_repeat_char(' ', lineno_width, f)?;
-                    lineno_color.paint(header_sigil, f)?;
-                    f.write_char(' ')?;
-                    path.fmt(f)?;
-                    f.write_char(':')?;
-                    col.fmt(f)?;
-                    f.write_char(':')?;
-                    row.fmt(f)
-                } else {
-                    format_repeat_char(' ', lineno_width, f)?;
-                    lineno_color.paint(header_sigil, f)?;
-                    f.write_char(' ')?;
-                    path.fmt(f)
-                }
-            }
-            DisplayRawLine::Annotation {
-                annotation,
-                source_aligned,
-                continuation,
-            } => {
-                if *source_aligned {
-                    if *continuation {
-                        format_repeat_char(' ', lineno_width + 3, f)?;
-                    } else {
-                        let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
-                        format_repeat_char(' ', lineno_width, f)?;
-                        f.write_char(' ')?;
-                        lineno_color.paint("=", f)?;
-                        f.write_char(' ')?;
-                    }
-                }
-                self.format_annotation(annotation, *continuation, false, f)
-            }
-        }
-    }
-
-    #[inline]
-    fn format_line(
-        &self,
-        dl: &DisplayLine<'_>,
-        lineno_width: usize,
-        inline_marks_width: usize,
-        f: &mut fmt::Formatter<'_>,
-    ) -> fmt::Result {
-        match dl {
-            DisplayLine::Source {
-                lineno,
-                inline_marks,
-                line,
-            } => {
-                let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
-                if self.anonymized_line_numbers && lineno.is_some() {
-                    lineno_color.paint_fn(
-                        Box::new(|f| {
-                            f.write_str(Self::ANONYMIZED_LINE_NUM)?;
-                            f.write_str(" |")
-                        }),
-                        f,
-                    )?;
-                } else {
-                    lineno_color.paint_fn(
-                        Box::new(|f| {
-                            match lineno {
-                                Some(n) => write!(f, "{:>width$}", n, width = lineno_width),
-                                None => format_repeat_char(' ', lineno_width, f),
-                            }?;
-                            f.write_str(" |")
-                        }),
-                        f,
-                    )?;
-                }
-                if *line != DisplaySourceLine::Empty {
-                    if !inline_marks.is_empty() || 0 < inline_marks_width {
-                        f.write_char(' ')?;
-                        self.format_inline_marks(inline_marks, inline_marks_width, 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)?;
-                }
-                Ok(())
-            }
-            DisplayLine::Fold { inline_marks } => {
-                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)?;
-                }
-                Ok(())
-            }
-            DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f),
-        }
-    }
-
-    fn format_inline_marks(
-        &self,
-        inline_marks: &[DisplayMark],
-        inline_marks_width: usize,
-        f: &mut fmt::Formatter<'_>,
-    ) -> fmt::Result {
-        format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?;
-        for mark in inline_marks {
-            self.get_annotation_style(&mark.annotation_type).paint_fn(
-                Box::new(|f| {
-                    f.write_char(match mark.mark_type {
-                        DisplayMarkType::AnnotationThrough => '|',
-                        DisplayMarkType::AnnotationStart => '/',
-                    })
-                }),
-                f,
-            )?;
-        }
-        Ok(())
-    }
-}

From 9076cbf66336e5137b47dc7a52df2999b6c82598 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sun, 12 Nov 2023 12:44:16 -0700
Subject: [PATCH 060/302] feat: Add `Renderer`

---
 benches/simple.rs         |  7 ++++---
 examples/expected_type.rs |  7 ++++---
 examples/footer.rs        |  7 ++++---
 examples/format.rs        |  7 ++++---
 examples/multislice.rs    |  7 ++++---
 src/lib.rs                |  1 +
 src/renderer/mod.rs       | 11 +++++++++++
 tests/fixtures_test.rs    |  7 ++++---
 tests/formatter.rs        | 16 +++++++++++-----
 9 files changed, 47 insertions(+), 23 deletions(-)
 create mode 100644 src/renderer/mod.rs

diff --git a/benches/simple.rs b/benches/simple.rs
index 4c13a8f..4427afd 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -4,8 +4,9 @@ extern crate criterion;
 
 use criterion::{black_box, Criterion};
 
+use annotate_snippets::renderer::Renderer;
 use annotate_snippets::{
-    display_list::{DisplayList, FormatOptions},
+    display_list::FormatOptions,
     snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
 };
 
@@ -62,8 +63,8 @@ fn create_snippet() {
         },
     };
 
-    let dl = DisplayList::from(snippet);
-    let _result = dl.to_string();
+    let renderer = Renderer;
+    let _result = renderer.render(snippet).to_string();
 }
 
 pub fn criterion_benchmark(c: &mut Criterion) {
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 6f2a0d9..cd23bfc 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,5 +1,6 @@
+use annotate_snippets::renderer::Renderer;
 use annotate_snippets::{
-    display_list::{DisplayList, FormatOptions},
+    display_list::FormatOptions,
     snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
 };
 
@@ -38,6 +39,6 @@ fn main() {
         },
     };
 
-    let dl = DisplayList::from(snippet);
-    println!("{}", dl);
+    let renderer = Renderer;
+    println!("{}", renderer.render(snippet));
 }
diff --git a/examples/footer.rs b/examples/footer.rs
index f3c15c4..6bf6f5e 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,5 +1,6 @@
+use annotate_snippets::renderer::Renderer;
 use annotate_snippets::{
-    display_list::{DisplayList, FormatOptions},
+    display_list::FormatOptions,
     snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
 };
 
@@ -34,6 +35,6 @@ fn main() {
         },
     };
 
-    let dl = DisplayList::from(snippet);
-    println!("{}", dl);
+    let renderer = Renderer;
+    println!("{}", renderer.render(snippet));
 }
diff --git a/examples/format.rs b/examples/format.rs
index 98b77a1..a053ccc 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,5 +1,6 @@
+use annotate_snippets::renderer::Renderer;
 use annotate_snippets::{
-    display_list::{DisplayList, FormatOptions},
+    display_list::FormatOptions,
     snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
 };
 
@@ -56,6 +57,6 @@ fn main() {
         },
     };
 
-    let dl = DisplayList::from(snippet);
-    println!("{}", dl);
+    let renderer = Renderer;
+    println!("{}", renderer.render(snippet));
 }
diff --git a/examples/multislice.rs b/examples/multislice.rs
index 5675a07..a875ae2 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,5 +1,6 @@
+use annotate_snippets::renderer::Renderer;
 use annotate_snippets::{
-    display_list::{DisplayList, FormatOptions},
+    display_list::FormatOptions,
     snippet::{Annotation, AnnotationType, Slice, Snippet},
 };
 
@@ -33,6 +34,6 @@ fn main() {
         },
     };
 
-    let dl = DisplayList::from(snippet);
-    println!("{}", dl);
+    let renderer = Renderer;
+    println!("{}", renderer.render(snippet));
 }
diff --git a/src/lib.rs b/src/lib.rs
index d581367..e11e9d5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -50,5 +50,6 @@
 
 pub mod display_list;
 pub mod formatter;
+pub mod renderer;
 pub mod snippet;
 pub mod stylesheets;
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
new file mode 100644
index 0000000..9a66c1c
--- /dev/null
+++ b/src/renderer/mod.rs
@@ -0,0 +1,11 @@
+use crate::display_list::DisplayList;
+use crate::snippet::Snippet;
+use std::fmt::Display;
+
+pub struct Renderer;
+
+impl Renderer {
+    pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a {
+        DisplayList::from(snippet)
+    }
+}
diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs
index 13f8543..8dfd229 100644
--- a/tests/fixtures_test.rs
+++ b/tests/fixtures_test.rs
@@ -2,7 +2,8 @@ mod diff;
 mod snippet;
 
 use crate::snippet::SnippetDef;
-use annotate_snippets::{display_list::DisplayList, snippet::Snippet};
+use annotate_snippets::renderer::Renderer;
+use annotate_snippets::snippet::Snippet;
 use glob::glob;
 use std::{error::Error, fs::File, io, io::prelude::*};
 
@@ -30,8 +31,8 @@ fn test_fixtures() {
         let snippet = read_fixture(&src).expect("Failed to read file");
         let expected_out = read_file(&path_out).expect("Failed to read file");
 
-        let dl = DisplayList::from(snippet);
-        let actual_out = dl.to_string();
+        let renderer = Renderer;
+        let actual_out = renderer.render(snippet).to_string();
         println!("{}", expected_out);
         println!("{}", actual_out.trim_end());
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index f95b002..1226ab4 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,4 +1,5 @@
 use annotate_snippets::display_list::*;
+use annotate_snippets::renderer::Renderer;
 use annotate_snippets::snippet::{self, Snippet};
 
 #[test]
@@ -548,7 +549,8 @@ fn test_i_29() {
   |        ^^^^ oops
   |"#;
 
-    assert_eq!(DisplayList::from(snippets).to_string(), expected);
+    let renderer = Renderer;
+    assert_eq!(renderer.render(snippets).to_string(), expected);
 }
 
 #[test]
@@ -576,7 +578,8 @@ fn test_point_to_double_width_characters() {
   |             ^^^^ world
   |"#;
 
-    assert_eq!(DisplayList::from(snippets).to_string(), expected);
+    let renderer = Renderer;
+    assert_eq!(renderer.render(snippets).to_string(), expected);
 }
 
 #[test]
@@ -606,7 +609,8 @@ fn test_point_to_double_width_characters_across_lines() {
   | |______^ Good morning
   |"#;
 
-    assert_eq!(DisplayList::from(snippets).to_string(), expected);
+    let renderer = Renderer;
+    assert_eq!(renderer.render(snippets).to_string(), expected);
 }
 
 #[test]
@@ -643,7 +647,8 @@ fn test_point_to_double_width_characters_multiple() {
   |     ---- note: Sushi2
   |"#;
 
-    assert_eq!(DisplayList::from(snippets).to_string(), expected);
+    let renderer = Renderer;
+    assert_eq!(renderer.render(snippets).to_string(), expected);
 }
 
 #[test]
@@ -671,5 +676,6 @@ fn test_point_to_double_width_characters_mixed() {
   |             ^^^^^^^^^^^ New world
   |"#;
 
-    assert_eq!(DisplayList::from(snippets).to_string(), expected);
+    let renderer = Renderer;
+    assert_eq!(renderer.render(snippets).to_string(), expected);
 }

From 598c6244983fb392457f3fbec9badf25fab6d051 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 3 Dec 2023 09:19:35 +0000
Subject: [PATCH 061/302] chore(config): migrate config .github/renovate.json5

---
 .github/renovate.json5 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 72d0579..3119c42 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -29,7 +29,7 @@
     {
       commitMessageTopic: 'MSRV',
       matchManagers: [
-        'regex',
+        'custom.regex',
       ],
       matchPackageNames: [
         'rust',

From c84cb0f1ae2815f98dbe1b12dee8015c591ac4f1 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 5 Dec 2023 05:17:36 +0000
Subject: [PATCH 062/302] chore(config): migrate config .github/renovate.json5

---
 .github/renovate.json5 | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 21d082f..87676d4 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -29,24 +29,18 @@
     {
       commitMessageTopic: 'MSRV',
       matchManagers: [
-        'regex',
+        'custom.regex',
       ],
       matchPackageNames: [
         'rust',
       ],
-      minimumReleaseAge: '84 days',  // 2 releases back * 6 weeks per release * 7 days per week
+      minimumReleaseAge: '84 days',
       internalChecksFilter: 'strict',
-      extractVersion: '^(?<version>\\d+\\.\\d+)',  // Drop the patch version
+      extractVersion: '^(?<version>\\d+\\.\\d+)',
       schedule: [
         '* * * * *',
       ],
     },
-    // Goals:
-    // - Keep version reqs low, ignoring compatible normal/build dependencies
-    // - Take advantage of latest dev-dependencies
-    // - Rollup safe upgrades to reduce CI runner load
-    // - Help keep number of versions down by always using latest breaking change
-    // - Have lockfile and manifest in-sync
     {
       matchManagers: [
         'cargo',

From d0c65b26493d60f86a82c5919ef736b35808c23a Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 2 Dec 2023 13:01:27 -0700
Subject: [PATCH 063/302] fix!: Move format options to `Renderer`

BREAKING CHANGE: This removes `opt` from `Snippet`
---
 benches/simple.rs                             |  16 +--
 examples/expected_type.rs                     |  11 +-
 examples/footer.rs                            |  11 +-
 examples/format.rs                            |  11 +-
 examples/multislice.rs                        |  11 +-
 src/display_list/mod.rs                       |  43 +++---
 src/renderer/mod.rs                           |  49 ++++++-
 src/snippet.rs                                |   3 -
 tests/{snippet => deserialize}/mod.rs         | 131 +++++++++---------
 tests/dl_from_snippet.rs                      |  27 ++--
 tests/fixtures/no-color/issue_52.toml         |   6 +-
 tests/fixtures/no-color/issue_9.toml          |  14 +-
 .../no-color/multiline_annotation.toml        |   8 +-
 .../no-color/multiline_annotation2.toml       |   6 +-
 .../no-color/multiline_annotation3.toml       |   6 +-
 .../no-color/multiple_annotations.toml        |   8 +-
 tests/fixtures/no-color/simple.toml           |  10 +-
 tests/fixtures/no-color/strip_line.toml       |  10 +-
 tests/fixtures/no-color/strip_line_char.toml  |  10 +-
 .../fixtures/no-color/strip_line_non_ws.toml  |  11 +-
 tests/fixtures_test.rs                        |  11 +-
 tests/formatter.rs                            |  15 +-
 22 files changed, 208 insertions(+), 220 deletions(-)
 rename tests/{snippet => deserialize}/mod.rs (88%)

diff --git a/benches/simple.rs b/benches/simple.rs
index 4427afd..8ccd18f 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -5,12 +5,9 @@ extern crate criterion;
 use criterion::{black_box, Criterion};
 
 use annotate_snippets::renderer::Renderer;
-use annotate_snippets::{
-    display_list::FormatOptions,
-    snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
-};
+use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
 
-fn create_snippet() {
+fn create_snippet(renderer: Renderer) {
     let snippet = Snippet {
         slices: vec![Slice {
             source: r#") -> Option<String> {
@@ -57,18 +54,15 @@ fn create_snippet() {
             annotation_type: AnnotationType::Error,
         }),
         footer: vec![],
-        opt: FormatOptions {
-            color: true,
-            ..Default::default()
-        },
     };
 
-    let renderer = Renderer;
     let _result = renderer.render(snippet).to_string();
 }
 
 pub fn criterion_benchmark(c: &mut Criterion) {
-    c.bench_function("format", |b| b.iter(|| black_box(create_snippet())));
+    c.bench_function("format", |b| {
+        b.iter(|| black_box(create_snippet(Renderer::plain())))
+    });
 }
 
 criterion_group!(benches, criterion_benchmark);
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index cd23bfc..959419c 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,8 +1,5 @@
 use annotate_snippets::renderer::Renderer;
-use annotate_snippets::{
-    display_list::FormatOptions,
-    snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
-};
+use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
 
 fn main() {
     let snippet = Snippet {
@@ -33,12 +30,8 @@ fn main() {
                 },
             ],
         }],
-        opt: FormatOptions {
-            color: true,
-            ..Default::default()
-        },
     };
 
-    let renderer = Renderer;
+    let renderer = Renderer::plain();
     println!("{}", renderer.render(snippet));
 }
diff --git a/examples/footer.rs b/examples/footer.rs
index 6bf6f5e..0191c1d 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,8 +1,5 @@
 use annotate_snippets::renderer::Renderer;
-use annotate_snippets::{
-    display_list::FormatOptions,
-    snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
-};
+use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
 
 fn main() {
     let snippet = Snippet {
@@ -29,12 +26,8 @@ fn main() {
                 annotation_type: AnnotationType::Error,
             }],
         }],
-        opt: FormatOptions {
-            color: true,
-            ..Default::default()
-        },
     };
 
-    let renderer = Renderer;
+    let renderer = Renderer::plain();
     println!("{}", renderer.render(snippet));
 }
diff --git a/examples/format.rs b/examples/format.rs
index a053ccc..7302eef 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,8 +1,5 @@
 use annotate_snippets::renderer::Renderer;
-use annotate_snippets::{
-    display_list::FormatOptions,
-    snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
-};
+use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
 
 fn main() {
     let snippet = Snippet {
@@ -51,12 +48,8 @@ fn main() {
             annotation_type: AnnotationType::Error,
         }),
         footer: vec![],
-        opt: FormatOptions {
-            color: true,
-            ..Default::default()
-        },
     };
 
-    let renderer = Renderer;
+    let renderer = Renderer::plain();
     println!("{}", renderer.render(snippet));
 }
diff --git a/examples/multislice.rs b/examples/multislice.rs
index a875ae2..dc51d4f 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,8 +1,5 @@
 use annotate_snippets::renderer::Renderer;
-use annotate_snippets::{
-    display_list::FormatOptions,
-    snippet::{Annotation, AnnotationType, Slice, Snippet},
-};
+use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet};
 
 fn main() {
     let snippet = Snippet {
@@ -28,12 +25,8 @@ fn main() {
                 annotations: vec![],
             },
         ],
-        opt: FormatOptions {
-            color: true,
-            ..Default::default()
-        },
     };
 
-    let renderer = Renderer;
+    let renderer = Renderer::plain();
     println!("{}", renderer.render(snippet));
 }
diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs
index 1ad328a..1498dc5 100644
--- a/src/display_list/mod.rs
+++ b/src/display_list/mod.rs
@@ -108,13 +108,28 @@ impl<'a> Display for DisplayList<'a> {
 }
 
 impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
-    fn from(
+    fn from(snippet: snippet::Snippet<'a>) -> DisplayList<'a> {
+        Self::new(snippet, false, false, None)
+    }
+}
+
+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(
         snippet::Snippet {
             title,
             footer,
             slices,
-            opt,
         }: snippet::Snippet<'a>,
+        color: bool,
+        anonymized_line_numbers: bool,
+        margin: Option<Margin>,
     ) -> DisplayList<'a> {
         let mut body = vec![];
         if let Some(annotation) = title {
@@ -126,7 +141,7 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
                 slice,
                 idx == 0,
                 !footer.is_empty(),
-                opt.margin,
+                margin,
             ));
         }
 
@@ -134,12 +149,6 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
             body.append(&mut format_annotation(annotation));
         }
 
-        let FormatOptions {
-            color,
-            anonymized_line_numbers,
-            margin,
-        } = opt;
-
         Self {
             body,
             stylesheet: get_term_style(color),
@@ -147,15 +156,6 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
             margin,
         }
     }
-}
-
-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";
 
     #[inline]
     fn format_annotation_type(
@@ -527,13 +527,6 @@ impl<'a> DisplayList<'a> {
     }
 }
 
-#[derive(Debug, Default, Copy, Clone)]
-pub struct FormatOptions {
-    pub color: bool,
-    pub anonymized_line_numbers: bool,
-    pub margin: Option<Margin>,
-}
-
 #[derive(Clone, Copy, Debug)]
 pub struct Margin {
     /// The available whitespace in the left that can be consumed when centering.
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 9a66c1c..b1f5f05 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1,11 +1,54 @@
-use crate::display_list::DisplayList;
+use crate::display_list::{DisplayList, Margin};
 use crate::snippet::Snippet;
 use std::fmt::Display;
 
-pub struct Renderer;
+#[derive(Clone)]
+pub struct Renderer {
+    color: bool,
+    anonymized_line_numbers: bool,
+    margin: Option<Margin>,
+}
 
 impl Renderer {
+    /// No terminal styling
+    pub fn plain() -> Self {
+        Self {
+            color: false,
+            anonymized_line_numbers: false,
+            margin: None,
+        }
+    }
+
+    /// Default terminal styling
+    pub fn styled() -> Self {
+        Self {
+            color: true,
+            anonymized_line_numbers: false,
+            margin: None,
+        }
+    }
+
+    pub fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
+        self.anonymized_line_numbers = anonymized_line_numbers;
+        self
+    }
+
+    pub fn color(mut self, color: bool) -> Self {
+        self.color = color;
+        self
+    }
+
+    pub fn margin(mut self, margin: Option<Margin>) -> Self {
+        self.margin = margin;
+        self
+    }
+
     pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a {
-        DisplayList::from(snippet)
+        DisplayList::new(
+            snippet,
+            self.color,
+            self.anonymized_line_numbers,
+            self.margin,
+        )
     }
 }
diff --git a/src/snippet.rs b/src/snippet.rs
index bc7ba00..c1914c9 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -28,10 +28,8 @@
 //!             annotations: vec![],
 //!         },
 //!     ],
-//!     opt: Default::default(),
 //! };
 //! ```
-use crate::display_list::FormatOptions;
 
 /// Primary structure provided for formatting
 #[derive(Debug, Default)]
@@ -39,7 +37,6 @@ pub struct Snippet<'a> {
     pub title: Option<Annotation<'a>>,
     pub footer: Vec<Annotation<'a>>,
     pub slices: Vec<Slice<'a>>,
-    pub opt: FormatOptions,
 }
 
 /// Structure containing the slice of text to be annotated and
diff --git a/tests/snippet/mod.rs b/tests/deserialize/mod.rs
similarity index 88%
rename from tests/snippet/mod.rs
rename to tests/deserialize/mod.rs
index 92d272d..58d14ef 100644
--- a/tests/snippet/mod.rs
+++ b/tests/deserialize/mod.rs
@@ -1,10 +1,19 @@
 use serde::{Deserialize, Deserializer, Serialize};
 
+use annotate_snippets::renderer::Renderer;
 use annotate_snippets::{
-    display_list::{FormatOptions, Margin},
+    display_list::Margin,
     snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
 };
 
+#[derive(Deserialize)]
+pub struct Fixture<'a> {
+    #[serde(default)]
+    pub renderer: RendererDef,
+    #[serde(borrow)]
+    pub snippet: SnippetDef<'a>,
+}
+
 #[derive(Deserialize)]
 pub struct SnippetDef<'a> {
     #[serde(deserialize_with = "deserialize_annotation")]
@@ -15,9 +24,6 @@ pub struct SnippetDef<'a> {
     #[serde(default)]
     #[serde(borrow)]
     pub footer: Vec<Annotation<'a>>,
-    #[serde(deserialize_with = "deserialize_opt")]
-    #[serde(default)]
-    pub opt: FormatOptions,
     #[serde(deserialize_with = "deserialize_slices")]
     #[serde(borrow)]
     pub slices: Vec<Slice<'a>>,
@@ -28,76 +34,16 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
         let SnippetDef {
             title,
             footer,
-            opt,
             slices,
         } = val;
         Snippet {
             title,
             footer,
             slices,
-            opt,
         }
     }
 }
 
-fn deserialize_opt<'de, D>(deserializer: D) -> Result<FormatOptions, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    #[derive(Deserialize)]
-    struct Wrapper(#[serde(with = "FormatOptionsDef")] FormatOptions);
-
-    Wrapper::deserialize(deserializer).map(|w| w.0)
-}
-
-#[derive(Deserialize)]
-#[serde(remote = "FormatOptions")]
-pub struct FormatOptionsDef {
-    #[serde(default)]
-    pub color: bool,
-    #[serde(default)]
-    pub anonymized_line_numbers: bool,
-    #[serde(deserialize_with = "deserialize_margin")]
-    #[serde(default)]
-    pub margin: Option<Margin>,
-}
-
-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,
-            )
-        })
-    })
-}
-
 fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error>
 where
     D: Deserializer<'de>,
@@ -206,3 +152,60 @@ enum AnnotationTypeDef {
     Note,
     Help,
 }
+
+#[derive(Default, Deserialize)]
+pub struct RendererDef {
+    #[serde(default)]
+    anonymized_line_numbers: bool,
+    #[serde(deserialize_with = "deserialize_margin")]
+    #[serde(default)]
+    margin: Option<Margin>,
+}
+
+impl From<RendererDef> for Renderer {
+    fn from(val: RendererDef) -> Self {
+        let RendererDef {
+            anonymized_line_numbers,
+            margin,
+        } = val;
+        Renderer::plain()
+            .anonymized_line_numbers(anonymized_line_numbers)
+            .margin(margin)
+    }
+}
+
+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/dl_from_snippet.rs b/tests/dl_from_snippet.rs
index 06f2ff7..9971c7e 100644
--- a/tests/dl_from_snippet.rs
+++ b/tests/dl_from_snippet.rs
@@ -11,7 +11,6 @@ fn test_format_title() {
         }),
         footer: vec![],
         slices: vec![],
-        opt: Default::default(),
     };
     let output = dl::DisplayList {
         body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation {
@@ -26,8 +25,8 @@ fn test_format_title() {
             source_aligned: false,
             continuation: false,
         })],
-        stylesheet: get_term_style(input.opt.color),
-        anonymized_line_numbers: input.opt.anonymized_line_numbers,
+        stylesheet: get_term_style(false),
+        anonymized_line_numbers: false,
         margin: None,
     };
     assert_eq!(dl::DisplayList::from(input), output);
@@ -48,7 +47,6 @@ fn test_format_slice() {
             annotations: vec![],
             fold: false,
         }],
-        opt: Default::default(),
     };
     let output = dl::DisplayList {
         body: vec![
@@ -79,8 +77,8 @@ fn test_format_slice() {
                 line: dl::DisplaySourceLine::Empty,
             },
         ],
-        stylesheet: get_term_style(input.opt.color),
-        anonymized_line_numbers: input.opt.anonymized_line_numbers,
+        stylesheet: get_term_style(false),
+        anonymized_line_numbers: false,
         margin: None,
     };
     assert_eq!(dl::DisplayList::from(input), output);
@@ -111,7 +109,6 @@ fn test_format_slices_continuation() {
                 fold: false,
             },
         ],
-        opt: Default::default(),
     };
     let output = dl::DisplayList {
         body: vec![
@@ -162,8 +159,8 @@ fn test_format_slices_continuation() {
                 line: dl::DisplaySourceLine::Empty,
             },
         ],
-        stylesheet: get_term_style(input.opt.color),
-        anonymized_line_numbers: input.opt.anonymized_line_numbers,
+        stylesheet: get_term_style(false),
+        anonymized_line_numbers: false,
         margin: None,
     };
     assert_eq!(dl::DisplayList::from(input), output);
@@ -190,7 +187,6 @@ fn test_format_slice_annotation_standalone() {
             }],
             fold: false,
         }],
-        opt: Default::default(),
     };
     let output = dl::DisplayList {
         body: vec![
@@ -238,8 +234,8 @@ fn test_format_slice_annotation_standalone() {
                 line: dl::DisplaySourceLine::Empty,
             },
         ],
-        stylesheet: get_term_style(input.opt.color),
-        anonymized_line_numbers: input.opt.anonymized_line_numbers,
+        stylesheet: get_term_style(false),
+        anonymized_line_numbers: false,
         margin: None,
     };
     assert_eq!(dl::DisplayList::from(input), output);
@@ -255,7 +251,6 @@ fn test_format_label() {
             annotation_type: snippet::AnnotationType::Error,
         }],
         slices: vec![],
-        opt: Default::default(),
     };
     let output = dl::DisplayList {
         body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation {
@@ -270,8 +265,8 @@ fn test_format_label() {
             source_aligned: true,
             continuation: false,
         })],
-        stylesheet: get_term_style(input.opt.color),
-        anonymized_line_numbers: input.opt.anonymized_line_numbers,
+        stylesheet: get_term_style(false),
+        anonymized_line_numbers: false,
         margin: None,
     };
     assert_eq!(dl::DisplayList::from(input), output);
@@ -296,7 +291,6 @@ fn test_i26() {
             origin: None,
             fold: false,
         }],
-        opt: Default::default(),
     };
 
     let _ = dl::DisplayList::from(input);
@@ -322,7 +316,6 @@ fn test_i_29() {
             }],
             fold: true,
         }],
-        opt: Default::default(),
     };
 
     let expected = DisplayList {
diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml
index 8d54613..d31e0cb 100644
--- a/tests/fixtures/no-color/issue_52.toml
+++ b/tests/fixtures/no-color/issue_52.toml
@@ -1,7 +1,7 @@
-[title]
+[snippet.title]
 annotation_type = "Error"
 
-[[slices]]
+[[snippet.slices]]
 source = """
 
 
@@ -10,7 +10,7 @@ invalid syntax
 line_start = 1
 origin = "path/to/error.rs"
 fold = true
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "error here"
 annotation_type = "Warning"
 range = [2,16]
diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml
index a30563b..ee1fe27 100644
--- a/tests/fixtures/no-color/issue_9.toml
+++ b/tests/fixtures/no-color/issue_9.toml
@@ -1,28 +1,28 @@
-[title]
+[snippet.title]
 label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 annotation_type = "Error"
 
-[[slices]]
+[[snippet.slices]]
 source = "let x = vec![1];"
 line_start = 4
 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs"
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait"
 annotation_type = "Warning"
 range = [4, 5]
 
-[[slices]]
+[[snippet.slices]]
 source = "let y = x;"
 line_start = 7
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "value moved here"
 annotation_type = "Warning"
 range = [8, 9]
 
-[[slices]]
+[[snippet.slices]]
 source = "x;"
 line_start = 9
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "value used here after move"
 annotation_type = "Error"
 range = [0, 1]
diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml
index c3dc1e9..604e04b 100644
--- a/tests/fixtures/no-color/multiline_annotation.toml
+++ b/tests/fixtures/no-color/multiline_annotation.toml
@@ -1,4 +1,4 @@
-[[slices]]
+[[snippet.slices]]
 source = """
 ) -> Option<String> {
     for ann in annotations {
@@ -26,15 +26,15 @@ source = """
 line_start = 51
 origin = "src/format.rs"
 fold = true
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "expected `std::option::Option<std::string::String>` because of return type"
 annotation_type = "Warning"
 range = [5, 19]
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "expected enum `std::option::Option`, found ()"
 annotation_type = "Error"
 range = [22, 766]
-[title]
+[snippet.title]
 label = "mismatched types"
 id = "E0308"
 annotation_type =  "Error"
diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml
index 845bf9f..3287fdc 100644
--- a/tests/fixtures/no-color/multiline_annotation2.toml
+++ b/tests/fixtures/no-color/multiline_annotation2.toml
@@ -1,4 +1,4 @@
-[[slices]]
+[[snippet.slices]]
 source = """
                         if let DisplayLine::Source {
                             ref mut inline_marks,
@@ -7,12 +7,12 @@ source = """
 line_start = 139
 origin = "src/display_list.rs"
 fold = false
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "missing fields `lineno`, `content`"
 annotation_type = "Error"
 range = [31, 128]
 
-[title]
+[snippet.title]
 label = "pattern does not mention fields `lineno`, `content`"
 id = "E0027"
 annotation_type = "Error"
diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml
index 21bbcd8..9fe85fb 100644
--- a/tests/fixtures/no-color/multiline_annotation3.toml
+++ b/tests/fixtures/no-color/multiline_annotation3.toml
@@ -1,4 +1,4 @@
-[[slices]]
+[[snippet.slices]]
 source = """
 This is an exampl
 e of an edge case of an annotation overflowing
@@ -7,12 +7,12 @@ to exactly one character on next line.
 line_start = 26
 origin = "foo.txt"
 fold = false
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "this should not be on separate lines"
 annotation_type = "Error"
 range = [11, 18]
 
-[title]
+[snippet.title]
 label = "spacing error found"
 id = "E####"
 annotation_type = "Error"
diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml
index 84efc5f..f037c9a 100644
--- a/tests/fixtures/no-color/multiple_annotations.toml
+++ b/tests/fixtures/no-color/multiple_annotations.toml
@@ -1,4 +1,4 @@
-[[slices]]
+[[snippet.slices]]
 source = """
 fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {
     if let Some(annotation) = main_annotation {
@@ -11,15 +11,15 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>
 }
 """
 line_start = 96
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "Variable defined here"
 annotation_type = "Error"
 range = [100, 110]
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "Referenced here"
 annotation_type = "Error"
 range = [184, 194]
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "Referenced again here"
 annotation_type = "Error"
 range = [243, 253]
diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml
index 6c38674..2e9b6d8 100644
--- a/tests/fixtures/no-color/simple.toml
+++ b/tests/fixtures/no-color/simple.toml
@@ -1,18 +1,18 @@
-[[slices]]
+[[snippet.slices]]
 source = """
         })
 
         for line in &self.body {"""
 line_start = 169
 origin = "src/format_color.rs"
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "unexpected token"
 annotation_type = "Error"
 range = [20, 23]
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "expected one of `.`, `;`, `?`, or an operator here"
 annotation_type = "Warning"
 range = [10, 11]
-[title]
+[snippet.title]
 label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
-annotation_type = "Error"
+annotation_type = "Error"
\ No newline at end of file
diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml
index 76d9519..4c609c4 100644
--- a/tests/fixtures/no-color/strip_line.toml
+++ b/tests/fixtures/no-color/strip_line.toml
@@ -1,22 +1,22 @@
-[title]
+[snippet.title]
 id = "E0308"
 label = "mismatched types"
 annotation_type = "Error"
 
-[[slices]]
+[[snippet.slices]]
 source = "                                                                                                                                                                                    let _: () = 42;"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "expected (), found integer"
 annotation_type = "Error"
 range = [192, 194]
 
-[opt]
+[renderer]
 color = false
 anonymized_line_numbers = true
-[opt.margin]
+[renderer.margin]
 whitespace_left = 180
 span_left = 192
 span_right = 194
diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml
index 5b432be..76ea749 100644
--- a/tests/fixtures/no-color/strip_line_char.toml
+++ b/tests/fixtures/no-color/strip_line_char.toml
@@ -1,22 +1,22 @@
-[title]
+[snippet.title]
 id = "E0308"
 label = "mismatched types"
 annotation_type = "Error"
 
-[[slices]]
+[[snippet.slices]]
 source = "                                                                                                                                                                                    let _: () = 42ñ"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "expected (), found integer"
 annotation_type = "Error"
 range = [192, 194]
 
-[opt]
+[renderer]
 color = false
 anonymized_line_numbers = true
-[opt.margin]
+[renderer.margin]
 whitespace_left = 180
 span_left = 192
 span_right = 194
diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml
index 5129f5c..c46d6e2 100644
--- a/tests/fixtures/no-color/strip_line_non_ws.toml
+++ b/tests/fixtures/no-color/strip_line_non_ws.toml
@@ -1,22 +1,21 @@
-[title]
+[snippet.title]
 id = "E0308"
 label = "mismatched types"
 annotation_type = "Error"
 
-[[slices]]
+[[snippet.slices]]
 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"
 
-[[slices.annotations]]
+[[snippet.slices.annotations]]
 label = "expected (), found integer"
 annotation_type = "Error"
 range = [240, 242]
 
-[opt]
-color = false
+[renderer]
 anonymized_line_numbers = true
-[opt.margin]
+[renderer.margin]
 whitespace_left = 4
 span_left = 240
 span_right = 242
diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs
index 8dfd229..854719f 100644
--- a/tests/fixtures_test.rs
+++ b/tests/fixtures_test.rs
@@ -1,7 +1,7 @@
+mod deserialize;
 mod diff;
-mod snippet;
 
-use crate::snippet::SnippetDef;
+use crate::deserialize::Fixture;
 use annotate_snippets::renderer::Renderer;
 use annotate_snippets::snippet::Snippet;
 use glob::glob;
@@ -14,8 +14,8 @@ fn read_file(path: &str) -> Result<String, io::Error> {
     Ok(s.trim_end().to_string())
 }
 
-fn read_fixture(src: &str) -> Result<Snippet<'_>, Box<dyn Error>> {
-    Ok(toml::from_str(src).map(|a: SnippetDef| a.into())?)
+fn read_fixture(src: &str) -> Result<(Renderer, Snippet<'_>), Box<dyn Error>> {
+    Ok(toml::from_str(src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?)
 }
 
 #[test]
@@ -28,10 +28,9 @@ fn test_fixtures() {
         let path_out = path_in.replace(".toml", ".txt");
 
         let src = read_file(path_in).expect("Failed to read file");
-        let snippet = read_fixture(&src).expect("Failed to read file");
+        let (renderer, snippet) = read_fixture(&src).expect("Failed to read file");
         let expected_out = read_file(&path_out).expect("Failed to read file");
 
-        let renderer = Renderer;
         let actual_out = renderer.render(snippet).to_string();
         println!("{}", expected_out);
         println!("{}", actual_out.trim_end());
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 1226ab4..2117fec 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -539,7 +539,6 @@ fn test_i_29() {
             }],
             fold: true,
         }],
-        opt: Default::default(),
     };
     let expected = r#"error: oops
  --> <current file>:2:8
@@ -549,7 +548,7 @@ fn test_i_29() {
   |        ^^^^ oops
   |"#;
 
-    let renderer = Renderer;
+    let renderer = Renderer::plain();
     assert_eq!(renderer.render(snippets).to_string(), expected);
 }
 
@@ -569,7 +568,6 @@ fn test_point_to_double_width_characters() {
         }],
         title: None,
         footer: vec![],
-        opt: Default::default(),
     };
 
     let expected = r#" --> <current file>:1:7
@@ -578,7 +576,7 @@ fn test_point_to_double_width_characters() {
   |             ^^^^ world
   |"#;
 
-    let renderer = Renderer;
+    let renderer = Renderer::plain();
     assert_eq!(renderer.render(snippets).to_string(), expected);
 }
 
@@ -598,7 +596,6 @@ fn test_point_to_double_width_characters_across_lines() {
         }],
         title: None,
         footer: vec![],
-        opt: Default::default(),
     };
 
     let expected = r#" --> <current file>:1:3
@@ -609,7 +606,7 @@ fn test_point_to_double_width_characters_across_lines() {
   | |______^ Good morning
   |"#;
 
-    let renderer = Renderer;
+    let renderer = Renderer::plain();
     assert_eq!(renderer.render(snippets).to_string(), expected);
 }
 
@@ -636,7 +633,6 @@ fn test_point_to_double_width_characters_multiple() {
         }],
         title: None,
         footer: vec![],
-        opt: Default::default(),
     };
 
     let expected = r#" --> <current file>:1:1
@@ -647,7 +643,7 @@ fn test_point_to_double_width_characters_multiple() {
   |     ---- note: Sushi2
   |"#;
 
-    let renderer = Renderer;
+    let renderer = Renderer::plain();
     assert_eq!(renderer.render(snippets).to_string(), expected);
 }
 
@@ -667,7 +663,6 @@ fn test_point_to_double_width_characters_mixed() {
         }],
         title: None,
         footer: vec![],
-        opt: Default::default(),
     };
 
     let expected = r#" --> <current file>:1:7
@@ -676,6 +671,6 @@ fn test_point_to_double_width_characters_mixed() {
   |             ^^^^^^^^^^^ New world
   |"#;
 
-    let renderer = Renderer;
+    let renderer = Renderer::plain();
     assert_eq!(renderer.render(snippets).to_string(), expected);
 }

From 4affdfb50ea0670d85e52737c082c03f89ae8ada Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 2 Dec 2023 13:36:57 -0700
Subject: [PATCH 064/302] fix!: Move to a new `StyleSheet`

BREAKING CHANGE: This removes the `formatter` and `stylesheets` modules
---
 src/display_list/mod.rs     | 114 +++++++++++++++++-------------------
 src/formatter/mod.rs        |  23 --------
 src/formatter/style.rs      |  51 ----------------
 src/lib.rs                  |   2 -
 src/renderer/mod.rs         |  68 +++++++++++++++++----
 src/renderer/stylesheet.rs  |  62 ++++++++++++++++++++
 src/stylesheets/color.rs    |  50 ----------------
 src/stylesheets/mod.rs      |  11 ----
 src/stylesheets/no_color.rs |  31 ----------
 tests/dl_from_snippet.rs    |  15 ++---
 10 files changed, 182 insertions(+), 245 deletions(-)
 delete mode 100644 src/formatter/mod.rs
 delete mode 100644 src/formatter/style.rs
 create mode 100644 src/renderer/stylesheet.rs
 delete mode 100644 src/stylesheets/color.rs
 delete mode 100644 src/stylesheets/mod.rs
 delete mode 100644 src/stylesheets/no_color.rs

diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs
index 1498dc5..6b1720f 100644
--- a/src/display_list/mod.rs
+++ b/src/display_list/mod.rs
@@ -31,18 +31,18 @@
 //! styling.
 //!
 //! 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::{cmp, fmt};
+use yansi_term::Style;
 
-use crate::formatter::style::{Style, StyleClass};
-use crate::formatter::{get_term_style, style::Stylesheet};
-use crate::snippet;
+use crate::renderer::stylesheet::Stylesheet;
 
 /// List of lines to be displayed.
 pub struct DisplayList<'a> {
     pub body: Vec<DisplayLine<'a>>,
-    pub stylesheet: Box<dyn Stylesheet>,
+    pub stylesheet: Stylesheet,
     pub anonymized_line_numbers: bool,
     pub margin: Option<Margin>,
 }
@@ -52,7 +52,7 @@ impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> {
         Self {
             body,
             anonymized_line_numbers: false,
-            stylesheet: get_term_style(false),
+            stylesheet: Stylesheet::default(),
             margin: None,
         }
     }
@@ -109,7 +109,7 @@ impl<'a> Display for DisplayList<'a> {
 
 impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
     fn from(snippet: snippet::Snippet<'a>) -> DisplayList<'a> {
-        Self::new(snippet, false, false, None)
+        Self::new(snippet, Stylesheet::default(), false, None)
     }
 }
 
@@ -127,7 +127,7 @@ impl<'a> DisplayList<'a> {
             footer,
             slices,
         }: snippet::Snippet<'a>,
-        color: bool,
+        stylesheet: Stylesheet,
         anonymized_line_numbers: bool,
         margin: Option<Margin>,
     ) -> DisplayList<'a> {
@@ -151,7 +151,7 @@ impl<'a> DisplayList<'a> {
 
         Self {
             body,
-            stylesheet: get_term_style(color),
+            stylesheet,
             anonymized_line_numbers,
             margin,
         }
@@ -183,15 +183,15 @@ impl<'a> DisplayList<'a> {
         }
     }
 
-    fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> {
-        self.stylesheet.get_style(match annotation_type {
-            DisplayAnnotationType::Error => StyleClass::Error,
-            DisplayAnnotationType::Warning => StyleClass::Warning,
-            DisplayAnnotationType::Info => StyleClass::Info,
-            DisplayAnnotationType::Note => StyleClass::Note,
-            DisplayAnnotationType::Help => StyleClass::Help,
-            DisplayAnnotationType::None => StyleClass::None,
-        })
+    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(),
+        }
     }
 
     fn format_label(
@@ -199,12 +199,12 @@ impl<'a> DisplayList<'a> {
         label: &[DisplayTextFragment<'_>],
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
-        let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis);
+        let emphasis_style = self.stylesheet.emphasis();
 
         for fragment in label {
             match fragment.style {
                 DisplayTextStyle::Regular => fragment.content.fmt(f)?,
-                DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?,
+                DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content).fmt(f)?,
             }
         }
         Ok(())
@@ -231,8 +231,8 @@ impl<'a> DisplayList<'a> {
         if formatted_len == 0 {
             self.format_label(&annotation.label, f)
         } else {
-            color.paint_fn(
-                Box::new(|f| {
+            color
+                .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
                     Self::format_annotation_type(&annotation.annotation_type, f)?;
                     if let Some(id) = &annotation.id {
                         f.write_char('[')?;
@@ -240,18 +240,17 @@ impl<'a> DisplayList<'a> {
                         f.write_char(']')?;
                     }
                     Ok(())
-                }),
-                f,
-            )?;
+                }))
+                .fmt(f)?;
+
             if !is_annotation_empty(annotation) {
                 if in_source {
-                    color.paint_fn(
-                        Box::new(|f| {
+                    color
+                        .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
                             f.write_str(": ")?;
                             self.format_label(&annotation.label, f)
-                        }),
-                        f,
-                    )?;
+                        }))
+                        .fmt(f)?;
                 } else {
                     f.write_str(": ")?;
                     self.format_label(&annotation.label, f)?;
@@ -362,27 +361,25 @@ impl<'a> DisplayList<'a> {
                     _ => range.0,
                 };
 
-                color.paint_fn(
-                    Box::new(|f| {
+                color
+                    .paint_fn(|f| {
                         format_repeat_char(indent_char, indent_length + 1, f)?;
                         format_repeat_char(mark, range.1 - indent_length, f)
-                    }),
-                    f,
-                )?;
+                    })
+                    .fmt(f)?;
 
                 if !is_annotation_empty(annotation) {
                     f.write_char(' ')?;
-                    color.paint_fn(
-                        Box::new(|f| {
+                    color
+                        .paint_fn(|f| {
                             self.format_annotation(
                                 annotation,
                                 annotation_part == &DisplayAnnotationPart::LabelContinuation,
                                 true,
                                 f,
                             )
-                        }),
-                        f,
-                    )?;
+                        })
+                        .fmt(f)?;
                 }
 
                 Ok(())
@@ -407,11 +404,11 @@ impl<'a> DisplayList<'a> {
                     DisplayHeaderType::Initial => "-->",
                     DisplayHeaderType::Continuation => ":::",
                 };
-                let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
+                let lineno_color = self.stylesheet.line_no();
 
                 if let Some((col, row)) = pos {
                     format_repeat_char(' ', lineno_width, f)?;
-                    lineno_color.paint(header_sigil, f)?;
+                    lineno_color.paint(header_sigil).fmt(f)?;
                     f.write_char(' ')?;
                     path.fmt(f)?;
                     f.write_char(':')?;
@@ -420,7 +417,7 @@ impl<'a> DisplayList<'a> {
                     row.fmt(f)
                 } else {
                     format_repeat_char(' ', lineno_width, f)?;
-                    lineno_color.paint(header_sigil, f)?;
+                    lineno_color.paint(header_sigil).fmt(f)?;
                     f.write_char(' ')?;
                     path.fmt(f)
                 }
@@ -434,10 +431,10 @@ impl<'a> DisplayList<'a> {
                     if *continuation {
                         format_repeat_char(' ', lineno_width + 3, f)?;
                     } else {
-                        let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
+                        let lineno_color = self.stylesheet.line_no();
                         format_repeat_char(' ', lineno_width, f)?;
                         f.write_char(' ')?;
-                        lineno_color.paint("=", f)?;
+                        lineno_color.paint("=").fmt(f)?;
                         f.write_char(' ')?;
                     }
                 }
@@ -460,26 +457,24 @@ impl<'a> DisplayList<'a> {
                 inline_marks,
                 line,
             } => {
-                let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
+                let lineno_color = self.stylesheet.line_no();
                 if self.anonymized_line_numbers && lineno.is_some() {
-                    lineno_color.paint_fn(
-                        Box::new(|f| {
+                    lineno_color
+                        .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
                             f.write_str(Self::ANONYMIZED_LINE_NUM)?;
                             f.write_str(" |")
-                        }),
-                        f,
-                    )?;
+                        }))
+                        .fmt(f)?;
                 } else {
-                    lineno_color.paint_fn(
-                        Box::new(|f| {
+                    lineno_color
+                        .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
                             match lineno {
                                 Some(n) => write!(f, "{:>width$}", n, width = lineno_width),
                                 None => format_repeat_char(' ', lineno_width, f),
                             }?;
                             f.write_str(" |")
-                        }),
-                        f,
-                    )?;
+                        }))
+                        .fmt(f)?;
                 }
                 if *line != DisplaySourceLine::Empty {
                     if !inline_marks.is_empty() || 0 < inline_marks_width {
@@ -513,15 +508,14 @@ impl<'a> DisplayList<'a> {
     ) -> fmt::Result {
         format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?;
         for mark in inline_marks {
-            self.get_annotation_style(&mark.annotation_type).paint_fn(
-                Box::new(|f| {
+            self.get_annotation_style(&mark.annotation_type)
+                .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
                     f.write_char(match mark.mark_type {
                         DisplayMarkType::AnnotationThrough => '|',
                         DisplayMarkType::AnnotationStart => '/',
                     })
-                }),
-                f,
-            )?;
+                }))
+                .fmt(f)?;
         }
         Ok(())
     }
diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs
deleted file mode 100644
index 72bf9c7..0000000
--- a/src/formatter/mod.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-pub mod style;
-
-use self::style::Stylesheet;
-
-#[cfg(feature = "color")]
-use crate::stylesheets::color::AnsiTermStylesheet;
-use crate::stylesheets::no_color::NoColorStylesheet;
-
-#[cfg(feature = "color")]
-#[inline]
-pub fn get_term_style(color: bool) -> Box<dyn Stylesheet> {
-    if color {
-        Box::new(AnsiTermStylesheet)
-    } else {
-        Box::new(NoColorStylesheet)
-    }
-}
-
-#[cfg(not(feature = "color"))]
-#[inline]
-pub fn get_term_style(_color: bool) -> Box<dyn Stylesheet> {
-    Box::new(NoColorStylesheet)
-}
diff --git a/src/formatter/style.rs b/src/formatter/style.rs
deleted file mode 100644
index 3fc01c1..0000000
--- a/src/formatter/style.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-//! Set of structures required to implement a stylesheet
-//!
-//! In order to provide additional styling information for the
-//! formatter, a structs can implement `Stylesheet` and `Style`
-//! traits.
-//!
-use std::fmt;
-
-/// StyleClass is a collection of named variants of style classes
-pub enum StyleClass {
-    /// Message indicating an error.
-    Error,
-    /// Message indicating a warning.
-    Warning,
-    /// Message indicating an information.
-    Info,
-    /// Message indicating a note.
-    Note,
-    /// Message indicating a help.
-    Help,
-
-    /// Style for line numbers.
-    LineNo,
-
-    /// Parts of the text that are to be emphasised.
-    Emphasis,
-
-    /// Parts of the text that are regular. Usually a no-op.
-    None,
-}
-
-/// This trait implements a return value for the `Stylesheet::get_style`.
-pub trait Style {
-    /// The method used to write text with formatter
-    fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result;
-    /// The method used to write display function with formatter
-    fn paint_fn<'a>(
-        &self,
-        c: Box<dyn FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result + 'a>,
-        f: &mut fmt::Formatter<'_>,
-    ) -> fmt::Result;
-    /// The method used by the `Formatter` to display the message in bold font.
-    fn bold(&self) -> Box<dyn Style>;
-}
-
-/// Trait to annotate structs that can provide `Style` implementations for
-/// every `StyleClass` variant.
-pub trait Stylesheet {
-    /// Returns a `Style` implementer based on the requested `StyleClass` variant.
-    fn get_style(&self, class: StyleClass) -> Box<dyn Style>;
-}
diff --git a/src/lib.rs b/src/lib.rs
index e11e9d5..bf0dc05 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -49,7 +49,5 @@
 // TODO: check documentation
 
 pub mod display_list;
-pub mod formatter;
 pub mod renderer;
 pub mod snippet;
-pub mod stylesheets;
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index b1f5f05..a79d8d2 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1,30 +1,43 @@
+pub mod stylesheet;
+
 use crate::display_list::{DisplayList, Margin};
 use crate::snippet::Snippet;
 use std::fmt::Display;
+use stylesheet::Stylesheet;
+use yansi_term::Color::Fixed;
+use yansi_term::Style;
 
 #[derive(Clone)]
 pub struct Renderer {
-    color: bool,
     anonymized_line_numbers: bool,
     margin: Option<Margin>,
+    stylesheet: Stylesheet,
 }
 
 impl Renderer {
     /// No terminal styling
     pub fn plain() -> Self {
         Self {
-            color: false,
             anonymized_line_numbers: false,
             margin: None,
+            stylesheet: Stylesheet::default(),
         }
     }
 
     /// Default terminal styling
     pub fn styled() -> Self {
         Self {
-            color: true,
-            anonymized_line_numbers: false,
-            margin: None,
+            stylesheet: Stylesheet {
+                error: Fixed(9).bold(),
+                warning: Fixed(11).bold(),
+                info: Fixed(12).bold(),
+                note: Style::new().bold(),
+                help: Fixed(14).bold(),
+                line_no: Fixed(12).bold(),
+                emphasis: Style::new().bold(),
+                none: Style::new(),
+            },
+            ..Self::plain()
         }
     }
 
@@ -33,20 +46,55 @@ impl Renderer {
         self
     }
 
-    pub fn color(mut self, color: bool) -> Self {
-        self.color = color;
+    pub fn margin(mut self, margin: Option<Margin>) -> Self {
+        self.margin = margin;
         self
     }
 
-    pub fn margin(mut self, margin: Option<Margin>) -> Self {
-        self.margin = margin;
+    pub fn error(mut self, style: Style) -> Self {
+        self.stylesheet.error = style;
+        self
+    }
+
+    pub fn warning(mut self, style: Style) -> Self {
+        self.stylesheet.warning = style;
+        self
+    }
+
+    pub fn info(mut self, style: Style) -> Self {
+        self.stylesheet.info = style;
+        self
+    }
+
+    pub fn note(mut self, style: Style) -> Self {
+        self.stylesheet.note = style;
+        self
+    }
+
+    pub fn help(mut self, style: Style) -> Self {
+        self.stylesheet.help = style;
+        self
+    }
+
+    pub fn line_no(mut self, style: Style) -> Self {
+        self.stylesheet.line_no = style;
+        self
+    }
+
+    pub fn emphasis(mut self, style: Style) -> Self {
+        self.stylesheet.emphasis = style;
+        self
+    }
+
+    pub fn none(mut self, style: Style) -> Self {
+        self.stylesheet.none = style;
         self
     }
 
     pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a {
         DisplayList::new(
             snippet,
-            self.color,
+            self.stylesheet,
             self.anonymized_line_numbers,
             self.margin,
         )
diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs
new file mode 100644
index 0000000..f52c139
--- /dev/null
+++ b/src/renderer/stylesheet.rs
@@ -0,0 +1,62 @@
+use yansi_term::Style;
+
+#[derive(Clone, Copy, Debug)]
+pub struct Stylesheet {
+    pub(crate) error: Style,
+    pub(crate) warning: Style,
+    pub(crate) info: Style,
+    pub(crate) note: Style,
+    pub(crate) help: Style,
+    pub(crate) line_no: Style,
+    pub(crate) emphasis: Style,
+    pub(crate) none: Style,
+}
+
+impl Default for Stylesheet {
+    fn default() -> Self {
+        Self {
+            error: Style::new(),
+            warning: Style::new(),
+            info: Style::new(),
+            note: Style::new(),
+            help: Style::new(),
+            line_no: Style::new(),
+            emphasis: Style::new(),
+            none: Style::new(),
+        }
+    }
+}
+
+impl Stylesheet {
+    pub(crate) fn error(&self) -> &Style {
+        &self.error
+    }
+
+    pub(crate) fn warning(&self) -> &Style {
+        &self.warning
+    }
+
+    pub(crate) fn info(&self) -> &Style {
+        &self.info
+    }
+
+    pub(crate) fn note(&self) -> &Style {
+        &self.note
+    }
+
+    pub(crate) fn help(&self) -> &Style {
+        &self.help
+    }
+
+    pub(crate) fn line_no(&self) -> &Style {
+        &self.line_no
+    }
+
+    pub(crate) fn emphasis(&self) -> &Style {
+        &self.emphasis
+    }
+
+    pub(crate) fn none(&self) -> &Style {
+        &self.none
+    }
+}
diff --git a/src/stylesheets/color.rs b/src/stylesheets/color.rs
deleted file mode 100644
index 024dd06..0000000
--- a/src/stylesheets/color.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-use std::fmt::{self, Display};
-
-use yansi_term::{Color::Fixed, Style as AnsiTermStyle};
-
-use crate::formatter::style::{Style, StyleClass, Stylesheet};
-
-struct AnsiTermStyleWrapper {
-    style: AnsiTermStyle,
-}
-
-impl Style for AnsiTermStyleWrapper {
-    fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.style.paint(text).fmt(f)
-    }
-
-    fn paint_fn<'a>(
-        &self,
-        c: Box<dyn FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result + 'a>,
-        f: &mut fmt::Formatter<'_>,
-    ) -> fmt::Result {
-        self.style.paint_fn(c).fmt(f)
-    }
-
-    fn bold(&self) -> Box<dyn Style> {
-        Box::new(AnsiTermStyleWrapper { style: self.style })
-    }
-}
-
-pub struct AnsiTermStylesheet;
-
-impl Stylesheet for AnsiTermStylesheet {
-    fn get_style(&self, class: StyleClass) -> Box<dyn Style> {
-        let ansi_term_style = match class {
-            StyleClass::Error => Fixed(9).bold(),
-            StyleClass::Warning => Fixed(11).bold(),
-            StyleClass::Info => Fixed(12).bold(),
-            StyleClass::Note => AnsiTermStyle::new().bold(),
-            StyleClass::Help => Fixed(14).bold(),
-
-            StyleClass::LineNo => Fixed(12).bold(),
-
-            StyleClass::Emphasis => AnsiTermStyle::new().bold(),
-
-            StyleClass::None => AnsiTermStyle::new(),
-        };
-        Box::new(AnsiTermStyleWrapper {
-            style: ansi_term_style,
-        })
-    }
-}
diff --git a/src/stylesheets/mod.rs b/src/stylesheets/mod.rs
deleted file mode 100644
index 4648852..0000000
--- a/src/stylesheets/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-//! List of stylesheets
-//!
-//! The list depends on what optional dependencies the crate has been
-//! compiled with.
-//!
-//! By default the `no_color` is available. If the crate gets compiled
-//! with `ansi_term`, the `color` stylesheet is added.
-
-#[cfg(feature = "color")]
-pub mod color;
-pub mod no_color;
diff --git a/src/stylesheets/no_color.rs b/src/stylesheets/no_color.rs
deleted file mode 100644
index 21cb269..0000000
--- a/src/stylesheets/no_color.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use std::fmt;
-
-use crate::formatter::style::{Style, StyleClass, Stylesheet};
-
-pub struct NoOpStyle {}
-
-impl Style for NoOpStyle {
-    fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_str(text)
-    }
-
-    fn paint_fn<'a>(
-        &self,
-        c: Box<dyn FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result + 'a>,
-        f: &mut fmt::Formatter<'_>,
-    ) -> fmt::Result {
-        c(f)
-    }
-
-    fn bold(&self) -> Box<dyn Style> {
-        Box::new(NoOpStyle {})
-    }
-}
-
-pub struct NoColorStylesheet;
-
-impl Stylesheet for NoColorStylesheet {
-    fn get_style(&self, _class: StyleClass) -> Box<dyn Style> {
-        Box::new(NoOpStyle {})
-    }
-}
diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs
index 9971c7e..5fb0762 100644
--- a/tests/dl_from_snippet.rs
+++ b/tests/dl_from_snippet.rs
@@ -1,5 +1,6 @@
 use annotate_snippets::display_list::DisplayList;
-use annotate_snippets::{display_list as dl, formatter::get_term_style, snippet};
+use annotate_snippets::renderer::stylesheet::Stylesheet;
+use annotate_snippets::{display_list as dl, snippet};
 
 #[test]
 fn test_format_title() {
@@ -25,7 +26,7 @@ fn test_format_title() {
             source_aligned: false,
             continuation: false,
         })],
-        stylesheet: get_term_style(false),
+        stylesheet: Stylesheet::default(),
         anonymized_line_numbers: false,
         margin: None,
     };
@@ -77,7 +78,7 @@ fn test_format_slice() {
                 line: dl::DisplaySourceLine::Empty,
             },
         ],
-        stylesheet: get_term_style(false),
+        stylesheet: Stylesheet::default(),
         anonymized_line_numbers: false,
         margin: None,
     };
@@ -159,7 +160,7 @@ fn test_format_slices_continuation() {
                 line: dl::DisplaySourceLine::Empty,
             },
         ],
-        stylesheet: get_term_style(false),
+        stylesheet: Stylesheet::default(),
         anonymized_line_numbers: false,
         margin: None,
     };
@@ -234,7 +235,7 @@ fn test_format_slice_annotation_standalone() {
                 line: dl::DisplaySourceLine::Empty,
             },
         ],
-        stylesheet: get_term_style(false),
+        stylesheet: Stylesheet::default(),
         anonymized_line_numbers: false,
         margin: None,
     };
@@ -265,7 +266,7 @@ fn test_format_label() {
             source_aligned: true,
             continuation: false,
         })],
-        stylesheet: get_term_style(false),
+        stylesheet: Stylesheet::default(),
         anonymized_line_numbers: false,
         margin: None,
     };
@@ -381,7 +382,7 @@ fn test_i_29() {
                 line: dl::DisplaySourceLine::Empty,
             },
         ],
-        stylesheet: get_term_style(false),
+        stylesheet: Stylesheet::default(),
         anonymized_line_numbers: false,
         margin: None,
     };

From dfd4e87d6f31ec50d29af26d7310cff5e66ca978 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 2 Dec 2023 13:53:43 -0700
Subject: [PATCH 065/302] fix!: Switch to `anstyle` for color

BREAKING CHANGE: This removes `color` feature
---
 Cargo.lock                 |  11 +--
 Cargo.toml                 |   4 +-
 src/display_list/mod.rs    | 134 ++++++++++++++++++++-----------------
 src/renderer/mod.rs        |  17 +++--
 src/renderer/stylesheet.rs |   2 +-
 tests/diff/mod.rs          |  32 +++++++--
 6 files changed, 109 insertions(+), 91 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index c15507c..0d9659e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,13 +21,13 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 name = "annotate-snippets"
 version = "0.9.2"
 dependencies = [
+ "anstyle",
  "criterion",
  "difference",
  "glob",
  "serde",
  "toml",
  "unicode-width",
- "yansi-term",
 ]
 
 [[package]]
@@ -688,12 +688,3 @@ name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
-[[package]]
-name = "yansi-term"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
-dependencies = [
- "winapi",
-]
diff --git a/Cargo.toml b/Cargo.toml
index 8666651..ec39e99 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,8 +14,8 @@ keywords = ["code", "analysis", "ascii", "errors", "debug"]
 maintenance = { status = "actively-developed" }
 
 [dependencies]
+anstyle = "1.0.4"
 unicode-width = "0.1.11"
-yansi-term = { version = "0.1.2", optional = true }
 
 [dev-dependencies]
 criterion = "0.5.1"
@@ -23,7 +23,6 @@ difference = "2.0.0"
 glob = "0.3.1"
 serde = { version = "1.0.192", features = ["derive"] }
 toml = "0.5.11"
-yansi-term = "0.1.2"
 
 [[bench]]
 name = "simple"
@@ -31,4 +30,3 @@ harness = false
 
 [features]
 default = []
-color = ["yansi-term"]
diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs
index 6b1720f..b07d250 100644
--- a/src/display_list/mod.rs
+++ b/src/display_list/mod.rs
@@ -35,9 +35,8 @@ use crate::snippet;
 use std::cmp::{max, min};
 use std::fmt::{Display, Write};
 use std::{cmp, fmt};
-use yansi_term::Style;
 
-use crate::renderer::stylesheet::Stylesheet;
+use crate::renderer::{stylesheet::Stylesheet, Style};
 
 /// List of lines to be displayed.
 pub struct DisplayList<'a> {
@@ -204,7 +203,15 @@ impl<'a> DisplayList<'a> {
         for fragment in label {
             match fragment.style {
                 DisplayTextStyle::Regular => fragment.content.fmt(f)?,
-                DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content).fmt(f)?,
+                DisplayTextStyle::Emphasis => {
+                    write!(
+                        f,
+                        "{}{}{}",
+                        emphasis_style.render(),
+                        fragment.content,
+                        emphasis_style.render_reset()
+                    )?;
+                }
             }
         }
         Ok(())
@@ -231,26 +238,21 @@ impl<'a> DisplayList<'a> {
         if formatted_len == 0 {
             self.format_label(&annotation.label, f)
         } else {
-            color
-                .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
-                    Self::format_annotation_type(&annotation.annotation_type, f)?;
-                    if let Some(id) = &annotation.id {
-                        f.write_char('[')?;
-                        f.write_str(id)?;
-                        f.write_char(']')?;
-                    }
-                    Ok(())
-                }))
-                .fmt(f)?;
+            write!(f, "{}", color.render())?;
+            Self::format_annotation_type(&annotation.annotation_type, f)?;
+            if let Some(id) = &annotation.id {
+                f.write_char('[')?;
+                f.write_str(id)?;
+                f.write_char(']')?;
+            }
+            write!(f, "{}", color.render_reset())?;
 
             if !is_annotation_empty(annotation) {
                 if in_source {
-                    color
-                        .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
-                            f.write_str(": ")?;
-                            self.format_label(&annotation.label, f)
-                        }))
-                        .fmt(f)?;
+                    write!(f, "{}", color.render())?;
+                    f.write_str(": ")?;
+                    self.format_label(&annotation.label, f)?;
+                    write!(f, "{}", color.render_reset())?;
                 } else {
                     f.write_str(": ")?;
                     self.format_label(&annotation.label, f)?;
@@ -361,25 +363,21 @@ impl<'a> DisplayList<'a> {
                     _ => range.0,
                 };
 
-                color
-                    .paint_fn(|f| {
-                        format_repeat_char(indent_char, indent_length + 1, f)?;
-                        format_repeat_char(mark, range.1 - indent_length, f)
-                    })
-                    .fmt(f)?;
+                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(' ')?;
-                    color
-                        .paint_fn(|f| {
-                            self.format_annotation(
-                                annotation,
-                                annotation_part == &DisplayAnnotationPart::LabelContinuation,
-                                true,
-                                f,
-                            )
-                        })
-                        .fmt(f)?;
+                    write!(f, "{}", color.render())?;
+                    self.format_annotation(
+                        annotation,
+                        annotation_part == &DisplayAnnotationPart::LabelContinuation,
+                        true,
+                        f,
+                    )?;
+                    write!(f, "{}", color.render_reset())?;
                 }
 
                 Ok(())
@@ -408,7 +406,13 @@ impl<'a> DisplayList<'a> {
 
                 if let Some((col, row)) = pos {
                     format_repeat_char(' ', lineno_width, f)?;
-                    lineno_color.paint(header_sigil).fmt(f)?;
+                    write!(
+                        f,
+                        "{}{}{}",
+                        lineno_color.render(),
+                        header_sigil,
+                        lineno_color.render_reset()
+                    )?;
                     f.write_char(' ')?;
                     path.fmt(f)?;
                     f.write_char(':')?;
@@ -417,7 +421,13 @@ impl<'a> DisplayList<'a> {
                     row.fmt(f)
                 } else {
                     format_repeat_char(' ', lineno_width, f)?;
-                    lineno_color.paint(header_sigil).fmt(f)?;
+                    write!(
+                        f,
+                        "{}{}{}",
+                        lineno_color.render(),
+                        header_sigil,
+                        lineno_color.render_reset()
+                    )?;
                     f.write_char(' ')?;
                     path.fmt(f)
                 }
@@ -434,7 +444,12 @@ impl<'a> DisplayList<'a> {
                         let lineno_color = self.stylesheet.line_no();
                         format_repeat_char(' ', lineno_width, f)?;
                         f.write_char(' ')?;
-                        lineno_color.paint("=").fmt(f)?;
+                        write!(
+                            f,
+                            "{}={}",
+                            lineno_color.render(),
+                            lineno_color.render_reset()
+                        )?;
                         f.write_char(' ')?;
                     }
                 }
@@ -459,22 +474,18 @@ impl<'a> DisplayList<'a> {
             } => {
                 let lineno_color = self.stylesheet.line_no();
                 if self.anonymized_line_numbers && lineno.is_some() {
-                    lineno_color
-                        .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
-                            f.write_str(Self::ANONYMIZED_LINE_NUM)?;
-                            f.write_str(" |")
-                        }))
-                        .fmt(f)?;
+                    write!(f, "{}", lineno_color.render())?;
+                    f.write_str(Self::ANONYMIZED_LINE_NUM)?;
+                    f.write_str(" |")?;
+                    write!(f, "{}", lineno_color.render_reset())?;
                 } else {
-                    lineno_color
-                        .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
-                            match lineno {
-                                Some(n) => write!(f, "{:>width$}", n, width = lineno_width),
-                                None => format_repeat_char(' ', lineno_width, f),
-                            }?;
-                            f.write_str(" |")
-                        }))
-                        .fmt(f)?;
+                    write!(f, "{}", lineno_color.render())?;
+                    match lineno {
+                        Some(n) => write!(f, "{:>width$}", n, width = lineno_width),
+                        None => format_repeat_char(' ', lineno_width, f),
+                    }?;
+                    f.write_str(" |")?;
+                    write!(f, "{}", lineno_color.render_reset())?;
                 }
                 if *line != DisplaySourceLine::Empty {
                     if !inline_marks.is_empty() || 0 < inline_marks_width {
@@ -508,14 +519,13 @@ impl<'a> DisplayList<'a> {
     ) -> fmt::Result {
         format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?;
         for mark in inline_marks {
-            self.get_annotation_style(&mark.annotation_type)
-                .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| {
-                    f.write_char(match mark.mark_type {
-                        DisplayMarkType::AnnotationThrough => '|',
-                        DisplayMarkType::AnnotationStart => '/',
-                    })
-                }))
-                .fmt(f)?;
+            let annotation_style = self.get_annotation_style(&mark.annotation_type);
+            write!(f, "{}", annotation_style.render())?;
+            f.write_char(match mark.mark_type {
+                DisplayMarkType::AnnotationThrough => '|',
+                DisplayMarkType::AnnotationStart => '/',
+            })?;
+            write!(f, "{}", annotation_style.render_reset())?;
         }
         Ok(())
     }
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index a79d8d2..5b6d55b 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -2,10 +2,9 @@ pub mod stylesheet;
 
 use crate::display_list::{DisplayList, Margin};
 use crate::snippet::Snippet;
+pub use anstyle::*;
 use std::fmt::Display;
 use stylesheet::Stylesheet;
-use yansi_term::Color::Fixed;
-use yansi_term::Style;
 
 #[derive(Clone)]
 pub struct Renderer {
@@ -28,13 +27,13 @@ impl Renderer {
     pub fn styled() -> Self {
         Self {
             stylesheet: Stylesheet {
-                error: Fixed(9).bold(),
-                warning: Fixed(11).bold(),
-                info: Fixed(12).bold(),
-                note: Style::new().bold(),
-                help: Fixed(14).bold(),
-                line_no: Fixed(12).bold(),
-                emphasis: Style::new().bold(),
+                error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD),
+                warning: AnsiColor::BrightYellow.on_default().effects(Effects::BOLD),
+                info: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD),
+                note: Style::new().effects(Effects::BOLD),
+                help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
+                line_no: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD),
+                emphasis: Style::new().effects(Effects::BOLD),
                 none: Style::new(),
             },
             ..Self::plain()
diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs
index f52c139..899d9a7 100644
--- a/src/renderer/stylesheet.rs
+++ b/src/renderer/stylesheet.rs
@@ -1,4 +1,4 @@
-use yansi_term::Style;
+use anstyle::Style;
 
 #[derive(Clone, Copy, Debug)]
 pub struct Stylesheet {
diff --git a/tests/diff/mod.rs b/tests/diff/mod.rs
index 576c6c4..60ccae1 100644
--- a/tests/diff/mod.rs
+++ b/tests/diff/mod.rs
@@ -1,6 +1,7 @@
+use annotate_snippets::renderer::{AnsiColor, Color, Style};
 use difference::{Changeset, Difference};
-use yansi_term::Color::{Black, Green, Red};
 
+const GREEN: Style = AnsiColor::Green.on_default();
 pub fn get_diff(left: &str, right: &str) -> String {
     let mut output = String::new();
 
@@ -14,15 +15,28 @@ pub fn get_diff(left: &str, right: &str) -> String {
             Difference::Add(ref x) => {
                 match diffs[i - 1] {
                     Difference::Rem(ref y) => {
-                        output += &format!("{}", Green.paint("+"));
+                        output += &format!("{}+{}", GREEN.render(), GREEN.render_reset());
                         let Changeset { diffs, .. } = Changeset::new(y, x, " ");
                         for c in diffs {
                             match c {
                                 Difference::Same(ref z) => {
-                                    output += &format!("{} ", Green.paint(z.as_str()));
+                                    output += &format!(
+                                        "{}{}{} ",
+                                        GREEN.render(),
+                                        z.as_str(),
+                                        GREEN.render_reset()
+                                    );
                                 }
                                 Difference::Add(ref z) => {
-                                    output += &format!("{} ", Black.on(Green).paint(z.as_str()));
+                                    let black_on_green = Style::new()
+                                        .bg_color(Some(Color::Ansi(AnsiColor::Green)))
+                                        .fg_color(Some(Color::Ansi(AnsiColor::Black)));
+                                    output += &format!(
+                                        "{}{}{} ",
+                                        black_on_green.render(),
+                                        z.as_str(),
+                                        black_on_green.render_reset()
+                                    );
                                 }
                                 _ => (),
                             }
@@ -30,12 +44,18 @@ pub fn get_diff(left: &str, right: &str) -> String {
                         output += "\n";
                     }
                     _ => {
-                        output += &format!("+{}\n", Green.paint(x.as_str()));
+                        output += &format!(
+                            "+{}{}{}\n",
+                            GREEN.render(),
+                            x.as_str(),
+                            GREEN.render_reset()
+                        );
                     }
                 };
             }
             Difference::Rem(ref x) => {
-                output += &format!("-{}\n", Red.paint(x.as_str()));
+                let red = AnsiColor::Red.on_default();
+                output += &format!("-{}{}{}\n", red.render(), x.as_str(), red.render_reset());
             }
         }
     }

From 79f657ea252c3c0ce55fa69894ee520f8820b4bf Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 2 Dec 2023 13:57:04 -0700
Subject: [PATCH 066/302] fix!: Move `Margin` to `renderer`

---
 src/display_list/mod.rs  | 117 +-------------------------------------
 src/renderer/margin.rs   | 119 +++++++++++++++++++++++++++++++++++++++
 src/renderer/mod.rs      |   4 +-
 tests/deserialize/mod.rs |   2 +-
 4 files changed, 124 insertions(+), 118 deletions(-)
 create mode 100644 src/renderer/margin.rs

diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs
index b07d250..d9b7f34 100644
--- a/src/display_list/mod.rs
+++ b/src/display_list/mod.rs
@@ -32,11 +32,10 @@
 //!
 //! 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::{cmp, fmt};
 
-use crate::renderer::{stylesheet::Stylesheet, Style};
+use crate::renderer::{stylesheet::Stylesheet, Margin, Style};
 
 /// List of lines to be displayed.
 pub struct DisplayList<'a> {
@@ -531,120 +530,6 @@ impl<'a> DisplayList<'a> {
     }
 }
 
-#[derive(Clone, Copy, Debug)]
-pub struct Margin {
-    /// The available whitespace in the left that can be consumed when centering.
-    whitespace_left: usize,
-    /// The column of the beginning of left-most span.
-    span_left: usize,
-    /// The column of the end of right-most span.
-    span_right: usize,
-    /// The beginning of the line to be displayed.
-    computed_left: usize,
-    /// 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,
-    /// 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,
-}
-
-impl Margin {
-    pub fn new(
-        whitespace_left: usize,
-        span_left: usize,
-        span_right: usize,
-        label_right: usize,
-        column_width: usize,
-        max_line_len: usize,
-    ) -> Self {
-        // The 6 is padding to give a bit of room for `...` when displaying:
-        // ```
-        // error: message
-        //   --> file.rs:16:58
-        //    |
-        // 16 | ... fn foo(self) -> Self::Bar {
-        //    |                     ^^^^^^^^^
-        // ```
-
-        let mut m = Margin {
-            whitespace_left: whitespace_left.saturating_sub(6),
-            span_left: span_left.saturating_sub(6),
-            span_right: span_right + 6,
-            computed_left: 0,
-            computed_right: 0,
-            column_width,
-            label_right: label_right + 6,
-        };
-        m.compute(max_line_len);
-        m
-    }
-
-    pub(crate) fn was_cut_left(&self) -> bool {
-        self.computed_left > 0
-    }
-
-    pub(crate) fn was_cut_right(&self, line_len: usize) -> bool {
-        let right =
-            if self.computed_right == self.span_right || self.computed_right == self.label_right {
-                // Account for the "..." padding given above. Otherwise we end up with code lines that
-                // do fit but end in "..." as if they were trimmed.
-                self.computed_right - 6
-            } else {
-                self.computed_right
-            };
-        right < line_len && self.computed_left + self.column_width < line_len
-    }
-
-    fn compute(&mut self, max_line_len: usize) {
-        // When there's a lot of whitespace (>20), we want to trim it as it is useless.
-        self.computed_left = if self.whitespace_left > 20 {
-            self.whitespace_left - 16 // We want some padding.
-        } else {
-            0
-        };
-        // We want to show as much as possible, max_line_len is the right-most boundary for the
-        // relevant code.
-        self.computed_right = max(max_line_len, self.computed_left);
-
-        if self.computed_right - self.computed_left > self.column_width {
-            // Trimming only whitespace isn't enough, let's get craftier.
-            if self.label_right - self.whitespace_left <= self.column_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 {
-                // 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;
-                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 {
-                // 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;
-                self.computed_left = self.span_left.saturating_sub(padding_left);
-                self.computed_right = self.computed_left + self.column_width;
-            } else {
-                // Mostly give up but still don't show the full line.
-                self.computed_left = self.span_left;
-                self.computed_right = self.span_right;
-            }
-        }
-    }
-
-    pub(crate) fn left(&self, line_len: usize) -> usize {
-        min(self.computed_left, line_len)
-    }
-
-    pub(crate) fn right(&self, line_len: usize) -> usize {
-        if line_len.saturating_sub(self.computed_left) <= self.column_width {
-            line_len
-        } else {
-            min(line_len, self.computed_right)
-        }
-    }
-}
-
 /// Inline annotation which can be used in either Raw or Source line.
 #[derive(Debug, PartialEq)]
 pub struct Annotation<'a> {
diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs
new file mode 100644
index 0000000..361f5f3
--- /dev/null
+++ b/src/renderer/margin.rs
@@ -0,0 +1,119 @@
+use std::cmp::{max, min};
+
+const ELLIPSIS_PASSING: usize = 6;
+const LONG_WHITESPACE: usize = 20;
+const LONG_WHITESPACE_PADDING: usize = 4;
+
+#[derive(Clone, Copy, Debug)]
+pub struct Margin {
+    /// The available whitespace in the left that can be consumed when centering.
+    whitespace_left: usize,
+    /// The column of the beginning of left-most span.
+    span_left: usize,
+    /// The column of the end of right-most span.
+    span_right: usize,
+    /// The beginning of the line to be displayed.
+    computed_left: usize,
+    /// 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,
+    /// 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,
+}
+
+impl Margin {
+    pub fn new(
+        whitespace_left: usize,
+        span_left: usize,
+        span_right: usize,
+        label_right: usize,
+        column_width: usize,
+        max_line_len: usize,
+    ) -> Self {
+        // The 6 is padding to give a bit of room for `...` when displaying:
+        // ```
+        // error: message
+        //   --> file.rs:16:58
+        //    |
+        // 16 | ... fn foo(self) -> Self::Bar {
+        //    |                     ^^^^^^^^^
+        // ```
+
+        let mut m = Margin {
+            whitespace_left: whitespace_left.saturating_sub(ELLIPSIS_PASSING),
+            span_left: span_left.saturating_sub(ELLIPSIS_PASSING),
+            span_right: span_right + ELLIPSIS_PASSING,
+            computed_left: 0,
+            computed_right: 0,
+            column_width,
+            label_right: label_right + ELLIPSIS_PASSING,
+        };
+        m.compute(max_line_len);
+        m
+    }
+
+    pub(crate) fn was_cut_left(&self) -> bool {
+        self.computed_left > 0
+    }
+
+    pub(crate) fn was_cut_right(&self, line_len: usize) -> bool {
+        let right =
+            if self.computed_right == self.span_right || self.computed_right == self.label_right {
+                // Account for the "..." padding given above. Otherwise we end up with code lines that
+                // do fit but end in "..." as if they were trimmed.
+                self.computed_right - ELLIPSIS_PASSING
+            } else {
+                self.computed_right
+            };
+        right < line_len && self.computed_left + self.column_width < line_len
+    }
+
+    fn compute(&mut self, max_line_len: usize) {
+        // When there's a lot of whitespace (>20), we want to trim it as it is useless.
+        self.computed_left = if self.whitespace_left > LONG_WHITESPACE {
+            self.whitespace_left - (LONG_WHITESPACE - LONG_WHITESPACE_PADDING) // We want some padding.
+        } else {
+            0
+        };
+        // We want to show as much as possible, max_line_len is the right-most boundary for the
+        // relevant code.
+        self.computed_right = max(max_line_len, self.computed_left);
+
+        if self.computed_right - self.computed_left > self.column_width {
+            // Trimming only whitespace isn't enough, let's get craftier.
+            if self.label_right - self.whitespace_left <= self.column_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 {
+                // 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;
+                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 {
+                // 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;
+                self.computed_left = self.span_left.saturating_sub(padding_left);
+                self.computed_right = self.computed_left + self.column_width;
+            } else {
+                // Mostly give up but still don't show the full line.
+                self.computed_left = self.span_left;
+                self.computed_right = self.span_right;
+            }
+        }
+    }
+
+    pub(crate) fn left(&self, line_len: usize) -> usize {
+        min(self.computed_left, line_len)
+    }
+
+    pub(crate) fn right(&self, line_len: usize) -> usize {
+        if line_len.saturating_sub(self.computed_left) <= self.column_width {
+            line_len
+        } else {
+            min(line_len, self.computed_right)
+        }
+    }
+}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 5b6d55b..712d203 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1,8 +1,10 @@
+mod margin;
 pub mod stylesheet;
 
-use crate::display_list::{DisplayList, Margin};
+use crate::display_list::DisplayList;
 use crate::snippet::Snippet;
 pub use anstyle::*;
+pub use margin::Margin;
 use std::fmt::Display;
 use stylesheet::Stylesheet;
 
diff --git a/tests/deserialize/mod.rs b/tests/deserialize/mod.rs
index 58d14ef..af959cb 100644
--- a/tests/deserialize/mod.rs
+++ b/tests/deserialize/mod.rs
@@ -2,7 +2,7 @@ use serde::{Deserialize, Deserializer, Serialize};
 
 use annotate_snippets::renderer::Renderer;
 use annotate_snippets::{
-    display_list::Margin,
+    renderer::Margin,
     snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
 };
 

From da45f4858af3ec4c0d792ecc40225e27fdd2bac8 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 2 Dec 2023 14:04:43 -0700
Subject: [PATCH 067/302] fix!: Make `display_list` private

---
 src/display_list/mod.rs    | 899 ++++++++++++++++++++++++++++++++++++-
 src/lib.rs                 |  21 +-
 src/renderer/mod.rs        |   2 +-
 src/renderer/stylesheet.rs |   2 +-
 tests/dl_from_snippet.rs   | 391 ----------------
 tests/formatter.rs         | 518 ---------------------
 6 files changed, 903 insertions(+), 930 deletions(-)
 delete mode 100644 tests/dl_from_snippet.rs

diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs
index d9b7f34..da1a05b 100644
--- a/src/display_list/mod.rs
+++ b/src/display_list/mod.rs
@@ -38,7 +38,7 @@ use std::{cmp, fmt};
 use crate::renderer::{stylesheet::Stylesheet, Margin, Style};
 
 /// List of lines to be displayed.
-pub struct DisplayList<'a> {
+pub(crate) struct DisplayList<'a> {
     pub body: Vec<DisplayLine<'a>>,
     pub stylesheet: Stylesheet,
     pub anonymized_line_numbers: bool,
@@ -343,7 +343,6 @@ impl<'a> DisplayList<'a> {
                 let indent_char = match annotation_part {
                     DisplayAnnotationPart::Standalone => ' ',
                     DisplayAnnotationPart::LabelContinuation => ' ',
-                    DisplayAnnotationPart::Consequitive => ' ',
                     DisplayAnnotationPart::MultilineStart => '_',
                     DisplayAnnotationPart::MultilineEnd => '_',
                 };
@@ -358,7 +357,6 @@ impl<'a> DisplayList<'a> {
                 let color = self.get_annotation_style(annotation_type);
                 let indent_length = match annotation_part {
                     DisplayAnnotationPart::LabelContinuation => range.1,
-                    DisplayAnnotationPart::Consequitive => range.1,
                     _ => range.0,
                 };
 
@@ -626,8 +624,6 @@ pub enum DisplayAnnotationPart {
     Standalone,
     /// A continuation of a multi-line label of an annotation.
     LabelContinuation,
-    /// A consequitive annotation in case multiple annotations annotate a single line.
-    Consequitive,
     /// A line starting a multiline annotation.
     MultilineStart,
     /// A line ending a multiline annotation.
@@ -1228,3 +1224,896 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
         .iter()
         .all(|fragment| fragment.content.is_empty())
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_format_title() {
+        let input = snippet::Snippet {
+            title: Some(snippet::Annotation {
+                id: Some("E0001"),
+                label: Some("This is a title"),
+                annotation_type: snippet::AnnotationType::Error,
+            }),
+            footer: vec![],
+            slices: vec![],
+        };
+        let output = DisplayList {
+            body: vec![DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Error,
+                    id: Some("E0001"),
+                    label: vec![DisplayTextFragment {
+                        content: "This is a title",
+                        style: DisplayTextStyle::Emphasis,
+                    }],
+                },
+                source_aligned: false,
+                continuation: false,
+            })],
+            stylesheet: Stylesheet::default(),
+            anonymized_line_numbers: false,
+            margin: None,
+        };
+        assert_eq!(DisplayList::from(input), output);
+    }
+
+    #[test]
+    fn test_format_slice() {
+        let line_1 = "This is line 1";
+        let line_2 = "This is line 2";
+        let source = [line_1, line_2].join("\n");
+        let input = snippet::Snippet {
+            title: None,
+            footer: vec![],
+            slices: vec![snippet::Slice {
+                source: &source,
+                line_start: 5402,
+                origin: None,
+                annotations: vec![],
+                fold: false,
+            }],
+        };
+        let output = DisplayList {
+            body: vec![
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+                DisplayLine::Source {
+                    lineno: Some(5402),
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Content {
+                        text: line_1,
+                        range: (0, line_1.len()),
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: Some(5403),
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Content {
+                        range: (line_1.len() + 1, source.len()),
+                        text: line_2,
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+            ],
+            stylesheet: Stylesheet::default(),
+            anonymized_line_numbers: false,
+            margin: None,
+        };
+        assert_eq!(DisplayList::from(input), output);
+    }
+
+    #[test]
+    fn test_format_slices_continuation() {
+        let src_0 = "This is slice 1";
+        let src_0_len = src_0.len();
+        let src_1 = "This is slice 2";
+        let src_1_len = src_1.len();
+        let input = snippet::Snippet {
+            title: None,
+            footer: vec![],
+            slices: vec![
+                snippet::Slice {
+                    source: src_0,
+                    line_start: 5402,
+                    origin: Some("file1.rs"),
+                    annotations: vec![],
+                    fold: false,
+                },
+                snippet::Slice {
+                    source: src_1,
+                    line_start: 2,
+                    origin: Some("file2.rs"),
+                    annotations: vec![],
+                    fold: false,
+                },
+            ],
+        };
+        let output = DisplayList {
+            body: vec![
+                DisplayLine::Raw(DisplayRawLine::Origin {
+                    path: "file1.rs",
+                    pos: None,
+                    header_type: DisplayHeaderType::Initial,
+                }),
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+                DisplayLine::Source {
+                    lineno: Some(5402),
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Content {
+                        text: src_0,
+                        range: (0, src_0_len),
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+                DisplayLine::Raw(DisplayRawLine::Origin {
+                    path: "file2.rs",
+                    pos: None,
+                    header_type: DisplayHeaderType::Continuation,
+                }),
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+                DisplayLine::Source {
+                    lineno: Some(2),
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Content {
+                        text: src_1,
+                        range: (0, src_1_len),
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+            ],
+            stylesheet: Stylesheet::default(),
+            anonymized_line_numbers: false,
+            margin: None,
+        };
+        assert_eq!(DisplayList::from(input), output);
+    }
+
+    #[test]
+    fn test_format_slice_annotation_standalone() {
+        let line_1 = "This is line 1";
+        let line_2 = "This is line 2";
+        let source = [line_1, line_2].join("\n");
+        // In line 2
+        let range = (22, 24);
+        let input = snippet::Snippet {
+            title: None,
+            footer: vec![],
+            slices: vec![snippet::Slice {
+                source: &source,
+                line_start: 5402,
+                origin: None,
+                annotations: vec![snippet::SourceAnnotation {
+                    range,
+                    label: "Test annotation",
+                    annotation_type: snippet::AnnotationType::Info,
+                }],
+                fold: false,
+            }],
+        };
+        let output = DisplayList {
+            body: vec![
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+                DisplayLine::Source {
+                    lineno: Some(5402),
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Content {
+                        range: (0, line_1.len()),
+                        text: line_1,
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: Some(5403),
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Content {
+                        range: (line_1.len() + 1, source.len()),
+                        text: line_2,
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Annotation {
+                        annotation: Annotation {
+                            annotation_type: DisplayAnnotationType::Info,
+                            id: None,
+                            label: vec![DisplayTextFragment {
+                                content: "Test annotation",
+                                style: DisplayTextStyle::Regular,
+                            }],
+                        },
+                        range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)),
+                        annotation_type: DisplayAnnotationType::Info,
+                        annotation_part: DisplayAnnotationPart::Standalone,
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+            ],
+            stylesheet: Stylesheet::default(),
+            anonymized_line_numbers: false,
+            margin: None,
+        };
+        assert_eq!(DisplayList::from(input), output);
+    }
+
+    #[test]
+    fn test_format_label() {
+        let input = snippet::Snippet {
+            title: None,
+            footer: vec![snippet::Annotation {
+                id: None,
+                label: Some("This __is__ a title"),
+                annotation_type: snippet::AnnotationType::Error,
+            }],
+            slices: vec![],
+        };
+        let output = DisplayList {
+            body: vec![DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Error,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "This __is__ a title",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                source_aligned: true,
+                continuation: false,
+            })],
+            stylesheet: Stylesheet::default(),
+            anonymized_line_numbers: false,
+            margin: None,
+        };
+        assert_eq!(DisplayList::from(input), output);
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_i26() {
+        let source = "short";
+        let label = "label";
+        let input = snippet::Snippet {
+            title: None,
+            footer: vec![],
+            slices: vec![snippet::Slice {
+                annotations: vec![snippet::SourceAnnotation {
+                    range: (0, source.len() + 1),
+                    label,
+                    annotation_type: snippet::AnnotationType::Error,
+                }],
+                source,
+                line_start: 0,
+                origin: None,
+                fold: false,
+            }],
+        };
+
+        let _ = DisplayList::from(input);
+    }
+
+    #[test]
+    fn test_i_29() {
+        let snippets = snippet::Snippet {
+            title: Some(snippet::Annotation {
+                id: None,
+                label: Some("oops"),
+                annotation_type: snippet::AnnotationType::Error,
+            }),
+            footer: vec![],
+            slices: vec![snippet::Slice {
+                source: "First line\r\nSecond oops line",
+                line_start: 1,
+                origin: Some("<current file>"),
+                annotations: vec![snippet::SourceAnnotation {
+                    range: (19, 23),
+                    label: "oops",
+                    annotation_type: snippet::AnnotationType::Error,
+                }],
+                fold: true,
+            }],
+        };
+
+        let expected = DisplayList {
+            body: vec![
+                DisplayLine::Raw(DisplayRawLine::Annotation {
+                    annotation: Annotation {
+                        annotation_type: DisplayAnnotationType::Error,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "oops",
+                            style: DisplayTextStyle::Emphasis,
+                        }],
+                    },
+                    source_aligned: false,
+                    continuation: false,
+                }),
+                DisplayLine::Raw(DisplayRawLine::Origin {
+                    path: "<current file>",
+                    pos: Some((2, 8)),
+                    header_type: DisplayHeaderType::Initial,
+                }),
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+                DisplayLine::Source {
+                    lineno: Some(1),
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Content {
+                        text: "First line",
+                        range: (0, 10),
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: Some(2),
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Content {
+                        text: "Second oops line",
+                        range: (12, 28),
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Annotation {
+                        annotation: Annotation {
+                            annotation_type: DisplayAnnotationType::None,
+                            id: None,
+                            label: vec![DisplayTextFragment {
+                                content: "oops",
+                                style: DisplayTextStyle::Regular,
+                            }],
+                        },
+                        range: (7, 11),
+                        annotation_type: DisplayAnnotationType::Error,
+                        annotation_part: DisplayAnnotationPart::Standalone,
+                    },
+                },
+                DisplayLine::Source {
+                    lineno: None,
+                    inline_marks: vec![],
+                    line: DisplaySourceLine::Empty,
+                },
+            ],
+            stylesheet: Stylesheet::default(),
+            anonymized_line_numbers: false,
+            margin: None,
+        };
+
+        assert_eq!(DisplayList::from(snippets), expected);
+    }
+
+    #[test]
+    fn test_source_empty() {
+        let dl = DisplayList::from(vec![DisplayLine::Source {
+            lineno: None,
+            inline_marks: vec![],
+            line: DisplaySourceLine::Empty,
+        }]);
+
+        assert_eq!(dl.to_string(), " |");
+    }
+
+    #[test]
+    fn test_source_content() {
+        let dl = DisplayList::from(vec![
+            DisplayLine::Source {
+                lineno: Some(56),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: "This is an example",
+                    range: (0, 19),
+                },
+            },
+            DisplayLine::Source {
+                lineno: Some(57),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: "of content lines",
+                    range: (0, 19),
+                },
+            },
+        ]);
+
+        assert_eq!(
+            dl.to_string(),
+            "56 | This is an example\n57 | of content lines"
+        );
+    }
+
+    #[test]
+    fn test_source_annotation_standalone_singleline() {
+        let dl = DisplayList::from(vec![DisplayLine::Source {
+            lineno: None,
+            inline_marks: vec![],
+            line: DisplaySourceLine::Annotation {
+                range: (0, 5),
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::None,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "Example string",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                annotation_type: DisplayAnnotationType::Error,
+                annotation_part: DisplayAnnotationPart::Standalone,
+            },
+        }]);
+
+        assert_eq!(dl.to_string(), " | ^^^^^ Example string");
+    }
+
+    #[test]
+    fn test_source_annotation_standalone_multiline() {
+        let dl = DisplayList::from(vec![
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
+                    range: (0, 5),
+                    annotation: Annotation {
+                        annotation_type: DisplayAnnotationType::Help,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "Example string",
+                            style: DisplayTextStyle::Regular,
+                        }],
+                    },
+                    annotation_type: DisplayAnnotationType::Warning,
+                    annotation_part: DisplayAnnotationPart::Standalone,
+                },
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
+                    range: (0, 5),
+                    annotation: Annotation {
+                        annotation_type: DisplayAnnotationType::Help,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "Second line",
+                            style: DisplayTextStyle::Regular,
+                        }],
+                    },
+                    annotation_type: DisplayAnnotationType::Warning,
+                    annotation_part: DisplayAnnotationPart::LabelContinuation,
+                },
+            },
+        ]);
+
+        assert_eq!(
+            dl.to_string(),
+            " | ----- help: Example string\n |             Second line"
+        );
+    }
+
+    #[test]
+    fn test_source_annotation_standalone_multi_annotation() {
+        let dl = DisplayList::from(vec![
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
+                    range: (0, 5),
+                    annotation: Annotation {
+                        annotation_type: DisplayAnnotationType::Info,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "Example string",
+                            style: DisplayTextStyle::Regular,
+                        }],
+                    },
+                    annotation_type: DisplayAnnotationType::Note,
+                    annotation_part: DisplayAnnotationPart::Standalone,
+                },
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
+                    range: (0, 5),
+                    annotation: Annotation {
+                        annotation_type: DisplayAnnotationType::Info,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "Second line",
+                            style: DisplayTextStyle::Regular,
+                        }],
+                    },
+                    annotation_type: DisplayAnnotationType::Note,
+                    annotation_part: DisplayAnnotationPart::LabelContinuation,
+                },
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
+                    range: (0, 5),
+                    annotation: Annotation {
+                        annotation_type: DisplayAnnotationType::Warning,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "Second line of the warning",
+                            style: DisplayTextStyle::Regular,
+                        }],
+                    },
+                    annotation_type: DisplayAnnotationType::Note,
+                    annotation_part: DisplayAnnotationPart::LabelContinuation,
+                },
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
+                    range: (0, 5),
+                    annotation: Annotation {
+                        annotation_type: DisplayAnnotationType::Info,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "This is an info",
+                            style: DisplayTextStyle::Regular,
+                        }],
+                    },
+                    annotation_type: DisplayAnnotationType::Info,
+                    annotation_part: DisplayAnnotationPart::Standalone,
+                },
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
+                    range: (0, 5),
+                    annotation: Annotation {
+                        annotation_type: DisplayAnnotationType::Help,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "This is help",
+                            style: DisplayTextStyle::Regular,
+                        }],
+                    },
+                    annotation_type: DisplayAnnotationType::Help,
+                    annotation_part: DisplayAnnotationPart::Standalone,
+                },
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
+                    range: (0, 0),
+                    annotation: Annotation {
+                        annotation_type: DisplayAnnotationType::None,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "This is an annotation of type none",
+                            style: DisplayTextStyle::Regular,
+                        }],
+                    },
+                    annotation_type: DisplayAnnotationType::None,
+                    annotation_part: DisplayAnnotationPart::Standalone,
+                },
+            },
+        ]);
+
+        assert_eq!(dl.to_string(), " | ----- info: Example string\n |             Second line\n |                Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n |  This is an annotation of type none");
+    }
+
+    #[test]
+    fn test_fold_line() {
+        let dl = DisplayList::from(vec![
+            DisplayLine::Source {
+                lineno: Some(5),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: "This is line 5",
+                    range: (0, 19),
+                },
+            },
+            DisplayLine::Fold {
+                inline_marks: vec![],
+            },
+            DisplayLine::Source {
+                lineno: Some(10021),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: "... and now we're at line 10021",
+                    range: (0, 19),
+                },
+            },
+        ]);
+
+        assert_eq!(
+            dl.to_string(),
+            "    5 | This is line 5\n...\n10021 | ... and now we're at line 10021"
+        );
+    }
+
+    #[test]
+    fn test_raw_origin_initial_nopos() {
+        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
+            path: "src/test.rs",
+            pos: None,
+            header_type: DisplayHeaderType::Initial,
+        })]);
+
+        assert_eq!(dl.to_string(), "--> src/test.rs");
+    }
+
+    #[test]
+    fn test_raw_origin_initial_pos() {
+        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
+            path: "src/test.rs",
+            pos: Some((23, 15)),
+            header_type: DisplayHeaderType::Initial,
+        })]);
+
+        assert_eq!(dl.to_string(), "--> src/test.rs:23:15");
+    }
+
+    #[test]
+    fn test_raw_origin_continuation() {
+        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
+            path: "src/test.rs",
+            pos: Some((23, 15)),
+            header_type: DisplayHeaderType::Continuation,
+        })]);
+
+        assert_eq!(dl.to_string(), "::: src/test.rs:23:15");
+    }
+
+    #[test]
+    fn test_raw_annotation_unaligned() {
+        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
+            annotation: Annotation {
+                annotation_type: DisplayAnnotationType::Error,
+                id: Some("E0001"),
+                label: vec![DisplayTextFragment {
+                    content: "This is an error",
+                    style: DisplayTextStyle::Regular,
+                }],
+            },
+            source_aligned: false,
+            continuation: false,
+        })]);
+
+        assert_eq!(dl.to_string(), "error[E0001]: This is an error");
+    }
+
+    #[test]
+    fn test_raw_annotation_unaligned_multiline() {
+        let dl = DisplayList::from(vec![
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Warning,
+                    id: Some("E0001"),
+                    label: vec![DisplayTextFragment {
+                        content: "This is an error",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                source_aligned: false,
+                continuation: false,
+            }),
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Warning,
+                    id: Some("E0001"),
+                    label: vec![DisplayTextFragment {
+                        content: "Second line of the error",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                source_aligned: false,
+                continuation: true,
+            }),
+        ]);
+
+        assert_eq!(
+            dl.to_string(),
+            "warning[E0001]: This is an error\n                Second line of the error"
+        );
+    }
+
+    #[test]
+    fn test_raw_annotation_aligned() {
+        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
+            annotation: Annotation {
+                annotation_type: DisplayAnnotationType::Error,
+                id: Some("E0001"),
+                label: vec![DisplayTextFragment {
+                    content: "This is an error",
+                    style: DisplayTextStyle::Regular,
+                }],
+            },
+            source_aligned: true,
+            continuation: false,
+        })]);
+
+        assert_eq!(dl.to_string(), " = error[E0001]: This is an error");
+    }
+
+    #[test]
+    fn test_raw_annotation_aligned_multiline() {
+        let dl = DisplayList::from(vec![
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Warning,
+                    id: Some("E0001"),
+                    label: vec![DisplayTextFragment {
+                        content: "This is an error",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                source_aligned: true,
+                continuation: false,
+            }),
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Warning,
+                    id: Some("E0001"),
+                    label: vec![DisplayTextFragment {
+                        content: "Second line of the error",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                source_aligned: true,
+                continuation: true,
+            }),
+        ]);
+
+        assert_eq!(
+            dl.to_string(),
+            " = warning[E0001]: This is an error\n                   Second line of the error"
+        );
+    }
+
+    #[test]
+    fn test_different_annotation_types() {
+        let dl = DisplayList::from(vec![
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Note,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "This is a note",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                source_aligned: false,
+                continuation: false,
+            }),
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::None,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "This is just a string",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                source_aligned: false,
+                continuation: false,
+            }),
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::None,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "Second line of none type annotation",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                source_aligned: false,
+                continuation: true,
+            }),
+        ]);
+
+        assert_eq!(
+            dl.to_string(),
+            "note: This is a note\nThis is just a string\n  Second line of none type annotation",
+        );
+    }
+
+    #[test]
+    fn test_inline_marks_empty_line() {
+        let dl = DisplayList::from(vec![DisplayLine::Source {
+            lineno: None,
+            inline_marks: vec![DisplayMark {
+                mark_type: DisplayMarkType::AnnotationThrough,
+                annotation_type: DisplayAnnotationType::Error,
+            }],
+            line: DisplaySourceLine::Empty,
+        }]);
+
+        assert_eq!(dl.to_string(), " | |",);
+    }
+
+    #[test]
+    fn test_anon_lines() {
+        let mut dl = DisplayList::from(vec![
+            DisplayLine::Source {
+                lineno: Some(56),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: "This is an example",
+                    range: (0, 19),
+                },
+            },
+            DisplayLine::Source {
+                lineno: Some(57),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: "of content lines",
+                    range: (0, 19),
+                },
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: "abc",
+                    range: (0, 19),
+                },
+            },
+        ]);
+
+        dl.anonymized_line_numbers = true;
+        assert_eq!(
+            dl.to_string(),
+            "LL | This is an example\nLL | of content lines\n   |\n   | abc"
+        );
+    }
+
+    #[test]
+    fn test_raw_origin_initial_pos_anon_lines() {
+        let mut dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
+            path: "src/test.rs",
+            pos: Some((23, 15)),
+            header_type: DisplayHeaderType::Initial,
+        })]);
+
+        // Using anonymized_line_numbers should not affect the initial position
+        dl.anonymized_line_numbers = true;
+        assert_eq!(dl.to_string(), "--> src/test.rs:23:15");
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index bf0dc05..342db23 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -26,28 +26,21 @@
 //! The crate uses a three stage process with two conversions between states:
 //!
 //! ```text
-//! Snippet --> DisplayList --> String
+//! Snippet --> Renderer --> impl Display
 //! ```
 //!
 //! The input type - [Snippet](self::snippet) is a structure designed
 //! to align with likely output from any parser whose code snippet is to be
 //! annotated.
 //!
-//! The middle structure - [DisplayList](self::display_list) is a
-//! structure designed to store the snippet data converted into a vector
-//! of lines containing semantic information about each line.
-//! This structure is the easiest to manipulate and organize.
+//! The middle structure - [Renderer](self::renderer) is a structure designed
+//! to convert a snippet into an internal structure that is designed to store
+//! the snippet data in a way that is easy to format.
+//! [Renderer](self::renderer) also handles the user-configurable formatting
+//! options, such as color, or margins.
 //!
 //! Finally, `impl Display` into a final `String` output.
-//!
-//! A user of the crate may choose to provide their own equivalent of the input
-//! structure with an `Into<DisplayList>` trait.
-//!
-//! A user of the crate may also choose to provide their own formatter logic,
-//! to convert a `DisplayList` into a `String`, or just a `Stylesheet` to
-//! use the crate's formatting logic, but with a custom stylesheet.
-// TODO: check documentation
 
-pub mod display_list;
+mod display_list;
 pub mod renderer;
 pub mod snippet;
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 712d203..2f03341 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1,5 +1,5 @@
 mod margin;
-pub mod stylesheet;
+pub(crate) mod stylesheet;
 
 use crate::display_list::DisplayList;
 use crate::snippet::Snippet;
diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs
index 899d9a7..b3dbbc3 100644
--- a/src/renderer/stylesheet.rs
+++ b/src/renderer/stylesheet.rs
@@ -1,7 +1,7 @@
 use anstyle::Style;
 
 #[derive(Clone, Copy, Debug)]
-pub struct Stylesheet {
+pub(crate) struct Stylesheet {
     pub(crate) error: Style,
     pub(crate) warning: Style,
     pub(crate) info: Style,
diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs
deleted file mode 100644
index 5fb0762..0000000
--- a/tests/dl_from_snippet.rs
+++ /dev/null
@@ -1,391 +0,0 @@
-use annotate_snippets::display_list::DisplayList;
-use annotate_snippets::renderer::stylesheet::Stylesheet;
-use annotate_snippets::{display_list as dl, snippet};
-
-#[test]
-fn test_format_title() {
-    let input = snippet::Snippet {
-        title: Some(snippet::Annotation {
-            id: Some("E0001"),
-            label: Some("This is a title"),
-            annotation_type: snippet::AnnotationType::Error,
-        }),
-        footer: vec![],
-        slices: vec![],
-    };
-    let output = dl::DisplayList {
-        body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation {
-            annotation: dl::Annotation {
-                annotation_type: dl::DisplayAnnotationType::Error,
-                id: Some("E0001"),
-                label: vec![dl::DisplayTextFragment {
-                    content: "This is a title",
-                    style: dl::DisplayTextStyle::Emphasis,
-                }],
-            },
-            source_aligned: false,
-            continuation: false,
-        })],
-        stylesheet: Stylesheet::default(),
-        anonymized_line_numbers: false,
-        margin: None,
-    };
-    assert_eq!(dl::DisplayList::from(input), output);
-}
-
-#[test]
-fn test_format_slice() {
-    let line_1 = "This is line 1";
-    let line_2 = "This is line 2";
-    let source = [line_1, line_2].join("\n");
-    let input = snippet::Snippet {
-        title: None,
-        footer: vec![],
-        slices: vec![snippet::Slice {
-            source: &source,
-            line_start: 5402,
-            origin: None,
-            annotations: vec![],
-            fold: false,
-        }],
-    };
-    let output = dl::DisplayList {
-        body: vec![
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-            dl::DisplayLine::Source {
-                lineno: Some(5402),
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Content {
-                    text: line_1,
-                    range: (0, line_1.len()),
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: Some(5403),
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Content {
-                    range: (line_1.len() + 1, source.len()),
-                    text: line_2,
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-        ],
-        stylesheet: Stylesheet::default(),
-        anonymized_line_numbers: false,
-        margin: None,
-    };
-    assert_eq!(dl::DisplayList::from(input), output);
-}
-
-#[test]
-fn test_format_slices_continuation() {
-    let src_0 = "This is slice 1";
-    let src_0_len = src_0.len();
-    let src_1 = "This is slice 2";
-    let src_1_len = src_1.len();
-    let input = snippet::Snippet {
-        title: None,
-        footer: vec![],
-        slices: vec![
-            snippet::Slice {
-                source: src_0,
-                line_start: 5402,
-                origin: Some("file1.rs"),
-                annotations: vec![],
-                fold: false,
-            },
-            snippet::Slice {
-                source: src_1,
-                line_start: 2,
-                origin: Some("file2.rs"),
-                annotations: vec![],
-                fold: false,
-            },
-        ],
-    };
-    let output = dl::DisplayList {
-        body: vec![
-            dl::DisplayLine::Raw(dl::DisplayRawLine::Origin {
-                path: "file1.rs",
-                pos: None,
-                header_type: dl::DisplayHeaderType::Initial,
-            }),
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-            dl::DisplayLine::Source {
-                lineno: Some(5402),
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Content {
-                    text: src_0,
-                    range: (0, src_0_len),
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-            dl::DisplayLine::Raw(dl::DisplayRawLine::Origin {
-                path: "file2.rs",
-                pos: None,
-                header_type: dl::DisplayHeaderType::Continuation,
-            }),
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-            dl::DisplayLine::Source {
-                lineno: Some(2),
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Content {
-                    text: src_1,
-                    range: (0, src_1_len),
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-        ],
-        stylesheet: Stylesheet::default(),
-        anonymized_line_numbers: false,
-        margin: None,
-    };
-    assert_eq!(dl::DisplayList::from(input), output);
-}
-
-#[test]
-fn test_format_slice_annotation_standalone() {
-    let line_1 = "This is line 1";
-    let line_2 = "This is line 2";
-    let source = [line_1, line_2].join("\n");
-    // In line 2
-    let range = (22, 24);
-    let input = snippet::Snippet {
-        title: None,
-        footer: vec![],
-        slices: vec![snippet::Slice {
-            source: &source,
-            line_start: 5402,
-            origin: None,
-            annotations: vec![snippet::SourceAnnotation {
-                range,
-                label: "Test annotation",
-                annotation_type: snippet::AnnotationType::Info,
-            }],
-            fold: false,
-        }],
-    };
-    let output = dl::DisplayList {
-        body: vec![
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-            dl::DisplayLine::Source {
-                lineno: Some(5402),
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Content {
-                    range: (0, line_1.len()),
-                    text: line_1,
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: Some(5403),
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Content {
-                    range: (line_1.len() + 1, source.len()),
-                    text: line_2,
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Annotation {
-                    annotation: dl::Annotation {
-                        annotation_type: dl::DisplayAnnotationType::Info,
-                        id: None,
-                        label: vec![dl::DisplayTextFragment {
-                            content: "Test annotation",
-                            style: dl::DisplayTextStyle::Regular,
-                        }],
-                    },
-                    range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)),
-                    annotation_type: dl::DisplayAnnotationType::Info,
-                    annotation_part: dl::DisplayAnnotationPart::Standalone,
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-        ],
-        stylesheet: Stylesheet::default(),
-        anonymized_line_numbers: false,
-        margin: None,
-    };
-    assert_eq!(dl::DisplayList::from(input), output);
-}
-
-#[test]
-fn test_format_label() {
-    let input = snippet::Snippet {
-        title: None,
-        footer: vec![snippet::Annotation {
-            id: None,
-            label: Some("This __is__ a title"),
-            annotation_type: snippet::AnnotationType::Error,
-        }],
-        slices: vec![],
-    };
-    let output = dl::DisplayList {
-        body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation {
-            annotation: dl::Annotation {
-                annotation_type: dl::DisplayAnnotationType::Error,
-                id: None,
-                label: vec![dl::DisplayTextFragment {
-                    content: "This __is__ a title",
-                    style: dl::DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: true,
-            continuation: false,
-        })],
-        stylesheet: Stylesheet::default(),
-        anonymized_line_numbers: false,
-        margin: None,
-    };
-    assert_eq!(dl::DisplayList::from(input), output);
-}
-
-#[test]
-#[should_panic]
-fn test_i26() {
-    let source = "short";
-    let label = "label";
-    let input = snippet::Snippet {
-        title: None,
-        footer: vec![],
-        slices: vec![snippet::Slice {
-            annotations: vec![snippet::SourceAnnotation {
-                range: (0, source.len() + 1),
-                label,
-                annotation_type: snippet::AnnotationType::Error,
-            }],
-            source,
-            line_start: 0,
-            origin: None,
-            fold: false,
-        }],
-    };
-
-    let _ = dl::DisplayList::from(input);
-}
-
-#[test]
-fn test_i_29() {
-    let snippets = snippet::Snippet {
-        title: Some(snippet::Annotation {
-            id: None,
-            label: Some("oops"),
-            annotation_type: snippet::AnnotationType::Error,
-        }),
-        footer: vec![],
-        slices: vec![snippet::Slice {
-            source: "First line\r\nSecond oops line",
-            line_start: 1,
-            origin: Some("<current file>"),
-            annotations: vec![snippet::SourceAnnotation {
-                range: (19, 23),
-                label: "oops",
-                annotation_type: snippet::AnnotationType::Error,
-            }],
-            fold: true,
-        }],
-    };
-
-    let expected = DisplayList {
-        body: vec![
-            dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation {
-                annotation: dl::Annotation {
-                    annotation_type: dl::DisplayAnnotationType::Error,
-                    id: None,
-                    label: vec![dl::DisplayTextFragment {
-                        content: "oops",
-                        style: dl::DisplayTextStyle::Emphasis,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            }),
-            dl::DisplayLine::Raw(dl::DisplayRawLine::Origin {
-                path: "<current file>",
-                pos: Some((2, 8)),
-                header_type: dl::DisplayHeaderType::Initial,
-            }),
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-            dl::DisplayLine::Source {
-                lineno: Some(1),
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Content {
-                    text: "First line",
-                    range: (0, 10),
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: Some(2),
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Content {
-                    text: "Second oops line",
-                    range: (12, 28),
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Annotation {
-                    annotation: dl::Annotation {
-                        annotation_type: dl::DisplayAnnotationType::None,
-                        id: None,
-                        label: vec![dl::DisplayTextFragment {
-                            content: "oops",
-                            style: dl::DisplayTextStyle::Regular,
-                        }],
-                    },
-                    range: (7, 11),
-                    annotation_type: dl::DisplayAnnotationType::Error,
-                    annotation_part: dl::DisplayAnnotationPart::Standalone,
-                },
-            },
-            dl::DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: dl::DisplaySourceLine::Empty,
-            },
-        ],
-        stylesheet: Stylesheet::default(),
-        anonymized_line_numbers: false,
-        margin: None,
-    };
-
-    assert_eq!(DisplayList::from(snippets), expected);
-}
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 2117fec..3f85b69 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,524 +1,6 @@
-use annotate_snippets::display_list::*;
 use annotate_snippets::renderer::Renderer;
 use annotate_snippets::snippet::{self, Snippet};
 
-#[test]
-fn test_source_empty() {
-    let dl = DisplayList::from(vec![DisplayLine::Source {
-        lineno: None,
-        inline_marks: vec![],
-        line: DisplaySourceLine::Empty,
-    }]);
-
-    assert_eq!(dl.to_string(), " |");
-}
-
-#[test]
-fn test_source_content() {
-    let dl = DisplayList::from(vec![
-        DisplayLine::Source {
-            lineno: Some(56),
-            inline_marks: vec![],
-            line: DisplaySourceLine::Content {
-                text: "This is an example",
-                range: (0, 19),
-            },
-        },
-        DisplayLine::Source {
-            lineno: Some(57),
-            inline_marks: vec![],
-            line: DisplaySourceLine::Content {
-                text: "of content lines",
-                range: (0, 19),
-            },
-        },
-    ]);
-
-    assert_eq!(
-        dl.to_string(),
-        "56 | This is an example\n57 | of content lines"
-    );
-}
-
-#[test]
-fn test_source_annotation_standalone_singleline() {
-    let dl = DisplayList::from(vec![DisplayLine::Source {
-        lineno: None,
-        inline_marks: vec![],
-        line: DisplaySourceLine::Annotation {
-            range: (0, 5),
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::None,
-                id: None,
-                label: vec![DisplayTextFragment {
-                    content: "Example string",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            annotation_type: DisplayAnnotationType::Error,
-            annotation_part: DisplayAnnotationPart::Standalone,
-        },
-    }]);
-
-    assert_eq!(dl.to_string(), " | ^^^^^ Example string");
-}
-
-#[test]
-fn test_source_annotation_standalone_multiline() {
-    let dl = DisplayList::from(vec![
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 5),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Help,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "Example string",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::Warning,
-                annotation_part: DisplayAnnotationPart::Standalone,
-            },
-        },
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 5),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Help,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "Second line",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::Warning,
-                annotation_part: DisplayAnnotationPart::LabelContinuation,
-            },
-        },
-    ]);
-
-    assert_eq!(
-        dl.to_string(),
-        " | ----- help: Example string\n |             Second line"
-    );
-}
-
-#[test]
-fn test_source_annotation_standalone_multi_annotation() {
-    let dl = DisplayList::from(vec![
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 5),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Info,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "Example string",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::Note,
-                annotation_part: DisplayAnnotationPart::Standalone,
-            },
-        },
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 5),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Info,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "Second line",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::Note,
-                annotation_part: DisplayAnnotationPart::LabelContinuation,
-            },
-        },
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 5),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Warning,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "This is a note",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::Note,
-                annotation_part: DisplayAnnotationPart::Consequitive,
-            },
-        },
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 5),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Warning,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "Second line of the warning",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::Note,
-                annotation_part: DisplayAnnotationPart::LabelContinuation,
-            },
-        },
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 5),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Info,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "This is an info",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::Info,
-                annotation_part: DisplayAnnotationPart::Standalone,
-            },
-        },
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 5),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Help,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "This is help",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::Help,
-                annotation_part: DisplayAnnotationPart::Standalone,
-            },
-        },
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 0),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::None,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "This is an annotation of type none",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::None,
-                annotation_part: DisplayAnnotationPart::Standalone,
-            },
-        },
-    ]);
-
-    assert_eq!(dl.to_string(), " | ----- info: Example string\n |             Second line\n |       warning: This is a note\n |                Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n |  This is an annotation of type none");
-}
-
-#[test]
-fn test_fold_line() {
-    let dl = DisplayList::from(vec![
-        DisplayLine::Source {
-            lineno: Some(5),
-            inline_marks: vec![],
-            line: DisplaySourceLine::Content {
-                text: "This is line 5",
-                range: (0, 19),
-            },
-        },
-        DisplayLine::Fold {
-            inline_marks: vec![],
-        },
-        DisplayLine::Source {
-            lineno: Some(10021),
-            inline_marks: vec![],
-            line: DisplaySourceLine::Content {
-                text: "... and now we're at line 10021",
-                range: (0, 19),
-            },
-        },
-    ]);
-
-    assert_eq!(
-        dl.to_string(),
-        "    5 | This is line 5\n...\n10021 | ... and now we're at line 10021"
-    );
-}
-
-#[test]
-fn test_raw_origin_initial_nopos() {
-    let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
-        path: "src/test.rs",
-        pos: None,
-        header_type: DisplayHeaderType::Initial,
-    })]);
-
-    assert_eq!(dl.to_string(), "--> src/test.rs");
-}
-
-#[test]
-fn test_raw_origin_initial_pos() {
-    let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
-        path: "src/test.rs",
-        pos: Some((23, 15)),
-        header_type: DisplayHeaderType::Initial,
-    })]);
-
-    assert_eq!(dl.to_string(), "--> src/test.rs:23:15");
-}
-
-#[test]
-fn test_raw_origin_continuation() {
-    let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
-        path: "src/test.rs",
-        pos: Some((23, 15)),
-        header_type: DisplayHeaderType::Continuation,
-    })]);
-
-    assert_eq!(dl.to_string(), "::: src/test.rs:23:15");
-}
-
-#[test]
-fn test_raw_annotation_unaligned() {
-    let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
-        annotation: Annotation {
-            annotation_type: DisplayAnnotationType::Error,
-            id: Some("E0001"),
-            label: vec![DisplayTextFragment {
-                content: "This is an error",
-                style: DisplayTextStyle::Regular,
-            }],
-        },
-        source_aligned: false,
-        continuation: false,
-    })]);
-
-    assert_eq!(dl.to_string(), "error[E0001]: This is an error");
-}
-
-#[test]
-fn test_raw_annotation_unaligned_multiline() {
-    let dl = DisplayList::from(vec![
-        DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::Warning,
-                id: Some("E0001"),
-                label: vec![DisplayTextFragment {
-                    content: "This is an error",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: false,
-            continuation: false,
-        }),
-        DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::Warning,
-                id: Some("E0001"),
-                label: vec![DisplayTextFragment {
-                    content: "Second line of the error",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: false,
-            continuation: true,
-        }),
-    ]);
-
-    assert_eq!(
-        dl.to_string(),
-        "warning[E0001]: This is an error\n                Second line of the error"
-    );
-}
-
-#[test]
-fn test_raw_annotation_aligned() {
-    let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
-        annotation: Annotation {
-            annotation_type: DisplayAnnotationType::Error,
-            id: Some("E0001"),
-            label: vec![DisplayTextFragment {
-                content: "This is an error",
-                style: DisplayTextStyle::Regular,
-            }],
-        },
-        source_aligned: true,
-        continuation: false,
-    })]);
-
-    assert_eq!(dl.to_string(), " = error[E0001]: This is an error");
-}
-
-#[test]
-fn test_raw_annotation_aligned_multiline() {
-    let dl = DisplayList::from(vec![
-        DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::Warning,
-                id: Some("E0001"),
-                label: vec![DisplayTextFragment {
-                    content: "This is an error",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: true,
-            continuation: false,
-        }),
-        DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::Warning,
-                id: Some("E0001"),
-                label: vec![DisplayTextFragment {
-                    content: "Second line of the error",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: true,
-            continuation: true,
-        }),
-    ]);
-
-    assert_eq!(
-        dl.to_string(),
-        " = warning[E0001]: This is an error\n                   Second line of the error"
-    );
-}
-
-#[test]
-fn test_different_annotation_types() {
-    let dl = DisplayList::from(vec![
-        DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::Note,
-                id: None,
-                label: vec![DisplayTextFragment {
-                    content: "This is a note",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: false,
-            continuation: false,
-        }),
-        DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::None,
-                id: None,
-                label: vec![DisplayTextFragment {
-                    content: "This is just a string",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: false,
-            continuation: false,
-        }),
-        DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::None,
-                id: None,
-                label: vec![DisplayTextFragment {
-                    content: "Second line of none type annotation",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: false,
-            continuation: true,
-        }),
-    ]);
-
-    assert_eq!(
-        dl.to_string(),
-        "note: This is a note\nThis is just a string\n  Second line of none type annotation",
-    );
-}
-
-#[test]
-fn test_inline_marks_empty_line() {
-    let dl = DisplayList::from(vec![DisplayLine::Source {
-        lineno: None,
-        inline_marks: vec![DisplayMark {
-            mark_type: DisplayMarkType::AnnotationThrough,
-            annotation_type: DisplayAnnotationType::Error,
-        }],
-        line: DisplaySourceLine::Empty,
-    }]);
-
-    assert_eq!(dl.to_string(), " | |",);
-}
-
-#[test]
-fn test_anon_lines() {
-    let mut dl = DisplayList::from(vec![
-        DisplayLine::Source {
-            lineno: Some(56),
-            inline_marks: vec![],
-            line: DisplaySourceLine::Content {
-                text: "This is an example",
-                range: (0, 19),
-            },
-        },
-        DisplayLine::Source {
-            lineno: Some(57),
-            inline_marks: vec![],
-            line: DisplaySourceLine::Content {
-                text: "of content lines",
-                range: (0, 19),
-            },
-        },
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Empty,
-        },
-        DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Content {
-                text: "abc",
-                range: (0, 19),
-            },
-        },
-    ]);
-
-    dl.anonymized_line_numbers = true;
-    assert_eq!(
-        dl.to_string(),
-        "LL | This is an example\nLL | of content lines\n   |\n   | abc"
-    );
-}
-
-#[test]
-fn test_raw_origin_initial_pos_anon_lines() {
-    let mut dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
-        path: "src/test.rs",
-        pos: Some((23, 15)),
-        header_type: DisplayHeaderType::Initial,
-    })]);
-
-    // Using anonymized_line_numbers should not affect the initial position
-    dl.anonymized_line_numbers = true;
-    assert_eq!(dl.to_string(), "--> src/test.rs:23:15");
-}
-
 #[test]
 fn test_i_29() {
     let snippets = Snippet {

From d45fbd42ff4eef34f8088a8a4a8dbac1b9af277a Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 2 Dec 2023 14:38:59 -0700
Subject: [PATCH 068/302] refactor(display_list): Remove `From` impls

---
 src/display_list/mod.rs    | 493 +++++++++++++++++--------------------
 src/renderer/stylesheet.rs |   6 +
 2 files changed, 235 insertions(+), 264 deletions(-)

diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs
index da1a05b..b88b213 100644
--- a/src/display_list/mod.rs
+++ b/src/display_list/mod.rs
@@ -45,17 +45,6 @@ pub(crate) struct DisplayList<'a> {
     pub margin: Option<Margin>,
 }
 
-impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> {
-    fn from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a> {
-        Self {
-            body,
-            anonymized_line_numbers: false,
-            stylesheet: Stylesheet::default(),
-            margin: None,
-        }
-    }
-}
-
 impl<'a> PartialEq for DisplayList<'a> {
     fn eq(&self, other: &Self) -> bool {
         self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
@@ -105,12 +94,6 @@ impl<'a> Display for DisplayList<'a> {
     }
 }
 
-impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
-    fn from(snippet: snippet::Snippet<'a>) -> DisplayList<'a> {
-        Self::new(snippet, Stylesheet::default(), false, None)
-    }
-}
-
 impl<'a> DisplayList<'a> {
     const ANONYMIZED_LINE_NUM: &'static str = "LL";
     const ERROR_TXT: &'static str = "error";
@@ -1229,6 +1212,17 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
 mod tests {
     use super::*;
 
+    const STYLESHEET: Stylesheet = Stylesheet::plain();
+
+    fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> DisplayList<'_> {
+        DisplayList {
+            body: lines,
+            stylesheet: STYLESHEET,
+            anonymized_line_numbers: false,
+            margin: None,
+        }
+    }
+
     #[test]
     fn test_format_title() {
         let input = snippet::Snippet {
@@ -1240,24 +1234,19 @@ mod tests {
             footer: vec![],
             slices: vec![],
         };
-        let output = DisplayList {
-            body: vec![DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Error,
-                    id: Some("E0001"),
-                    label: vec![DisplayTextFragment {
-                        content: "This is a title",
-                        style: DisplayTextStyle::Emphasis,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            })],
-            stylesheet: Stylesheet::default(),
-            anonymized_line_numbers: false,
-            margin: None,
-        };
-        assert_eq!(DisplayList::from(input), output);
+        let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
+            annotation: Annotation {
+                annotation_type: DisplayAnnotationType::Error,
+                id: Some("E0001"),
+                label: vec![DisplayTextFragment {
+                    content: "This is a title",
+                    style: DisplayTextStyle::Emphasis,
+                }],
+            },
+            source_aligned: false,
+            continuation: false,
+        })]);
+        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1276,40 +1265,35 @@ mod tests {
                 fold: false,
             }],
         };
-        let output = DisplayList {
-            body: vec![
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
-                },
-                DisplayLine::Source {
-                    lineno: Some(5402),
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Content {
-                        text: line_1,
-                        range: (0, line_1.len()),
-                    },
-                },
-                DisplayLine::Source {
-                    lineno: Some(5403),
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Content {
-                        range: (line_1.len() + 1, source.len()),
-                        text: line_2,
-                    },
+        let output = from_display_lines(vec![
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+            DisplayLine::Source {
+                lineno: Some(5402),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: line_1,
+                    range: (0, line_1.len()),
                 },
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
+            },
+            DisplayLine::Source {
+                lineno: Some(5403),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    range: (line_1.len() + 1, source.len()),
+                    text: line_2,
                 },
-            ],
-            stylesheet: Stylesheet::default(),
-            anonymized_line_numbers: false,
-            margin: None,
-        };
-        assert_eq!(DisplayList::from(input), output);
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+        ]);
+        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1338,60 +1322,55 @@ mod tests {
                 },
             ],
         };
-        let output = DisplayList {
-            body: vec![
-                DisplayLine::Raw(DisplayRawLine::Origin {
-                    path: "file1.rs",
-                    pos: None,
-                    header_type: DisplayHeaderType::Initial,
-                }),
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
-                },
-                DisplayLine::Source {
-                    lineno: Some(5402),
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Content {
-                        text: src_0,
-                        range: (0, src_0_len),
-                    },
-                },
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
-                },
-                DisplayLine::Raw(DisplayRawLine::Origin {
-                    path: "file2.rs",
-                    pos: None,
-                    header_type: DisplayHeaderType::Continuation,
-                }),
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
-                },
-                DisplayLine::Source {
-                    lineno: Some(2),
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Content {
-                        text: src_1,
-                        range: (0, src_1_len),
-                    },
+        let output = from_display_lines(vec![
+            DisplayLine::Raw(DisplayRawLine::Origin {
+                path: "file1.rs",
+                pos: None,
+                header_type: DisplayHeaderType::Initial,
+            }),
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+            DisplayLine::Source {
+                lineno: Some(5402),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: src_0,
+                    range: (0, src_0_len),
                 },
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+            DisplayLine::Raw(DisplayRawLine::Origin {
+                path: "file2.rs",
+                pos: None,
+                header_type: DisplayHeaderType::Continuation,
+            }),
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+            DisplayLine::Source {
+                lineno: Some(2),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: src_1,
+                    range: (0, src_1_len),
                 },
-            ],
-            stylesheet: Stylesheet::default(),
-            anonymized_line_numbers: false,
-            margin: None,
-        };
-        assert_eq!(DisplayList::from(input), output);
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+        ]);
+        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1416,57 +1395,52 @@ mod tests {
                 fold: false,
             }],
         };
-        let output = DisplayList {
-            body: vec![
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
-                },
-                DisplayLine::Source {
-                    lineno: Some(5402),
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Content {
-                        range: (0, line_1.len()),
-                        text: line_1,
-                    },
+        let output = from_display_lines(vec![
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+            DisplayLine::Source {
+                lineno: Some(5402),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    range: (0, line_1.len()),
+                    text: line_1,
                 },
-                DisplayLine::Source {
-                    lineno: Some(5403),
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Content {
-                        range: (line_1.len() + 1, source.len()),
-                        text: line_2,
-                    },
+            },
+            DisplayLine::Source {
+                lineno: Some(5403),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    range: (line_1.len() + 1, source.len()),
+                    text: line_2,
                 },
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Annotation {
-                        annotation: Annotation {
-                            annotation_type: DisplayAnnotationType::Info,
-                            id: None,
-                            label: vec![DisplayTextFragment {
-                                content: "Test annotation",
-                                style: DisplayTextStyle::Regular,
-                            }],
-                        },
-                        range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)),
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
+                    annotation: Annotation {
                         annotation_type: DisplayAnnotationType::Info,
-                        annotation_part: DisplayAnnotationPart::Standalone,
+                        id: None,
+                        label: vec![DisplayTextFragment {
+                            content: "Test annotation",
+                            style: DisplayTextStyle::Regular,
+                        }],
                     },
+                    range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)),
+                    annotation_type: DisplayAnnotationType::Info,
+                    annotation_part: DisplayAnnotationPart::Standalone,
                 },
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
-                },
-            ],
-            stylesheet: Stylesheet::default(),
-            anonymized_line_numbers: false,
-            margin: None,
-        };
-        assert_eq!(DisplayList::from(input), output);
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+        ]);
+        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1480,24 +1454,19 @@ mod tests {
             }],
             slices: vec![],
         };
-        let output = DisplayList {
-            body: vec![DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Error,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "This __is__ a title",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                source_aligned: true,
-                continuation: false,
-            })],
-            stylesheet: Stylesheet::default(),
-            anonymized_line_numbers: false,
-            margin: None,
-        };
-        assert_eq!(DisplayList::from(input), output);
+        let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
+            annotation: Annotation {
+                annotation_type: DisplayAnnotationType::Error,
+                id: None,
+                label: vec![DisplayTextFragment {
+                    content: "This __is__ a title",
+                    style: DisplayTextStyle::Regular,
+                }],
+            },
+            source_aligned: true,
+            continuation: false,
+        })]);
+        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1520,8 +1489,7 @@ mod tests {
                 fold: false,
             }],
         };
-
-        let _ = DisplayList::from(input);
+        let _ = DisplayList::new(input, STYLESHEET, false, None);
     }
 
     #[test]
@@ -1546,80 +1514,77 @@ mod tests {
             }],
         };
 
-        let expected = DisplayList {
-            body: vec![
-                DisplayLine::Raw(DisplayRawLine::Annotation {
+        let expected = from_display_lines(vec![
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Error,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "oops",
+                        style: DisplayTextStyle::Emphasis,
+                    }],
+                },
+                source_aligned: false,
+                continuation: false,
+            }),
+            DisplayLine::Raw(DisplayRawLine::Origin {
+                path: "<current file>",
+                pos: Some((2, 8)),
+                header_type: DisplayHeaderType::Initial,
+            }),
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+            DisplayLine::Source {
+                lineno: Some(1),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: "First line",
+                    range: (0, 10),
+                },
+            },
+            DisplayLine::Source {
+                lineno: Some(2),
+                inline_marks: vec![],
+                line: DisplaySourceLine::Content {
+                    text: "Second oops line",
+                    range: (12, 28),
+                },
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Annotation {
                     annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::Error,
+                        annotation_type: DisplayAnnotationType::None,
                         id: None,
                         label: vec![DisplayTextFragment {
                             content: "oops",
-                            style: DisplayTextStyle::Emphasis,
+                            style: DisplayTextStyle::Regular,
                         }],
                     },
-                    source_aligned: false,
-                    continuation: false,
-                }),
-                DisplayLine::Raw(DisplayRawLine::Origin {
-                    path: "<current file>",
-                    pos: Some((2, 8)),
-                    header_type: DisplayHeaderType::Initial,
-                }),
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
-                },
-                DisplayLine::Source {
-                    lineno: Some(1),
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Content {
-                        text: "First line",
-                        range: (0, 10),
-                    },
-                },
-                DisplayLine::Source {
-                    lineno: Some(2),
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Content {
-                        text: "Second oops line",
-                        range: (12, 28),
-                    },
-                },
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Annotation {
-                        annotation: Annotation {
-                            annotation_type: DisplayAnnotationType::None,
-                            id: None,
-                            label: vec![DisplayTextFragment {
-                                content: "oops",
-                                style: DisplayTextStyle::Regular,
-                            }],
-                        },
-                        range: (7, 11),
-                        annotation_type: DisplayAnnotationType::Error,
-                        annotation_part: DisplayAnnotationPart::Standalone,
-                    },
-                },
-                DisplayLine::Source {
-                    lineno: None,
-                    inline_marks: vec![],
-                    line: DisplaySourceLine::Empty,
+                    range: (7, 11),
+                    annotation_type: DisplayAnnotationType::Error,
+                    annotation_part: DisplayAnnotationPart::Standalone,
                 },
-            ],
-            stylesheet: Stylesheet::default(),
-            anonymized_line_numbers: false,
-            margin: None,
-        };
-
-        assert_eq!(DisplayList::from(snippets), expected);
+            },
+            DisplayLine::Source {
+                lineno: None,
+                inline_marks: vec![],
+                line: DisplaySourceLine::Empty,
+            },
+        ]);
+        assert_eq!(
+            DisplayList::new(snippets, STYLESHEET, false, None),
+            expected
+        );
     }
 
     #[test]
     fn test_source_empty() {
-        let dl = DisplayList::from(vec![DisplayLine::Source {
+        let dl = from_display_lines(vec![DisplayLine::Source {
             lineno: None,
             inline_marks: vec![],
             line: DisplaySourceLine::Empty,
@@ -1630,7 +1595,7 @@ mod tests {
 
     #[test]
     fn test_source_content() {
-        let dl = DisplayList::from(vec![
+        let dl = from_display_lines(vec![
             DisplayLine::Source {
                 lineno: Some(56),
                 inline_marks: vec![],
@@ -1657,7 +1622,7 @@ mod tests {
 
     #[test]
     fn test_source_annotation_standalone_singleline() {
-        let dl = DisplayList::from(vec![DisplayLine::Source {
+        let dl = from_display_lines(vec![DisplayLine::Source {
             lineno: None,
             inline_marks: vec![],
             line: DisplaySourceLine::Annotation {
@@ -1680,7 +1645,7 @@ mod tests {
 
     #[test]
     fn test_source_annotation_standalone_multiline() {
-        let dl = DisplayList::from(vec![
+        let dl = from_display_lines(vec![
             DisplayLine::Source {
                 lineno: None,
                 inline_marks: vec![],
@@ -1725,7 +1690,7 @@ mod tests {
 
     #[test]
     fn test_source_annotation_standalone_multi_annotation() {
-        let dl = DisplayList::from(vec![
+        let dl = from_display_lines(vec![
             DisplayLine::Source {
                 lineno: None,
                 inline_marks: vec![],
@@ -1835,7 +1800,7 @@ mod tests {
 
     #[test]
     fn test_fold_line() {
-        let dl = DisplayList::from(vec![
+        let dl = from_display_lines(vec![
             DisplayLine::Source {
                 lineno: Some(5),
                 inline_marks: vec![],
@@ -1865,7 +1830,7 @@ mod tests {
 
     #[test]
     fn test_raw_origin_initial_nopos() {
-        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
+        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
             path: "src/test.rs",
             pos: None,
             header_type: DisplayHeaderType::Initial,
@@ -1876,7 +1841,7 @@ mod tests {
 
     #[test]
     fn test_raw_origin_initial_pos() {
-        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
+        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
             path: "src/test.rs",
             pos: Some((23, 15)),
             header_type: DisplayHeaderType::Initial,
@@ -1887,7 +1852,7 @@ mod tests {
 
     #[test]
     fn test_raw_origin_continuation() {
-        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
+        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
             path: "src/test.rs",
             pos: Some((23, 15)),
             header_type: DisplayHeaderType::Continuation,
@@ -1898,7 +1863,7 @@ mod tests {
 
     #[test]
     fn test_raw_annotation_unaligned() {
-        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
+        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
                 annotation_type: DisplayAnnotationType::Error,
                 id: Some("E0001"),
@@ -1916,7 +1881,7 @@ mod tests {
 
     #[test]
     fn test_raw_annotation_unaligned_multiline() {
-        let dl = DisplayList::from(vec![
+        let dl = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
                     annotation_type: DisplayAnnotationType::Warning,
@@ -1951,7 +1916,7 @@ mod tests {
 
     #[test]
     fn test_raw_annotation_aligned() {
-        let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
+        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
                 annotation_type: DisplayAnnotationType::Error,
                 id: Some("E0001"),
@@ -1969,7 +1934,7 @@ mod tests {
 
     #[test]
     fn test_raw_annotation_aligned_multiline() {
-        let dl = DisplayList::from(vec![
+        let dl = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
                     annotation_type: DisplayAnnotationType::Warning,
@@ -2004,7 +1969,7 @@ mod tests {
 
     #[test]
     fn test_different_annotation_types() {
-        let dl = DisplayList::from(vec![
+        let dl = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
                     annotation_type: DisplayAnnotationType::Note,
@@ -2051,7 +2016,7 @@ mod tests {
 
     #[test]
     fn test_inline_marks_empty_line() {
-        let dl = DisplayList::from(vec![DisplayLine::Source {
+        let dl = from_display_lines(vec![DisplayLine::Source {
             lineno: None,
             inline_marks: vec![DisplayMark {
                 mark_type: DisplayMarkType::AnnotationThrough,
@@ -2065,7 +2030,7 @@ mod tests {
 
     #[test]
     fn test_anon_lines() {
-        let mut dl = DisplayList::from(vec![
+        let mut dl = from_display_lines(vec![
             DisplayLine::Source {
                 lineno: Some(56),
                 inline_marks: vec![],
@@ -2106,7 +2071,7 @@ mod tests {
 
     #[test]
     fn test_raw_origin_initial_pos_anon_lines() {
-        let mut dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin {
+        let mut dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
             path: "src/test.rs",
             pos: Some((23, 15)),
             header_type: DisplayHeaderType::Initial,
diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs
index b3dbbc3..ee1ab93 100644
--- a/src/renderer/stylesheet.rs
+++ b/src/renderer/stylesheet.rs
@@ -14,6 +14,12 @@ pub(crate) struct Stylesheet {
 
 impl Default for Stylesheet {
     fn default() -> Self {
+        Self::plain()
+    }
+}
+
+impl Stylesheet {
+    pub(crate) const fn plain() -> Self {
         Self {
             error: Style::new(),
             warning: Style::new(),

From a9bf3857a4d66e3186870d2375125a86275d2721 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 2 Dec 2023 14:40:42 -0700
Subject: [PATCH 069/302] refactor(display_list): Take a reference to
 `StyleSheet`

---
 src/display_list/mod.rs | 20 ++++++++++----------
 src/renderer/mod.rs     |  2 +-
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs
index b88b213..02e14a5 100644
--- a/src/display_list/mod.rs
+++ b/src/display_list/mod.rs
@@ -40,7 +40,7 @@ use crate::renderer::{stylesheet::Stylesheet, Margin, Style};
 /// List of lines to be displayed.
 pub(crate) struct DisplayList<'a> {
     pub body: Vec<DisplayLine<'a>>,
-    pub stylesheet: Stylesheet,
+    pub stylesheet: &'a Stylesheet,
     pub anonymized_line_numbers: bool,
     pub margin: Option<Margin>,
 }
@@ -108,7 +108,7 @@ impl<'a> DisplayList<'a> {
             footer,
             slices,
         }: snippet::Snippet<'a>,
-        stylesheet: Stylesheet,
+        stylesheet: &'a Stylesheet,
         anonymized_line_numbers: bool,
         margin: Option<Margin>,
     ) -> DisplayList<'a> {
@@ -1217,7 +1217,7 @@ mod tests {
     fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> DisplayList<'_> {
         DisplayList {
             body: lines,
-            stylesheet: STYLESHEET,
+            stylesheet: &STYLESHEET,
             anonymized_line_numbers: false,
             margin: None,
         }
@@ -1246,7 +1246,7 @@ mod tests {
             source_aligned: false,
             continuation: false,
         })]);
-        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
+        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1293,7 +1293,7 @@ mod tests {
                 line: DisplaySourceLine::Empty,
             },
         ]);
-        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
+        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1370,7 +1370,7 @@ mod tests {
                 line: DisplaySourceLine::Empty,
             },
         ]);
-        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
+        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1440,7 +1440,7 @@ mod tests {
                 line: DisplaySourceLine::Empty,
             },
         ]);
-        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
+        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1466,7 +1466,7 @@ mod tests {
             source_aligned: true,
             continuation: false,
         })]);
-        assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output);
+        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
     }
 
     #[test]
@@ -1489,7 +1489,7 @@ mod tests {
                 fold: false,
             }],
         };
-        let _ = DisplayList::new(input, STYLESHEET, false, None);
+        let _ = DisplayList::new(input, &STYLESHEET, false, None);
     }
 
     #[test]
@@ -1577,7 +1577,7 @@ mod tests {
             },
         ]);
         assert_eq!(
-            DisplayList::new(snippets, STYLESHEET, false, None),
+            DisplayList::new(snippets, &STYLESHEET, false, None),
             expected
         );
     }
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 2f03341..69f1cf4 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -95,7 +95,7 @@ impl Renderer {
     pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a {
         DisplayList::new(
             snippet,
-            self.stylesheet,
+            &self.stylesheet,
             self.anonymized_line_numbers,
             self.margin,
         )

From 748b79263dc59ea3c8604fd9e3f0657d10c76c90 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 2 Dec 2023 14:46:46 -0700
Subject: [PATCH 070/302] refactor(renderer): Make functions `const`

---
 src/renderer/mod.rs | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 69f1cf4..bf6bf69 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -17,16 +17,16 @@ pub struct Renderer {
 
 impl Renderer {
     /// No terminal styling
-    pub fn plain() -> Self {
+    pub const fn plain() -> Self {
         Self {
             anonymized_line_numbers: false,
             margin: None,
-            stylesheet: Stylesheet::default(),
+            stylesheet: Stylesheet::plain(),
         }
     }
 
     /// Default terminal styling
-    pub fn styled() -> Self {
+    pub const fn styled() -> Self {
         Self {
             stylesheet: Stylesheet {
                 error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD),
@@ -42,52 +42,52 @@ impl Renderer {
         }
     }
 
-    pub fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
+    pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
         self.anonymized_line_numbers = anonymized_line_numbers;
         self
     }
 
-    pub fn margin(mut self, margin: Option<Margin>) -> Self {
+    pub const fn margin(mut self, margin: Option<Margin>) -> Self {
         self.margin = margin;
         self
     }
 
-    pub fn error(mut self, style: Style) -> Self {
+    pub const fn error(mut self, style: Style) -> Self {
         self.stylesheet.error = style;
         self
     }
 
-    pub fn warning(mut self, style: Style) -> Self {
+    pub const fn warning(mut self, style: Style) -> Self {
         self.stylesheet.warning = style;
         self
     }
 
-    pub fn info(mut self, style: Style) -> Self {
+    pub const fn info(mut self, style: Style) -> Self {
         self.stylesheet.info = style;
         self
     }
 
-    pub fn note(mut self, style: Style) -> Self {
+    pub const fn note(mut self, style: Style) -> Self {
         self.stylesheet.note = style;
         self
     }
 
-    pub fn help(mut self, style: Style) -> Self {
+    pub const fn help(mut self, style: Style) -> Self {
         self.stylesheet.help = style;
         self
     }
 
-    pub fn line_no(mut self, style: Style) -> Self {
+    pub const fn line_no(mut self, style: Style) -> Self {
         self.stylesheet.line_no = style;
         self
     }
 
-    pub fn emphasis(mut self, style: Style) -> Self {
+    pub const fn emphasis(mut self, style: Style) -> Self {
         self.stylesheet.emphasis = style;
         self
     }
 
-    pub fn none(mut self, style: Style) -> Self {
+    pub const fn none(mut self, style: Style) -> Self {
         self.stylesheet.none = style;
         self
     }

From b0848f70cdf54b4601bd01d4ae99ce4c63a80be9 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Mon, 4 Dec 2023 16:21:30 -0700
Subject: [PATCH 071/302] chore(renderer): Add doc comments

---
 src/renderer/mod.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 75 insertions(+)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index bf6bf69..ce080d3 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1,3 +1,37 @@
+//! The renderer for [`Snippet`]s
+//!
+//! # Example
+//! ```
+//! use annotate_snippets::renderer::Renderer;
+//! use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet};
+//! let snippet = Snippet {
+//!     title: Some(Annotation {
+//!         label: Some("mismatched types"),
+//!         id: None,
+//!         annotation_type: AnnotationType::Error,
+//!     }),
+//!     footer: vec![],
+//!     slices: vec![
+//!         Slice {
+//!             source: "Foo",
+//!             line_start: 51,
+//!             origin: Some("src/format.rs"),
+//!             fold: false,
+//!             annotations: vec![],
+//!         },
+//!         Slice {
+//!             source: "Faa",
+//!             line_start: 129,
+//!             origin: Some("src/display.rs"),
+//!             fold: false,
+//!             annotations: vec![],
+//!         },
+//!     ],
+//!  };
+//!
+//!  let renderer = Renderer::styled();
+//!  println!("{}", renderer.render(snippet));
+
 mod margin;
 pub(crate) mod stylesheet;
 
@@ -8,6 +42,7 @@ pub use margin::Margin;
 use std::fmt::Display;
 use stylesheet::Stylesheet;
 
+/// A renderer for [`Snippet`]s
 #[derive(Clone)]
 pub struct Renderer {
     anonymized_line_numbers: bool,
@@ -42,56 +77,96 @@ impl Renderer {
         }
     }
 
+    /// Anonymize line numbers
+    ///
+    /// This enables (or disables) line number anonymization. When enabled, line numbers are replaced
+    /// with `LL`.
+    ///
+    /// # Example
+    ///
+    /// ```text
+    ///   --> $DIR/whitespace-trimming.rs:4:193
+    ///    |
+    /// LL | ...                   let _: () = 42;
+    ///    |                                   ^^ expected (), found integer
+    ///    |
+    /// ```
     pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
         self.anonymized_line_numbers = anonymized_line_numbers;
         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;
         self
     }
 
+    /// Set the output style for `error`
     pub const fn error(mut self, style: Style) -> Self {
         self.stylesheet.error = style;
         self
     }
 
+    /// Set the output style for `warning`
     pub const fn warning(mut self, style: Style) -> Self {
         self.stylesheet.warning = style;
         self
     }
 
+    /// Set the output style for `info`
     pub const fn info(mut self, style: Style) -> Self {
         self.stylesheet.info = style;
         self
     }
 
+    /// Set the output style for `note`
     pub const fn note(mut self, style: Style) -> Self {
         self.stylesheet.note = style;
         self
     }
 
+    /// Set the output style for `help`
     pub const fn help(mut self, style: Style) -> Self {
         self.stylesheet.help = style;
         self
     }
 
+    /// Set the output style for line numbers
     pub const fn line_no(mut self, style: Style) -> Self {
         self.stylesheet.line_no = style;
         self
     }
 
+    /// Set the output style for emphasis
     pub const fn emphasis(mut self, style: Style) -> Self {
         self.stylesheet.emphasis = style;
         self
     }
 
+    /// Set the output style for none
     pub const fn none(mut self, style: Style) -> Self {
         self.stylesheet.none = style;
         self
     }
 
+    /// Render a snippet into a `Display`able object
     pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a {
         DisplayList::new(
             snippet,

From a1007ddf2fc6f76e960a4fc01207228e64e9fae7 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 5 Dec 2023 11:39:46 -0700
Subject: [PATCH 072/302] fix!: Move around items in the public api

BREAKING CHANGE: This moves `snippet::*` and `Renderer` to root
---
 benches/simple.rs         |  3 +--
 examples/expected_type.rs |  3 +--
 examples/footer.rs        |  3 +--
 examples/format.rs        |  3 +--
 examples/multislice.rs    |  3 +--
 src/lib.rs                | 12 ++++++++----
 src/renderer/mod.rs       |  3 +--
 src/snippet.rs            |  2 +-
 tests/deserialize/mod.rs  |  4 +---
 tests/fixtures_test.rs    |  4 ++--
 tests/formatter.rs        | 41 +++++++++++++++++++--------------------
 11 files changed, 38 insertions(+), 43 deletions(-)

diff --git a/benches/simple.rs b/benches/simple.rs
index 8ccd18f..3a40bbf 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -4,8 +4,7 @@ extern crate criterion;
 
 use criterion::{black_box, Criterion};
 
-use annotate_snippets::renderer::Renderer;
-use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
+use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
 
 fn create_snippet(renderer: Renderer) {
     let snippet = Snippet {
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 959419c..bbd1fe6 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,5 +1,4 @@
-use annotate_snippets::renderer::Renderer;
-use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
+use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
 
 fn main() {
     let snippet = Snippet {
diff --git a/examples/footer.rs b/examples/footer.rs
index 0191c1d..ca02119 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,5 +1,4 @@
-use annotate_snippets::renderer::Renderer;
-use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
+use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
 
 fn main() {
     let snippet = Snippet {
diff --git a/examples/format.rs b/examples/format.rs
index 7302eef..41f852e 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,5 +1,4 @@
-use annotate_snippets::renderer::Renderer;
-use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
+use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
 
 fn main() {
     let snippet = Snippet {
diff --git a/examples/multislice.rs b/examples/multislice.rs
index dc51d4f..63ebb65 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,5 +1,4 @@
-use annotate_snippets::renderer::Renderer;
-use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet};
+use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet};
 
 fn main() {
     let snippet = Snippet {
diff --git a/src/lib.rs b/src/lib.rs
index 342db23..5da3575 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,18 +29,22 @@
 //! Snippet --> Renderer --> impl Display
 //! ```
 //!
-//! The input type - [Snippet](self::snippet) is a structure designed
+//! The input type - [Snippet] is a structure designed
 //! to align with likely output from any parser whose code snippet is to be
 //! annotated.
 //!
-//! The middle structure - [Renderer](self::renderer) is a structure designed
+//! The middle structure - [Renderer] is a structure designed
 //! to convert a snippet into an internal structure that is designed to store
 //! the snippet data in a way that is easy to format.
-//! [Renderer](self::renderer) also handles the user-configurable formatting
+//! [Renderer] also handles the user-configurable formatting
 //! options, such as color, or margins.
 //!
 //! Finally, `impl Display` into a final `String` output.
 
 mod display_list;
 pub mod renderer;
-pub mod snippet;
+mod snippet;
+
+#[doc(inline)]
+pub use renderer::Renderer;
+pub use snippet::*;
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index ce080d3..5f655c8 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -2,8 +2,7 @@
 //!
 //! # Example
 //! ```
-//! use annotate_snippets::renderer::Renderer;
-//! use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet};
+//! use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet};
 //! let snippet = Snippet {
 //!     title: Some(Annotation {
 //!         label: Some("mismatched types"),
diff --git a/src/snippet.rs b/src/snippet.rs
index c1914c9..02e70cc 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -3,7 +3,7 @@
 //! Example:
 //!
 //! ```
-//! use annotate_snippets::snippet::*;
+//! use annotate_snippets::*;
 //!
 //! Snippet {
 //!     title: Some(Annotation {
diff --git a/tests/deserialize/mod.rs b/tests/deserialize/mod.rs
index af959cb..1763005 100644
--- a/tests/deserialize/mod.rs
+++ b/tests/deserialize/mod.rs
@@ -1,9 +1,7 @@
 use serde::{Deserialize, Deserializer, Serialize};
 
-use annotate_snippets::renderer::Renderer;
 use annotate_snippets::{
-    renderer::Margin,
-    snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
+    renderer::Margin, Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation,
 };
 
 #[derive(Deserialize)]
diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs
index 854719f..063829a 100644
--- a/tests/fixtures_test.rs
+++ b/tests/fixtures_test.rs
@@ -2,8 +2,8 @@ mod deserialize;
 mod diff;
 
 use crate::deserialize::Fixture;
-use annotate_snippets::renderer::Renderer;
-use annotate_snippets::snippet::Snippet;
+use annotate_snippets::Renderer;
+use annotate_snippets::Snippet;
 use glob::glob;
 use std::{error::Error, fs::File, io, io::prelude::*};
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 3f85b69..97c7be3 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,23 +1,22 @@
-use annotate_snippets::renderer::Renderer;
-use annotate_snippets::snippet::{self, Snippet};
+use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
 
 #[test]
 fn test_i_29() {
     let snippets = Snippet {
-        title: Some(snippet::Annotation {
+        title: Some(Annotation {
             id: None,
             label: Some("oops"),
-            annotation_type: snippet::AnnotationType::Error,
+            annotation_type: AnnotationType::Error,
         }),
         footer: vec![],
-        slices: vec![snippet::Slice {
+        slices: vec![Slice {
             source: "First line\r\nSecond oops line",
             line_start: 1,
             origin: Some("<current file>"),
-            annotations: vec![snippet::SourceAnnotation {
+            annotations: vec![SourceAnnotation {
                 range: (19, 23),
                 label: "oops",
-                annotation_type: snippet::AnnotationType::Error,
+                annotation_type: AnnotationType::Error,
             }],
             fold: true,
         }],
@@ -37,14 +36,14 @@ fn test_i_29() {
 #[test]
 fn test_point_to_double_width_characters() {
     let snippets = Snippet {
-        slices: vec![snippet::Slice {
+        slices: vec![Slice {
             source: "こんにちは、世界",
             line_start: 1,
             origin: Some("<current file>"),
-            annotations: vec![snippet::SourceAnnotation {
+            annotations: vec![SourceAnnotation {
                 range: (6, 8),
                 label: "world",
-                annotation_type: snippet::AnnotationType::Error,
+                annotation_type: AnnotationType::Error,
             }],
             fold: false,
         }],
@@ -65,14 +64,14 @@ fn test_point_to_double_width_characters() {
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
     let snippets = Snippet {
-        slices: vec![snippet::Slice {
+        slices: vec![Slice {
             source: "おはよう\nございます",
             line_start: 1,
             origin: Some("<current file>"),
-            annotations: vec![snippet::SourceAnnotation {
+            annotations: vec![SourceAnnotation {
                 range: (2, 8),
                 label: "Good morning",
-                annotation_type: snippet::AnnotationType::Error,
+                annotation_type: AnnotationType::Error,
             }],
             fold: false,
         }],
@@ -95,20 +94,20 @@ fn test_point_to_double_width_characters_across_lines() {
 #[test]
 fn test_point_to_double_width_characters_multiple() {
     let snippets = Snippet {
-        slices: vec![snippet::Slice {
+        slices: vec![Slice {
             source: "お寿司\n食べたい🍣",
             line_start: 1,
             origin: Some("<current file>"),
             annotations: vec![
-                snippet::SourceAnnotation {
+                SourceAnnotation {
                     range: (0, 3),
                     label: "Sushi1",
-                    annotation_type: snippet::AnnotationType::Error,
+                    annotation_type: AnnotationType::Error,
                 },
-                snippet::SourceAnnotation {
+                SourceAnnotation {
                     range: (6, 8),
                     label: "Sushi2",
-                    annotation_type: snippet::AnnotationType::Note,
+                    annotation_type: AnnotationType::Note,
                 },
             ],
             fold: false,
@@ -132,14 +131,14 @@ fn test_point_to_double_width_characters_multiple() {
 #[test]
 fn test_point_to_double_width_characters_mixed() {
     let snippets = Snippet {
-        slices: vec![snippet::Slice {
+        slices: vec![Slice {
             source: "こんにちは、新しいWorld!",
             line_start: 1,
             origin: Some("<current file>"),
-            annotations: vec![snippet::SourceAnnotation {
+            annotations: vec![SourceAnnotation {
                 range: (6, 14),
                 label: "New world",
-                annotation_type: snippet::AnnotationType::Error,
+                annotation_type: AnnotationType::Error,
             }],
             fold: false,
         }],

From 71b1eb527b41da38fb6d4a70a1fc53afea4c3785 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 5 Dec 2023 11:58:27 -0700
Subject: [PATCH 073/302] refactor: Move `display_list` under `renderer`

---
 src/lib.rs                                            | 1 -
 src/{display_list/mod.rs => renderer/display_list.rs} | 0
 src/renderer/mod.rs                                   | 3 ++-
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename src/{display_list/mod.rs => renderer/display_list.rs} (100%)

diff --git a/src/lib.rs b/src/lib.rs
index 5da3575..80eac53 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -41,7 +41,6 @@
 //!
 //! Finally, `impl Display` into a final `String` output.
 
-mod display_list;
 pub mod renderer;
 mod snippet;
 
diff --git a/src/display_list/mod.rs b/src/renderer/display_list.rs
similarity index 100%
rename from src/display_list/mod.rs
rename to src/renderer/display_list.rs
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 5f655c8..0e19c08 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -31,12 +31,13 @@
 //!  let renderer = Renderer::styled();
 //!  println!("{}", renderer.render(snippet));
 
+mod display_list;
 mod margin;
 pub(crate) mod stylesheet;
 
-use crate::display_list::DisplayList;
 use crate::snippet::Snippet;
 pub use anstyle::*;
+use display_list::DisplayList;
 pub use margin::Margin;
 use std::fmt::Display;
 use stylesheet::Stylesheet;

From 5419c9c4bad72b3e9a5123d83ad9a02d415fff8d Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 12 Dec 2023 13:53:10 -0700
Subject: [PATCH 074/302] chore: Update CHANGELOG.md for `0.10.0`

---
 CHANGELOG.md | 32 ++++++++++++++++++++++++++++++--
 1 file changed, 30 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1ab962..0e55c8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,34 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased] - ReleaseDate
- 
+
+## [0.10.0] - December 12, 2023
+
+### Added
+
+- `Renderer` is now used for displaying a `Snippet` [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/9076cbf66336e5137b47dc7a52df2999b6c82598)
+  - `Renderer` also controls the color scheme and formatting of the snippet
+
+### Changed
+
+- Moved everything in the `snippet` to be in the crate root [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/a1007ddf2fc6f76e960a4fc01207228e64e9fae7)
+
+### Breaking Changes
+
+- `Renderer` now controls the color scheme and formatting of `Snippet`s [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/d0c65b26493d60f86a82c5919ef736b35808c23a)
+- Removed the `Style` and `Stylesheet` traits, as color is controlled by `Renderer` [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/4affdfb50ea0670d85e52737c082c03f89ae8ada)
+- Replaced [`yansi-term`](https://crates.io/crates/yansi-term) with [`anstyle`](https://crates.io/crates/anstyle) [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/dfd4e87d6f31ec50d29af26d7310cff5e66ca978)
+  - `anstyle` is designed primarily to exist in public APIs for interoperability 
+  - `anstyle` is re-exported under `annotate_snippets::renderer`
+- Removed the `color` feature in favor of `Renderer::plain()` [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/dfd4e87d6f31ec50d29af26d7310cff5e66ca978)
+- Moved `Margin` to `renderer` module [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/79f657ea252c3c0ce55fa69894ee520f8820b4bf)
+- Made the `display_list` module private [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/da45f4858af3ec4c0d792ecc40225e27fdd2bac8)
+
+### Compatibility
+
+- Changed the edition to `2021` [#61](https://github.com/rust-lang/annotate-snippets-rs/pull/61)
+- Set the minimum supported Rust version to `1.70.0` [#61](https://github.com/rust-lang/annotate-snippets-rs/pull/61)
+
 ## [0.9.2] - October 30, 2023
 
 - Remove parsing of __ in title strings, fixes (#53)
@@ -46,7 +73,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Update the syntax to Rust 2018 idioms. (#4)
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...HEAD
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...HEAD
+[0.10.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...0.10.0
 [0.9.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.1...0.9.2
 [0.9.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.0...0.9.1
 [0.9.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.8.0...0.9.0

From 17a0d37c5c398a5654567f7a7b9308c8b34d14c7 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 12 Dec 2023 13:55:46 -0700
Subject: [PATCH 075/302] chore: Update version to `0.10.0`

---
 Cargo.lock | 2 +-
 Cargo.toml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 0d9659e..149d5c5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "annotate-snippets"
-version = "0.9.2"
+version = "0.10.0"
 dependencies = [
  "anstyle",
  "criterion",
diff --git a/Cargo.toml b/Cargo.toml
index ec39e99..51e52a0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "annotate-snippets"
-version = "0.9.2"
+version = "0.10.0"
 edition = "2021"
 rust-version = "1.70"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]

From 7aee27897ee6391257016a0831b0d17f7ec741ba Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 12 Dec 2023 15:13:03 -0700
Subject: [PATCH 076/302] chore: Update `README.md` example to use `Renderer`

---
 README.md | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md
index 2e5a20f..f1b0352 100644
--- a/README.md
+++ b/README.md
@@ -33,10 +33,7 @@ Usage
 -----
 
 ```rust
-use annotate_snippets::{
-    display_list::{DisplayList, FormatOptions},
-    snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
-};
+use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
 
 fn main() {
     let snippet = Snippet {
@@ -58,7 +55,7 @@ fn main() {
                 SourceAnnotation {
                     label: "",
                     annotation_type: AnnotationType::Error,
-                    range: (187, 189),
+                    range: (193, 195),
                 },
                 SourceAnnotation {
                     label: "while parsing this struct",
@@ -67,14 +64,10 @@ fn main() {
                 },
             ],
         }],
-        opt: FormatOptions {
-            color: true,
-            ..Default::default()
-        },
     };
 
-    let dl = DisplayList::from(snippet);
-    println!("{}", dl);
+    let renderer = Renderer::plain();
+    println!("{}", renderer.render(snippet));
 }
 ```
 

From a6a3a0ef43f70ad86c7ce7ca33f9ee0f5840bd51 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 29 Dec 2023 05:30:13 +0000
Subject: [PATCH 077/302] chore(deps): update msrv to v1.73

---
 .github/workflows/ci.yml | 6 +++---
 Cargo.toml               | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3a3d812..efa54a0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -49,7 +49,7 @@ jobs:
     - name: No-default features
       run: cargo test --workspace --no-default-features
   msrv:
-    name: "Check MSRV: 1.70"  # MSRV
+    name: "Check MSRV: 1.73"  # MSRV
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
@@ -57,7 +57,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.70"  # MSRV
+        toolchain: "1.73"  # MSRV
     - uses: Swatinem/rust-cache@v2
     - name: Default features
       run: cargo check --workspace --all-targets
@@ -119,7 +119,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.70"  # MSRV
+        toolchain: "1.73"  # MSRV
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools
diff --git a/Cargo.toml b/Cargo.toml
index 51e52a0..2cdd75e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
 name = "annotate-snippets"
 version = "0.10.0"
 edition = "2021"
-rust-version = "1.70"  # MSRV
+rust-version = "1.73"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]
 description = "Library for building code annotations"
 license = "Apache-2.0/MIT"

From 69fa0268a44a9859c4db98a6692369c0e8719146 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 1 Jan 2024 00:59:55 +0000
Subject: [PATCH 078/302] chore(deps): update actions/setup-python action to v5

---
 .github/workflows/pre-commit.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 8044750..9551407 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -19,5 +19,5 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v4
-    - uses: actions/setup-python@v4
+    - uses: actions/setup-python@v5
     - uses: pre-commit/action@v3.0.0

From 82c9aa7bddef20e9705d3d404403e59b2cd7e8e1 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 1 Jan 2024 00:59:58 +0000
Subject: [PATCH 079/302] chore(deps): update github/codeql-action action to v3

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 19efcf9..1b09d21 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -134,7 +134,7 @@ jobs:
         | sarif-fmt
       continue-on-error: true
     - name: Upload
-      uses: github/codeql-action/upload-sarif@v2
+      uses: github/codeql-action/upload-sarif@v3
       with:
         sarif_file: clippy-results.sarif
         wait-for-processing: true

From a4c062667d3b0c94928d285052795637de0a7227 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 2 Jan 2024 09:56:35 -0600
Subject: [PATCH 080/302] chore: Make renovate commits to match

---
 .github/renovate.json5 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 3119c42..e5a5de0 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -3,6 +3,7 @@
     'before 5am on the first day of the month',
   ],
   semanticCommits: 'enabled',
+  commitMessageLowerCase: 'never',
   configMigration: true,
   dependencyDashboard: true,
   customManagers: [

From 929a279ec4fbd97f3d828369aa0e5ba697f2de00 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 2 Jan 2024 14:14:52 -0700
Subject: [PATCH 081/302] chore: Apply clippy's suggestions

---
 src/renderer/display_list.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 02e14a5..e7784db 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -766,7 +766,7 @@ fn format_slice(
     has_footer: bool,
     margin: Option<Margin>,
 ) -> Vec<DisplayLine<'_>> {
-    let main_range = slice.annotations.get(0).map(|x| x.range.0);
+    let main_range = slice.annotations.first().map(|x| x.range.0);
     let origin = slice.origin;
     let need_empty_header = origin.is_some() || is_first;
     let mut body = format_body(slice, need_empty_header, has_footer, margin);

From 61250a36135d0032ceda4316b43d3a62d8b07643 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 3 Jan 2024 08:27:59 -0600
Subject: [PATCH 082/302] chore(ci): Optimize CI runs

---
 .github/workflows/ci.yml        |  2 +-
 .github/workflows/rust-next.yml | 10 +++++++---
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1b09d21..943becf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,7 +41,7 @@ jobs:
         toolchain: ${{ matrix.rust }}
     - uses: Swatinem/rust-cache@v2
     - name: Build
-      run: cargo test --no-run --workspace --all-features
+      run: cargo test --workspace --no-run
     - name: Default features
       run: cargo test --workspace
     - name: All features
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index d8c2d25..43b4ec8 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -32,6 +32,8 @@ jobs:
       with:
         toolchain: ${{ matrix.rust }}
     - uses: Swatinem/rust-cache@v2
+    - name: Build
+      run: cargo test --workspace --no-run
     - name: Default features
       run: cargo test --workspace
     - name: All features
@@ -51,9 +53,11 @@ jobs:
     - uses: Swatinem/rust-cache@v2
     - name: Update dependencues
       run: cargo update
+    - name: Build
+      run: cargo test --workspace --no-run
     - name: Default features
-      run: cargo test --workspace --all-targets
+      run: cargo test --workspace
     - name: All features
-      run: cargo test --workspace --all-targets --all-features
+      run: cargo test --workspace --all-features
     - name: No-default features
-      run: cargo test --workspace --all-targets --no-default-features
+      run: cargo test --workspace --no-default-features

From 49ef459a49f9294b7c6947611c2b2524713d31bf Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 2 Jan 2024 14:07:15 -0700
Subject: [PATCH 083/302] fix: Match rustc's colors

---
 src/renderer/mod.rs | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 0e19c08..be81ebc 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -62,15 +62,30 @@ impl Renderer {
 
     /// Default terminal styling
     pub const fn styled() -> Self {
+        const BRIGHT_BLUE: Style = if cfg!(windows) {
+            AnsiColor::BrightCyan.on_default()
+        } else {
+            AnsiColor::BrightBlue.on_default()
+        };
         Self {
             stylesheet: Stylesheet {
                 error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD),
-                warning: AnsiColor::BrightYellow.on_default().effects(Effects::BOLD),
-                info: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD),
-                note: Style::new().effects(Effects::BOLD),
+                warning: if cfg!(windows) {
+                    AnsiColor::BrightYellow.on_default()
+                } else {
+                    AnsiColor::Yellow.on_default()
+                }
+                .effects(Effects::BOLD),
+                info: BRIGHT_BLUE.effects(Effects::BOLD),
+                note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD),
                 help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
-                line_no: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD),
-                emphasis: Style::new().effects(Effects::BOLD),
+                line_no: BRIGHT_BLUE.effects(Effects::BOLD),
+                emphasis: if cfg!(windows) {
+                    AnsiColor::BrightWhite.on_default()
+                } else {
+                    Style::new()
+                }
+                .effects(Effects::BOLD),
                 none: Style::new(),
             },
             ..Self::plain()

From bc5398f5f54e70a50deb0e7c2bb67cbff29a5ed8 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 4 Jan 2024 14:17:11 -0700
Subject: [PATCH 084/302] fix: Allow highlighting one past end

---
 src/renderer/display_list.rs          |  7 ++++---
 tests/fixtures/no-color/one_past.toml | 12 ++++++++++++
 tests/fixtures/no-color/one_past.txt  |  6 ++++++
 3 files changed, 22 insertions(+), 3 deletions(-)
 create mode 100644 tests/fixtures/no-color/one_past.toml
 create mode 100644 tests/fixtures/no-color/one_past.txt

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index e7784db..f283e52 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -934,14 +934,15 @@ fn format_body(
 ) -> Vec<DisplayLine<'_>> {
     let source_len = slice.source.chars().count();
     if let Some(bigger) = slice.annotations.iter().find_map(|x| {
-        if source_len < x.range.1 {
+        // Allow highlighting one past the last character in the source.
+        if source_len + 1 < x.range.1 {
             Some(x.range)
         } else {
             None
         }
     }) {
         panic!(
-            "SourceAnnotation range `{:?}` is bigger than source length `{}`",
+            "SourceAnnotation range `{:?}` is beyond the end of buffer `{}`",
             bigger, source_len
         )
     }
@@ -1479,7 +1480,7 @@ mod tests {
             footer: vec![],
             slices: vec![snippet::Slice {
                 annotations: vec![snippet::SourceAnnotation {
-                    range: (0, source.len() + 1),
+                    range: (0, source.len() + 2),
                     label,
                     annotation_type: snippet::AnnotationType::Error,
                 }],
diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml
new file mode 100644
index 0000000..62d1d42
--- /dev/null
+++ b/tests/fixtures/no-color/one_past.toml
@@ -0,0 +1,12 @@
+[snippet.title]
+label = "expected `.`, `=`"
+annotation_type = "Error"
+
+[[snippet.slices]]
+source = "asdf"
+line_start = 1
+origin = "Cargo.toml"
+[[snippet.slices.annotations]]
+label = ""
+annotation_type = "Error"
+range = [4, 5]
diff --git a/tests/fixtures/no-color/one_past.txt b/tests/fixtures/no-color/one_past.txt
new file mode 100644
index 0000000..7f255b8
--- /dev/null
+++ b/tests/fixtures/no-color/one_past.txt
@@ -0,0 +1,6 @@
+error: expected `.`, `=`
+ --> Cargo.toml:1:5
+  |
+1 | asdf
+  |     ^
+  |

From e400fcbeabf72c5518f39d07a52cf9f78b387768 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 4 Jan 2024 16:26:35 -0600
Subject: [PATCH 085/302] chore: Add release process

---
 CHANGELOG.md |  1 +
 Cargo.toml   | 10 ++++++++++
 release.toml |  1 +
 3 files changed, 12 insertions(+)
 create mode 100644 release.toml

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e55c8d..45fa915 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+<!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
 ## [0.10.0] - December 12, 2023
diff --git a/Cargo.toml b/Cargo.toml
index 2cdd75e..16d8cf9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,6 +10,16 @@ repository = "https://github.com/rust-lang/annotate-snippets-rs"
 readme = "README.md"
 keywords = ["code", "analysis", "ascii", "errors", "debug"]
 
+[package.metadata.release]
+tag-name = "{{version}}"
+pre-release-replacements = [
+  {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
+  {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1},
+  {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
+  {file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## [Unreleased] - ReleaseDate\n", exactly=1},
+  {file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\n[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/{{tag_name}}...HEAD", exactly=1},
+]
+
 [badges]
 maintenance = { status = "actively-developed" }
 
diff --git a/release.toml b/release.toml
new file mode 100644
index 0000000..dac7b6b
--- /dev/null
+++ b/release.toml
@@ -0,0 +1 @@
+allow-branch = ["master"]

From f285fc97a1bcba269ca25f84f994d325a4ae6a79 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 4 Jan 2024 15:35:35 -0700
Subject: [PATCH 086/302] chore: Update CHANGELOG.md

---
 CHANGELOG.md | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e55c8d..fe12b3d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased] - ReleaseDate
 
+### Fixed
+
+- Match `rustc`'s colors [#73](https://github.com/rust-lang/annotate-snippets-rs/pull/73)
+- Allow highlighting one past the end of `source` [#74](https://github.com/rust-lang/annotate-snippets-rs/pull/74)
+
+### Compatibility
+
+- Set the minimum supported Rust version to `1.73.0` [#71](https://github.com/rust-lang/annotate-snippets-rs/pull/71)
+
 ## [0.10.0] - December 12, 2023
 
 ### Added

From b6d8d79f2a447fb7f45baa7a64727f9af81fb84b Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 4 Jan 2024 15:46:23 -0700
Subject: [PATCH 087/302] chore: Release annotate-snippets version 0.10.1

---
 CHANGELOG.md | 5 ++++-
 Cargo.lock   | 2 +-
 Cargo.toml   | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 963d58a..b660519 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+## [0.10.1] - 2024-01-04
+
 ### Fixed
 
 - Match `rustc`'s colors [#73](https://github.com/rust-lang/annotate-snippets-rs/pull/73)
@@ -83,7 +85,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Update the syntax to Rust 2018 idioms. (#4)
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...HEAD
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...HEAD
+[0.10.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...0.10.1
 [0.10.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...0.10.0
 [0.9.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.1...0.9.2
 [0.9.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.0...0.9.1
diff --git a/Cargo.lock b/Cargo.lock
index 149d5c5..221841a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "annotate-snippets"
-version = "0.10.0"
+version = "0.10.1"
 dependencies = [
  "anstyle",
  "criterion",
diff --git a/Cargo.toml b/Cargo.toml
index 16d8cf9..a39fd9d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "annotate-snippets"
-version = "0.10.0"
+version = "0.10.1"
 edition = "2021"
 rust-version = "1.73"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]

From e819db4af62e231bcedd976faa488b4e6f46f312 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 18 Jan 2024 09:22:25 -0600
Subject: [PATCH 088/302] chore(ci): Cancel prior CI runs

---
 .github/workflows/audit.yml      | 4 ++++
 .github/workflows/ci.yml         | 4 ++++
 .github/workflows/committed.yml  | 4 ++++
 .github/workflows/pre-commit.yml | 4 ++++
 .github/workflows/rust-next.yml  | 4 ++++
 .github/workflows/spelling.yml   | 4 ++++
 6 files changed, 24 insertions(+)

diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index ccee1fe..07c70ee 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -17,6 +17,10 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   security_audit:
     permissions:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 943becf..2bbd5a5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,6 +14,10 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   ci:
     permissions:
diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml
index 0462558..e7a50fb 100644
--- a/.github/workflows/committed.yml
+++ b/.github/workflows/committed.yml
@@ -11,6 +11,10 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   committed:
     name: Lint Commits
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 9551407..7d77328 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -12,6 +12,10 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   pre-commit:
     permissions:
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index 43b4ec8..f0febc9 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -12,6 +12,10 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   test:
     name: Test
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
index 12f7585..8e58d9e 100644
--- a/.github/workflows/spelling.yml
+++ b/.github/workflows/spelling.yml
@@ -10,6 +10,10 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   spelling:
     name: Spell Check with Typos

From 0b029063fe83d818e3a79819b88bee8b8314f752 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 24 Jan 2024 08:40:56 -0600
Subject: [PATCH 089/302] chore(ci): Be explicit in renovate updates

---
 .github/renovate.json5 | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index e5a5de0..32d3628 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -73,6 +73,7 @@
       matchCurrentVersion: '>=1.0.0',
       matchUpdateTypes: [
         'minor',
+        'patch',
       ],
       enabled: false,
     },
@@ -100,6 +101,7 @@
       matchCurrentVersion: '>=1.0.0',
       matchUpdateTypes: [
         'minor',
+        'patch',
       ],
       automerge: true,
       groupName: 'compatible (dev)',

From 131de55d50284af7a54287d34699347b74d75709 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 31 Jan 2024 12:07:12 -0600
Subject: [PATCH 090/302] chore(ci): Add m1 runners

See https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/
---
 .github/workflows/ci.yml        | 2 +-
 .github/workflows/rust-next.yml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2bbd5a5..a8826b0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,7 +32,7 @@ jobs:
     name: Test
     strategy:
       matrix:
-        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
+        os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"]
         rust: ["stable"]
     continue-on-error: ${{ matrix.rust != 'stable' }}
     runs-on: ${{ matrix.os }}
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index f0febc9..49e5d8c 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -21,7 +21,7 @@ jobs:
     name: Test
     strategy:
       matrix:
-        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
+        os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"]
         rust: ["stable", "beta"]
         include:
         - os: ubuntu-latest

From 1293b88a110e2448512407a6d5d48e31684cc327 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 1 Feb 2024 00:35:37 +0000
Subject: [PATCH 091/302] chore(deps): update rust crate serde to 1.0.196

---
 Cargo.lock | 20 ++++++++++----------
 Cargo.toml |  2 +-
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 221841a..6261065 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -346,18 +346,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.69"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.33"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 dependencies = [
  "proc-macro2",
 ]
@@ -447,18 +447,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.192"
+version = "1.0.196"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
+checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.192"
+version = "1.0.196"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
+checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -478,9 +478,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.39"
+version = "2.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/Cargo.toml b/Cargo.toml
index a39fd9d..77a251a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,7 +31,7 @@ unicode-width = "0.1.11"
 criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
-serde = { version = "1.0.192", features = ["derive"] }
+serde = { version = "1.0.196", features = ["derive"] }
 toml = "0.5.11"
 
 [[bench]]

From 9a5af5c8d21d549a7eb785343ae055d931952075 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 8 Feb 2024 07:45:48 -0600
Subject: [PATCH 092/302] chore(ci): Only check intel mac on schedule

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a8826b0..af065d5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,7 +32,7 @@ jobs:
     name: Test
     strategy:
       matrix:
-        os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"]
+        os: ["ubuntu-latest", "windows-latest", "macos-14"]
         rust: ["stable"]
     continue-on-error: ${{ matrix.rust != 'stable' }}
     runs-on: ${{ matrix.os }}

From da56001fd6cabdb744f25b021c9efd230472f671 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 8 Feb 2024 07:48:29 -0600
Subject: [PATCH 093/302] chore(ci): Gather coverage

---
 .github/workflows/ci.yml | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index af065d5..7849a73 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -144,3 +144,22 @@ jobs:
         wait-for-processing: true
     - name: Report status
       run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated
+  coverage:
+    name: Coverage
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: "1.75"  # STABLE
+    - uses: Swatinem/rust-cache@v2
+    - name: Install cargo-tarpaulin
+      run: cargo install cargo-tarpaulin
+    - name: Gather coverage
+      run: cargo tarpaulin --output-dir coverage --out lcov
+    - name: Publish to Coveralls
+      uses: coverallsapp/github-action@master
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}

From 1313256db3e9a31a3e0647abaacc9a8e4edb51b1 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 8 Feb 2024 09:07:52 -0600
Subject: [PATCH 094/302] chore(ci): Use latest for coverage

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7849a73..2e6597e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -153,7 +153,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.75"  # STABLE
+        toolchain: stable
     - uses: Swatinem/rust-cache@v2
     - name: Install cargo-tarpaulin
       run: cargo install cargo-tarpaulin

From 51a98a25b6c30dd2fbdd74795432006525d1d84a Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 15 Feb 2024 09:57:26 -0600
Subject: [PATCH 095/302] chore(ci): Defer to package.rust-version for clippy

---
 .clippy.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.clippy.toml b/.clippy.toml
index 090e2be..293c14f 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -1,4 +1,3 @@
-msrv = "1.65.0"  # MSRV
 warn-on-all-wildcard-imports = true
 allow-expect-in-tests = true
 allow-unwrap-in-tests = true

From 4db293d99b81e9c7da8fb030b1471e4e96dc91ef Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 15 Feb 2024 09:58:01 -0600
Subject: [PATCH 096/302] chore(ci): Only verify MSRV for published packages

---
 .github/workflows/ci.yml | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2e6597e..03a4fc0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -53,7 +53,7 @@ jobs:
     - name: No-default features
       run: cargo test --workspace --no-default-features
   msrv:
-    name: "Check MSRV: 1.65.0"
+    name: "Check MSRV"
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
@@ -61,14 +61,15 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.65.0"  # MSRV
+        toolchain: stable
     - uses: Swatinem/rust-cache@v2
+    - uses: taiki-e/install-action@cargo-hack
     - name: Default features
-      run: cargo check --workspace --all-targets
+      run: cargo hack check --rust-version --ignore-private --workspace --all-targets
     - name: All features
-      run: cargo check --workspace --all-targets --all-features
+      run: cargo hack check --rust-version --ignore-private --workspace --all-targets --all-features
     - name: No-default features
-      run: cargo check --workspace --all-targets --no-default-features
+      run: cargo hack check --rust-version --ignore-private --workspace --all-targets --no-default-features
   lockfile:
     runs-on: ubuntu-latest
     steps:

From 779496bb002837bf4e115ae3a33e7c819abdf293 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 15 Feb 2024 10:03:14 -0600
Subject: [PATCH 097/302] chore(ci): Run the latest clippy

---
 .github/renovate.json5   | 37 +++++++++++++++++++++++++++++++++++--
 .github/workflows/ci.yml |  2 +-
 2 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 32d3628..06c1d63 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -21,7 +21,25 @@
         'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)',
         '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV',
       ],
-      depNameTemplate: 'rust',
+      depNameTemplate: 'MSRV',
+      packageNameTemplate: 'rust-lang/rust',
+      datasourceTemplate: 'github-releases',
+    },
+    {
+      customType: 'regex',
+      fileMatch: [
+        '^rust-toolchain\\.toml$',
+        'Cargo.toml$',
+        'clippy.toml$',
+        '\\.clippy.toml$',
+        '^\\.github/workflows/ci.yml$',
+        '^\\.github/workflows/rust-next.yml$',
+      ],
+      matchStrings: [
+        'STABLE.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)',
+        '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?STABLE',
+      ],
+      depNameTemplate: 'STABLE',
       packageNameTemplate: 'rust-lang/rust',
       datasourceTemplate: 'github-releases',
     },
@@ -33,7 +51,7 @@
         'custom.regex',
       ],
       matchPackageNames: [
-        'rust',
+        'MSRV',
       ],
       minimumReleaseAge: '336 days',  // 8 releases * 6 weeks per release * 7 days per week
       internalChecksFilter: 'strict',
@@ -41,6 +59,21 @@
       schedule: [
         '* * * * *',
       ],
+      groupName: 'rust-version',
+    },
+    {
+      commitMessageTopic: 'STABLE',
+      matchManagers: [
+        'custom.regex',
+      ],
+      matchPackageNames: [
+        'STABLE',
+      ],
+      extractVersion: '^(?<version>\\d+\\.\\d+)',  // Drop the patch version
+      schedule: [
+        '* * * * *',
+      ],
+      groupName: 'rust-version',
     },
     // Goals:
     // - Keep version reqs low, ignoring compatible normal/build dependencies
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 03a4fc0..1c18ca6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -124,7 +124,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.65.0"  # MSRV
+        toolchain: "1.76"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From c977df514987a625772ca04df9fc100ba8b7576f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 20 Feb 2024 20:22:05 -0600
Subject: [PATCH 098/302] chore(ci): Prevent cargo-hack from blowing away our
 lockfile

See taiki-e/cargo-hack#234
---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1c18ca6..12d398c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -65,11 +65,11 @@ jobs:
     - uses: Swatinem/rust-cache@v2
     - uses: taiki-e/install-action@cargo-hack
     - name: Default features
-      run: cargo hack check --rust-version --ignore-private --workspace --all-targets
+      run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets
     - name: All features
-      run: cargo hack check --rust-version --ignore-private --workspace --all-targets --all-features
+      run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets --all-features
     - name: No-default features
-      run: cargo hack check --rust-version --ignore-private --workspace --all-targets --no-default-features
+      run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets --no-default-features
   lockfile:
     runs-on: ubuntu-latest
     steps:

From 8a29fa8b4d7ac4734e71a90bb17783f55465da22 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 29 Feb 2024 08:56:51 -0700
Subject: [PATCH 099/302] feat: Add feature for testing colored output easier

---
 Cargo.toml          |  1 +
 src/lib.rs          |  9 +++++++++
 src/renderer/mod.rs | 10 +++++++---
 3 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index a39fd9d..45744cd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,3 +40,4 @@ harness = false
 
 [features]
 default = []
+testing-colors = []
diff --git a/src/lib.rs b/src/lib.rs
index 80eac53..03ffbb6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -40,6 +40,15 @@
 //! options, such as color, or margins.
 //!
 //! Finally, `impl Display` into a final `String` output.
+//!
+//! # features
+//! - `testing-colors` - Makes [Renderer::styled] colors OS independent, which
+//! allows for easier testing when testing colored output. It should be added as
+//! a feature in `[dev-dependencies]`, which can be done with the following command:
+//! ```text
+//! cargo add annotate-snippets --dev --feature testing-colors
+//! ```
+//!
 
 pub mod renderer;
 mod snippet;
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index be81ebc..b6108ce 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -61,8 +61,12 @@ impl Renderer {
     }
 
     /// Default terminal styling
+    ///
+    /// # Note
+    /// When testing styled terminal output, see the [`testing-colors` feature](crate#features)
     pub const fn styled() -> Self {
-        const BRIGHT_BLUE: Style = if cfg!(windows) {
+        const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors");
+        const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS {
             AnsiColor::BrightCyan.on_default()
         } else {
             AnsiColor::BrightBlue.on_default()
@@ -70,7 +74,7 @@ impl Renderer {
         Self {
             stylesheet: Stylesheet {
                 error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD),
-                warning: if cfg!(windows) {
+                warning: if USE_WINDOWS_COLORS {
                     AnsiColor::BrightYellow.on_default()
                 } else {
                     AnsiColor::Yellow.on_default()
@@ -80,7 +84,7 @@ impl Renderer {
                 note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD),
                 help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
                 line_no: BRIGHT_BLUE.effects(Effects::BOLD),
-                emphasis: if cfg!(windows) {
+                emphasis: if USE_WINDOWS_COLORS {
                     AnsiColor::BrightWhite.on_default()
                 } else {
                     Style::new()

From 6dab14f3c1014c71975d2e92e905ee55b87f6cf5 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 29 Feb 2024 14:54:00 -0700
Subject: [PATCH 100/302] chore: Update CHANGELOG.md

---
 CHANGELOG.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b660519..f2ffa6f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,8 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
 <!-- next-header -->
+
 ## [Unreleased] - ReleaseDate
 
+### Added
+
+- Added `testing-colors` feature to remove platform-specific colors when testing
+  [#82](https://github.com/rust-lang/annotate-snippets-rs/pull/82)
+
 ## [0.10.1] - 2024-01-04
 
 ### Fixed

From 9c373d4ab2545effe772169c502f6a1bcf019113 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 29 Feb 2024 14:56:51 -0700
Subject: [PATCH 101/302] chore: Release annotate-snippets version 0.10.2

---
 CHANGELOG.md | 7 +++++--
 Cargo.lock   | 2 +-
 Cargo.toml   | 2 +-
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2ffa6f..0f46022 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,9 +5,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
 <!-- next-header -->
-
 ## [Unreleased] - ReleaseDate
 
+
+## [0.10.2] - 2024-02-29
+
 ### Added
 
 - Added `testing-colors` feature to remove platform-specific colors when testing
@@ -91,7 +93,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Update the syntax to Rust 2018 idioms. (#4)
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...HEAD
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...HEAD
+[0.10.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...0.10.2
 [0.10.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...0.10.1
 [0.10.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...0.10.0
 [0.9.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.1...0.9.2
diff --git a/Cargo.lock b/Cargo.lock
index 6261065..86766fc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "annotate-snippets"
-version = "0.10.1"
+version = "0.10.2"
 dependencies = [
  "anstyle",
  "criterion",
diff --git a/Cargo.toml b/Cargo.toml
index 650d8eb..05a3482 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "annotate-snippets"
-version = "0.10.1"
+version = "0.10.2"
 edition = "2021"
 rust-version = "1.73"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]

From 7846c5130e5459ce452bd4fdb17373d83f45dff3 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Mar 2024 00:30:14 +0000
Subject: [PATCH 102/302] chore(deps): Update pre-commit/action action to
 v3.0.1

---
 .github/workflows/pre-commit.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 7d77328..1b000ab 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -24,4 +24,4 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - uses: actions/setup-python@v5
-    - uses: pre-commit/action@v3.0.0
+    - uses: pre-commit/action@v3.0.1

From 4171d18ca006ec0f99fcf2dcfb1d93d1f6faea96 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Mar 2024 00:40:16 +0000
Subject: [PATCH 103/302] chore(deps): update rust crate serde to 1.0.197

---
 Cargo.lock | 8 ++++----
 Cargo.toml | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 86766fc..53b8e3c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -447,18 +447,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.196"
+version = "1.0.197"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
+checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.196"
+version = "1.0.197"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
+checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/Cargo.toml b/Cargo.toml
index 05a3482..3f5bf82 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,7 +31,7 @@ unicode-width = "0.1.11"
 criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
-serde = { version = "1.0.196", features = ["derive"] }
+serde = { version = "1.0.197", features = ["derive"] }
 toml = "0.5.11"
 
 [[bench]]

From 2ed735f9cbb5b27b36ed0b28eda7db58d8054df1 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Mar 2024 04:54:49 +0000
Subject: [PATCH 104/302] chore(deps): update pre-commit/action action to
 v3.0.1

---
 .github/workflows/pre-commit.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 8044750..9a5c763 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -20,4 +20,4 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - uses: actions/setup-python@v4
-    - uses: pre-commit/action@v3.0.0
+    - uses: pre-commit/action@v3.0.1

From 2687807093a5f658d4241a900f823e1a48ef7c25 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Mar 2024 04:55:30 +0000
Subject: [PATCH 105/302] chore(deps): update github/codeql-action action to v3

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index efa54a0..f10a0db 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -134,7 +134,7 @@ jobs:
         | sarif-fmt
       continue-on-error: true
     - name: Upload
-      uses: github/codeql-action/upload-sarif@v2
+      uses: github/codeql-action/upload-sarif@v3
       with:
         sarif_file: clippy-results.sarif
         wait-for-processing: true

From ad801833687d72c45f1346b402bba32afc6bb28e Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 2 Mar 2024 16:24:16 -0700
Subject: [PATCH 106/302] refactor(fixtures): Move to snapbox with SVGs

---
 Cargo.lock                                    | 422 +++++++++++++++++-
 Cargo.toml                                    |   5 +
 tests/diff/mod.rs                             |  63 ---
 .../mod.rs => fixtures/deserialize.rs}        |   0
 tests/fixtures/main.rs                        |  33 ++
 tests/fixtures/no-color/issue_52.svg          |  35 ++
 tests/fixtures/no-color/issue_52.txt          |   7 -
 tests/fixtures/no-color/issue_9.svg           |  45 ++
 tests/fixtures/no-color/issue_9.txt           |  12 -
 .../no-color/multiline_annotation.svg         |  49 ++
 .../no-color/multiline_annotation.txt         |  14 -
 .../no-color/multiline_annotation2.svg        |  39 ++
 .../no-color/multiline_annotation2.txt        |   9 -
 .../no-color/multiline_annotation3.svg        |  39 ++
 .../no-color/multiline_annotation3.txt        |   9 -
 .../no-color/multiple_annotations.svg         |  49 ++
 .../no-color/multiple_annotations.txt         |  14 -
 tests/fixtures/no-color/one_past.svg          |  33 ++
 tests/fixtures/no-color/one_past.txt          |   6 -
 tests/fixtures/no-color/simple.svg            |  39 ++
 tests/fixtures/no-color/simple.txt            |   9 -
 tests/fixtures/no-color/strip_line.svg        |  33 ++
 tests/fixtures/no-color/strip_line.txt        |   6 -
 tests/fixtures/no-color/strip_line_char.svg   |  33 ++
 tests/fixtures/no-color/strip_line_char.txt   |   6 -
 tests/fixtures/no-color/strip_line_non_ws.svg |  33 ++
 tests/fixtures/no-color/strip_line_non_ws.txt |   6 -
 tests/fixtures_test.rs                        |  46 --
 28 files changed, 867 insertions(+), 227 deletions(-)
 delete mode 100644 tests/diff/mod.rs
 rename tests/{deserialize/mod.rs => fixtures/deserialize.rs} (100%)
 create mode 100644 tests/fixtures/main.rs
 create mode 100644 tests/fixtures/no-color/issue_52.svg
 delete mode 100644 tests/fixtures/no-color/issue_52.txt
 create mode 100644 tests/fixtures/no-color/issue_9.svg
 delete mode 100644 tests/fixtures/no-color/issue_9.txt
 create mode 100644 tests/fixtures/no-color/multiline_annotation.svg
 delete mode 100644 tests/fixtures/no-color/multiline_annotation.txt
 create mode 100644 tests/fixtures/no-color/multiline_annotation2.svg
 delete mode 100644 tests/fixtures/no-color/multiline_annotation2.txt
 create mode 100644 tests/fixtures/no-color/multiline_annotation3.svg
 delete mode 100644 tests/fixtures/no-color/multiline_annotation3.txt
 create mode 100644 tests/fixtures/no-color/multiple_annotations.svg
 delete mode 100644 tests/fixtures/no-color/multiple_annotations.txt
 create mode 100644 tests/fixtures/no-color/one_past.svg
 delete mode 100644 tests/fixtures/no-color/one_past.txt
 create mode 100644 tests/fixtures/no-color/simple.svg
 delete mode 100644 tests/fixtures/no-color/simple.txt
 create mode 100644 tests/fixtures/no-color/strip_line.svg
 delete mode 100644 tests/fixtures/no-color/strip_line.txt
 create mode 100644 tests/fixtures/no-color/strip_line_char.svg
 delete mode 100644 tests/fixtures/no-color/strip_line_char.txt
 create mode 100644 tests/fixtures/no-color/strip_line_non_ws.svg
 delete mode 100644 tests/fixtures/no-color/strip_line_non_ws.txt
 delete mode 100644 tests/fixtures_test.rs

diff --git a/Cargo.lock b/Cargo.lock
index 53b8e3c..034e18c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -26,28 +26,109 @@ dependencies = [
  "difference",
  "glob",
  "serde",
+ "snapbox",
  "toml",
  "unicode-width",
 ]
 
+[[package]]
+name = "anstream"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
 [[package]]
 name = "anstyle"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
 
+[[package]]
+name = "anstyle-lossy"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9a0444767dbd4aea9355cb47a370eb184dbfe918875e127eff52cb9d1638181"
+dependencies = [
+ "anstyle",
+]
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-svg"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6ddad447b448d6d5db36b31cbd3ff27c7af071619501998eeceab01968287a"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "anstyle-lossy",
+ "html-escape",
+ "unicode-width",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
 [[package]]
 name = "bitflags"
 version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
 
+[[package]]
+name = "bstr"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
 [[package]]
 name = "bumpalo"
 version = "3.14.0"
@@ -100,6 +181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
 dependencies = [
  "clap_builder",
+ "clap_derive",
 ]
 
 [[package]]
@@ -108,8 +190,22 @@ version = "4.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
 dependencies = [
+ "anstream",
  "anstyle",
  "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -118,6 +214,21 @@ version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
 
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "content_inspector"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "criterion"
 version = "0.5.1"
@@ -193,6 +304,12 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
 
+[[package]]
+name = "dunce"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
+
 [[package]]
 name = "either"
 version = "1.9.0"
@@ -201,12 +318,39 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
 
 [[package]]
 name = "errno"
-version = "0.3.6"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "escape8259"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba4f4911e3666fcd7826997b4745c8224295a6f3072f1418c3067b97a67557ee"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "filetime"
+version = "0.2.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
+checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
 dependencies = [
+ "cfg-if",
  "libc",
- "windows-sys",
+ "redox_syscall",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -215,18 +359,62 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
+[[package]]
+name = "globset"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
 [[package]]
 name = "half"
 version = "1.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
 
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
 [[package]]
 name = "hermit-abi"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
 
+[[package]]
+name = "html-escape"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
+dependencies = [
+ "utf8-width",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
 [[package]]
 name = "is-terminal"
 version = "0.4.9"
@@ -235,7 +423,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
 dependencies = [
  "hermit-abi",
  "rustix",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -264,15 +452,27 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.150"
+version = "0.2.153"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "libtest-mimic"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f0f4c6f44ecfd52e8b443f2ad18f2b996540135771561283c2352ce56a1c70b"
+dependencies = [
+ "clap",
+ "escape8259",
+ "termcolor",
+ "threadpool",
+]
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.11"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
 
 [[package]]
 name = "log"
@@ -295,6 +495,12 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
 [[package]]
 name = "num-traits"
 version = "0.2.17"
@@ -304,6 +510,16 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.18.0"
@@ -382,6 +598,15 @@ dependencies = [
  "crossbeam-utils",
 ]
 
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
 [[package]]
 name = "regex"
 version = "1.10.2"
@@ -413,17 +638,23 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "rustix"
-version = "0.38.21"
+version = "0.38.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
+checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
 dependencies = [
- "bitflags",
+ "bitflags 2.4.1",
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys",
+ "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
 [[package]]
 name = "ryu"
 version = "1.0.15"
@@ -476,6 +707,49 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "similar"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
+
+[[package]]
+name = "snapbox"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6de4a00e16c1e859fbc29a6484c5aad30f18fb9f4c2c29033c28aef7e965b82e"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "anstyle-svg",
+ "content_inspector",
+ "dunce",
+ "filetime",
+ "ignore",
+ "libtest-mimic",
+ "normalize-line-endings",
+ "serde_json",
+ "similar",
+ "snapbox-macros",
+ "tempfile",
+ "walkdir",
+]
+
+[[package]]
+name = "snapbox-macros"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1c4b838b05d15ab22754068cb73500b2f3b07bf09d310e15b27f88160f1de40"
+dependencies = [
+ "anstream",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
 [[package]]
 name = "syn"
 version = "2.0.48"
@@ -487,6 +761,36 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "tempfile"
+version = "3.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
 [[package]]
 name = "tinytemplate"
 version = "1.2.1"
@@ -518,6 +822,18 @@ version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
 
+[[package]]
+name = "utf8-width"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
 [[package]]
 name = "walkdir"
 version = "2.4.0"
@@ -629,7 +945,16 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.4",
 ]
 
 [[package]]
@@ -638,13 +963,28 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
 ]
 
 [[package]]
@@ -653,38 +993,80 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
diff --git a/Cargo.toml b/Cargo.toml
index 3f5bf82..8763c11 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,12 +32,17 @@ criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
 serde = { version = "1.0.197", features = ["derive"] }
+snapbox = { version = "0.5.8", features = ["diff", "harness", "path", "term-svg"] }
 toml = "0.5.11"
 
 [[bench]]
 name = "simple"
 harness = false
 
+[[test]]
+name = "fixtures"
+harness = false
+
 [features]
 default = []
 testing-colors = []
diff --git a/tests/diff/mod.rs b/tests/diff/mod.rs
deleted file mode 100644
index 60ccae1..0000000
--- a/tests/diff/mod.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use annotate_snippets::renderer::{AnsiColor, Color, Style};
-use difference::{Changeset, Difference};
-
-const GREEN: Style = AnsiColor::Green.on_default();
-pub fn get_diff(left: &str, right: &str) -> String {
-    let mut output = String::new();
-
-    let Changeset { diffs, .. } = Changeset::new(left, right, "\n");
-
-    for i in 0..diffs.len() {
-        match diffs[i] {
-            Difference::Same(ref x) => {
-                output += &format!(" {}\n", x);
-            }
-            Difference::Add(ref x) => {
-                match diffs[i - 1] {
-                    Difference::Rem(ref y) => {
-                        output += &format!("{}+{}", GREEN.render(), GREEN.render_reset());
-                        let Changeset { diffs, .. } = Changeset::new(y, x, " ");
-                        for c in diffs {
-                            match c {
-                                Difference::Same(ref z) => {
-                                    output += &format!(
-                                        "{}{}{} ",
-                                        GREEN.render(),
-                                        z.as_str(),
-                                        GREEN.render_reset()
-                                    );
-                                }
-                                Difference::Add(ref z) => {
-                                    let black_on_green = Style::new()
-                                        .bg_color(Some(Color::Ansi(AnsiColor::Green)))
-                                        .fg_color(Some(Color::Ansi(AnsiColor::Black)));
-                                    output += &format!(
-                                        "{}{}{} ",
-                                        black_on_green.render(),
-                                        z.as_str(),
-                                        black_on_green.render_reset()
-                                    );
-                                }
-                                _ => (),
-                            }
-                        }
-                        output += "\n";
-                    }
-                    _ => {
-                        output += &format!(
-                            "+{}{}{}\n",
-                            GREEN.render(),
-                            x.as_str(),
-                            GREEN.render_reset()
-                        );
-                    }
-                };
-            }
-            Difference::Rem(ref x) => {
-                let red = AnsiColor::Red.on_default();
-                output += &format!("-{}{}{}\n", red.render(), x.as_str(), red.render_reset());
-            }
-        }
-    }
-    output
-}
diff --git a/tests/deserialize/mod.rs b/tests/fixtures/deserialize.rs
similarity index 100%
rename from tests/deserialize/mod.rs
rename to tests/fixtures/deserialize.rs
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
new file mode 100644
index 0000000..c320407
--- /dev/null
+++ b/tests/fixtures/main.rs
@@ -0,0 +1,33 @@
+mod deserialize;
+
+use crate::deserialize::Fixture;
+use annotate_snippets::{Renderer, Snippet};
+use snapbox::data::DataFormat;
+use snapbox::Data;
+use std::error::Error;
+
+fn main() {
+    #[cfg(not(windows))]
+    snapbox::harness::Harness::new("tests/fixtures/", setup, test)
+        .select(["*/*.toml"])
+        .action_env("SNAPSHOTS")
+        .test();
+}
+
+fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case {
+    let name = input_path.file_name().unwrap().to_str().unwrap().to_owned();
+    let expected = input_path.with_extension("svg");
+    snapbox::harness::Case {
+        name,
+        fixture: input_path,
+        expected,
+    }
+}
+
+fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> {
+    let src = std::fs::read_to_string(input_path)?;
+    let (renderer, snippet): (Renderer, Snippet<'_>) =
+        toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?;
+    let actual = renderer.render(snippet).to_string();
+    Ok(Data::from(actual).coerce_to(DataFormat::TermSvg))
+}
diff --git a/tests/fixtures/no-color/issue_52.svg b/tests/fixtures/no-color/issue_52.svg
new file mode 100644
index 0000000..5ed2228
--- /dev/null
+++ b/tests/fixtures/no-color/issue_52.svg
@@ -0,0 +1,35 @@
+<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; path/to/error.rs:3:1</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>...</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>3 | invalid syntax</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>  | -------------- error here</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>  |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/issue_52.txt b/tests/fixtures/no-color/issue_52.txt
deleted file mode 100644
index b1c6bf2..0000000
--- a/tests/fixtures/no-color/issue_52.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-error
- --> path/to/error.rs:3:1
-  |
-...
-3 | invalid syntax
-  | -------------- error here
-  |
\ No newline at end of file
diff --git a/tests/fixtures/no-color/issue_9.svg b/tests/fixtures/no-color/issue_9.svg
new file mode 100644
index 0000000..af22d82
--- /dev/null
+++ b/tests/fixtures/no-color/issue_9.svg
@@ -0,0 +1,45 @@
+<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>4 | let x = vec![1];</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>  |     - move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>7 | let y = x;</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>  |         - value moved here</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="190px"><tspan>9 | x;</tspan>
+</tspan>
+    <tspan x="10px" y="208px"><tspan>  | ^ value used here after move</tspan>
+</tspan>
+    <tspan x="10px" y="226px"><tspan>  |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/issue_9.txt b/tests/fixtures/no-color/issue_9.txt
deleted file mode 100644
index affe6bc..0000000
--- a/tests/fixtures/no-color/issue_9.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-error: expected one of `.`, `;`, `?`, or an operator, found `for`
- --> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5
-  |
-4 | let x = vec![1];
-  |     - move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
-  |
-7 | let y = x;
-  |         - value moved here
-  |
-9 | x;
-  | ^ value used here after move
-  |
\ No newline at end of file
diff --git a/tests/fixtures/no-color/multiline_annotation.svg b/tests/fixtures/no-color/multiline_annotation.svg
new file mode 100644
index 0000000..5fa6e81
--- /dev/null
+++ b/tests/fixtures/no-color/multiline_annotation.svg
@@ -0,0 +1,49 @@
+<svg width="869px" height="272px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <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; src/format.rs:51:6</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>   |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>51 |   ) -&gt; Option&lt;String&gt; {</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>   |        -------------- expected `std::option::Option&lt;std::string::String&gt;` because of return type</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>52 | /     for ann in annotations {</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>53 | |         match (ann.range.0, ann.range.1) {</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>54 | |             (None, None) =&gt; continue,</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan>55 | |             (Some(start), Some(end)) if start &gt; end_index || end &lt; start_index =&gt; continue,</tspan>
+</tspan>
+    <tspan x="10px" y="190px"><tspan>...  |</tspan>
+</tspan>
+    <tspan x="10px" y="208px"><tspan>71 | |         }</tspan>
+</tspan>
+    <tspan x="10px" y="226px"><tspan>72 | |     }</tspan>
+</tspan>
+    <tspan x="10px" y="244px"><tspan>   | |_____^ expected enum `std::option::Option`, found ()</tspan>
+</tspan>
+    <tspan x="10px" y="262px"><tspan>   |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/multiline_annotation.txt b/tests/fixtures/no-color/multiline_annotation.txt
deleted file mode 100644
index bacdec1..0000000
--- a/tests/fixtures/no-color/multiline_annotation.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-error[E0308]: mismatched types
-  --> src/format.rs:51:6
-   |
-51 |   ) -> Option<String> {
-   |        -------------- expected `std::option::Option<std::string::String>` because of return type
-52 | /     for ann in annotations {
-53 | |         match (ann.range.0, ann.range.1) {
-54 | |             (None, None) => continue,
-55 | |             (Some(start), Some(end)) if start > end_index || end < start_index => continue,
-...  |
-71 | |         }
-72 | |     }
-   | |_____^ expected enum `std::option::Option`, found ()
-   |
diff --git a/tests/fixtures/no-color/multiline_annotation2.svg b/tests/fixtures/no-color/multiline_annotation2.svg
new file mode 100644
index 0000000..f4b4433
--- /dev/null
+++ b/tests/fixtures/no-color/multiline_annotation2.svg
@@ -0,0 +1,39 @@
+<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error[E0027]: pattern does not mention fields `lineno`, `content`</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>   --&gt; src/display_list.rs:139:32</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>    |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>139 |                           if let DisplayLine::Source {</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>    |  ________________________________^</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>140 | |                             ref mut inline_marks,</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>141 | |                         } = body[body_idx]</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>    | |_________________________^ missing fields `lineno`, `content`</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan>    |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/multiline_annotation2.txt b/tests/fixtures/no-color/multiline_annotation2.txt
deleted file mode 100644
index 8a00bfa..0000000
--- a/tests/fixtures/no-color/multiline_annotation2.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-error[E0027]: pattern does not mention fields `lineno`, `content`
-   --> src/display_list.rs:139:32
-    |
-139 |                           if let DisplayLine::Source {
-    |  ________________________________^
-140 | |                             ref mut inline_marks,
-141 | |                         } = body[body_idx]
-    | |_________________________^ missing fields `lineno`, `content`
-    |
diff --git a/tests/fixtures/no-color/multiline_annotation3.svg b/tests/fixtures/no-color/multiline_annotation3.svg
new file mode 100644
index 0000000..18a9bf6
--- /dev/null
+++ b/tests/fixtures/no-color/multiline_annotation3.svg
@@ -0,0 +1,39 @@
+<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error[E####]: spacing error found</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>  --&gt; foo.txt:26:12</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>   |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>26 |   This is an exampl</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>   |  ____________^</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>27 | | e of an edge case of an annotation overflowing</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>   | |_^ this should not be on separate lines</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>28 |   to exactly one character on next line.</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan>   |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/multiline_annotation3.txt b/tests/fixtures/no-color/multiline_annotation3.txt
deleted file mode 100644
index 12e174c..0000000
--- a/tests/fixtures/no-color/multiline_annotation3.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-error[E####]: spacing error found
-  --> foo.txt:26:12
-   |
-26 |   This is an exampl
-   |  ____________^
-27 | | e of an edge case of an annotation overflowing
-   | |_^ this should not be on separate lines
-28 |   to exactly one character on next line.
-   |
\ No newline at end of file
diff --git a/tests/fixtures/no-color/multiple_annotations.svg b/tests/fixtures/no-color/multiple_annotations.svg
new file mode 100644
index 0000000..3f15144
--- /dev/null
+++ b/tests/fixtures/no-color/multiple_annotations.svg
@@ -0,0 +1,49 @@
+<svg width="768px" height="272px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>    |</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> 96 | fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan> 97 |     if let Some(annotation) = main_annotation {</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>    |                 ^^^^^^^^^^ Variable defined here</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan> 98 |         result.push(format_title_line(</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan> 99 |             &amp;annotation.annotation_type,</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>    |              ^^^^^^^^^^ Referenced here</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>100 |             None,</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan>101 |             &amp;annotation.label,</tspan>
+</tspan>
+    <tspan x="10px" y="190px"><tspan>    |              ^^^^^^^^^^ Referenced again here</tspan>
+</tspan>
+    <tspan x="10px" y="208px"><tspan>102 |         ));</tspan>
+</tspan>
+    <tspan x="10px" y="226px"><tspan>103 |     }</tspan>
+</tspan>
+    <tspan x="10px" y="244px"><tspan>104 | }</tspan>
+</tspan>
+    <tspan x="10px" y="262px"><tspan>    |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/multiple_annotations.txt b/tests/fixtures/no-color/multiple_annotations.txt
deleted file mode 100644
index 26c677f..0000000
--- a/tests/fixtures/no-color/multiple_annotations.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-    |
- 96 | fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {
- 97 |     if let Some(annotation) = main_annotation {
-    |                 ^^^^^^^^^^ Variable defined here
- 98 |         result.push(format_title_line(
- 99 |             &annotation.annotation_type,
-    |              ^^^^^^^^^^ Referenced here
-100 |             None,
-101 |             &annotation.label,
-    |              ^^^^^^^^^^ Referenced again here
-102 |         ));
-103 |     }
-104 | }
-    |
diff --git a/tests/fixtures/no-color/one_past.svg b/tests/fixtures/no-color/one_past.svg
new file mode 100644
index 0000000..c8900d0
--- /dev/null
+++ b/tests/fixtures/no-color/one_past.svg
@@ -0,0 +1,33 @@
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:5</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>1 | asdf</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>  |     ^</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>  |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/one_past.txt b/tests/fixtures/no-color/one_past.txt
deleted file mode 100644
index 7f255b8..0000000
--- a/tests/fixtures/no-color/one_past.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-error: expected `.`, `=`
- --> Cargo.toml:1:5
-  |
-1 | asdf
-  |     ^
-  |
diff --git a/tests/fixtures/no-color/simple.svg b/tests/fixtures/no-color/simple.svg
new file mode 100644
index 0000000..51a3a65
--- /dev/null
+++ b/tests/fixtures/no-color/simple.svg
@@ -0,0 +1,39 @@
+<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>   --&gt; src/format_color.rs:171:9</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>    |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>169 |         })</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>    |           - expected one of `.`, `;`, `?`, or an operator here</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>170 | </tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>171 |         for line in &amp;self.body {</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>    |         ^^^ unexpected token</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan>    |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/simple.txt b/tests/fixtures/no-color/simple.txt
deleted file mode 100644
index 752cc89..0000000
--- a/tests/fixtures/no-color/simple.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-error: expected one of `.`, `;`, `?`, or an operator, found `for`
-   --> src/format_color.rs:171:9
-    |
-169 |         })
-    |           - expected one of `.`, `;`, `?`, or an operator here
-170 | 
-171 |         for line in &self.body {
-    |         ^^^ unexpected token
-    |
diff --git a/tests/fixtures/no-color/strip_line.svg b/tests/fixtures/no-color/strip_line.svg
new file mode 100644
index 0000000..b1fd8a6
--- /dev/null
+++ b/tests/fixtures/no-color/strip_line.svg
@@ -0,0 +1,33 @@
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <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/whitespace-trimming.rs:4:193</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>   |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>LL | ...                   let _: () = 42;</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>   |                                   ^^ expected (), found integer</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>   |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/strip_line.txt b/tests/fixtures/no-color/strip_line.txt
deleted file mode 100644
index 65b0538..0000000
--- a/tests/fixtures/no-color/strip_line.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-error[E0308]: mismatched types
-  --> $DIR/whitespace-trimming.rs:4:193
-   |
-LL | ...                   let _: () = 42;
-   |                                   ^^ expected (), found integer
-   |
diff --git a/tests/fixtures/no-color/strip_line_char.svg b/tests/fixtures/no-color/strip_line_char.svg
new file mode 100644
index 0000000..15296a1
--- /dev/null
+++ b/tests/fixtures/no-color/strip_line_char.svg
@@ -0,0 +1,33 @@
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <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/whitespace-trimming.rs:4:193</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>   |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>LL | ...                   let _: () = 42ñ</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>   |                                   ^^ expected (), found integer</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>   |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/strip_line_char.txt b/tests/fixtures/no-color/strip_line_char.txt
deleted file mode 100644
index 3d4b700..0000000
--- a/tests/fixtures/no-color/strip_line_char.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-error[E0308]: mismatched types
-  --> $DIR/whitespace-trimming.rs:4:193
-   |
-LL | ...                   let _: () = 42ñ
-   |                                   ^^ expected (), found integer
-   |
diff --git a/tests/fixtures/no-color/strip_line_non_ws.svg b/tests/fixtures/no-color/strip_line_non_ws.svg
new file mode 100644
index 0000000..6a72e7c
--- /dev/null
+++ b/tests/fixtures/no-color/strip_line_non_ws.svg
@@ -0,0 +1,33 @@
+<svg width="1238px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <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>
+    <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>
+    <tspan x="10px" y="100px"><tspan>   |                                                       ^^ expected (), found integer</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>   |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/strip_line_non_ws.txt b/tests/fixtures/no-color/strip_line_non_ws.txt
deleted file mode 100644
index 850619a..0000000
--- a/tests/fixtures/no-color/strip_line_non_ws.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-error[E0308]: mismatched types
-  --> $DIR/non-whitespace-trimming.rs:4:241
-   |
-LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();...
-   |                                                       ^^ expected (), found integer
-   |
diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs
deleted file mode 100644
index 063829a..0000000
--- a/tests/fixtures_test.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-mod deserialize;
-mod diff;
-
-use crate::deserialize::Fixture;
-use annotate_snippets::Renderer;
-use annotate_snippets::Snippet;
-use glob::glob;
-use std::{error::Error, fs::File, io, io::prelude::*};
-
-fn read_file(path: &str) -> Result<String, io::Error> {
-    let mut f = File::open(path)?;
-    let mut s = String::new();
-    (f.read_to_string(&mut s))?;
-    Ok(s.trim_end().to_string())
-}
-
-fn read_fixture(src: &str) -> Result<(Renderer, Snippet<'_>), Box<dyn Error>> {
-    Ok(toml::from_str(src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?)
-}
-
-#[test]
-#[cfg(not(windows))] // HACK: Not working on windows due to a serde error
-fn test_fixtures() {
-    for entry in glob("./tests/fixtures/no-color/**/*.toml").expect("Failed to read glob pattern") {
-        let p = entry.expect("Error while getting an entry");
-
-        let path_in = p.to_str().expect("Can't print path");
-        let path_out = path_in.replace(".toml", ".txt");
-
-        let src = read_file(path_in).expect("Failed to read file");
-        let (renderer, snippet) = read_fixture(&src).expect("Failed to read file");
-        let expected_out = read_file(&path_out).expect("Failed to read file");
-
-        let actual_out = renderer.render(snippet).to_string();
-        println!("{}", expected_out);
-        println!("{}", actual_out.trim_end());
-
-        assert_eq!(
-            expected_out,
-            actual_out.trim_end(),
-            "\n\n\nWhile parsing: {}\nThe diff is:\n\n\n{}\n\n\n",
-            path_in,
-            diff::get_diff(expected_out.as_str(), actual_out.as_str())
-        );
-    }
-}

From b65b8cabcd34da9fed88490a7a1cd8085777706a Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 8 Mar 2024 09:16:18 -0700
Subject: [PATCH 107/302] fix!: Take in byte spans

BREAKING CHANGE: This switches from char spans to byte spans
---
 Cargo.lock                   | 14 +++++++--
 Cargo.toml                   |  1 +
 src/renderer/display_list.rs | 57 ++++++++++++++++--------------------
 src/snippet.rs               |  1 +
 tests/formatter.rs           | 10 +++----
 5 files changed, 45 insertions(+), 38 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 034e18c..e507736 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -25,6 +25,7 @@ dependencies = [
  "criterion",
  "difference",
  "glob",
+ "itertools 0.12.1",
  "serde",
  "snapbox",
  "toml",
@@ -241,7 +242,7 @@ dependencies = [
  "clap",
  "criterion-plot",
  "is-terminal",
- "itertools",
+ "itertools 0.10.5",
  "num-traits",
  "once_cell",
  "oorandom",
@@ -262,7 +263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
 dependencies = [
  "cast",
- "itertools",
+ "itertools 0.10.5",
 ]
 
 [[package]]
@@ -435,6 +436,15 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "1.0.9"
diff --git a/Cargo.toml b/Cargo.toml
index 8763c11..0b19a89 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,7 @@ maintenance = { status = "actively-developed" }
 
 [dependencies]
 anstyle = "1.0.4"
+itertools = "0.12.1"
 unicode-width = "0.1.11"
 
 [dev-dependencies]
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index f283e52..d033946 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -32,6 +32,8 @@
 //!
 //! The above snippet has been built out of the following structure:
 use crate::snippet;
+use itertools::FoldWhile::{Continue, Done};
+use itertools::Itertools;
 use std::fmt::{Display, Write};
 use std::{cmp, fmt};
 
@@ -804,13 +806,27 @@ fn format_header<'a>(
 
         for item in body {
             if let DisplayLine::Source {
-                line: DisplaySourceLine::Content { range, .. },
+                line: DisplaySourceLine::Content { text, range },
                 lineno,
                 ..
             } = item
             {
                 if main_range >= range.0 && main_range <= range.1 {
-                    col = main_range - range.0 + 1;
+                    let char_column = text
+                        .chars()
+                        .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
+                        .chain(std::iter::once(1)) // treat the end of line as single-width
+                        .enumerate()
+                        .fold_while((0, 0), |(count, acc), (i, width)| {
+                            if acc <= main_range - range.0 {
+                                Continue((i, acc + width))
+                            } else {
+                                Done((count, acc))
+                            }
+                        })
+                        .into_inner()
+                        .0;
+                    col = char_column + 1;
                     line_offset = lineno.unwrap_or(1);
                     break;
                 }
@@ -932,7 +948,7 @@ fn format_body(
     has_footer: bool,
     margin: Option<Margin>,
 ) -> Vec<DisplayLine<'_>> {
-    let source_len = slice.source.chars().count();
+    let source_len = slice.source.len();
     if let Some(bigger) = slice.annotations.iter().find_map(|x| {
         // Allow highlighting one past the last character in the source.
         if source_len + 1 < x.range.1 {
@@ -955,18 +971,14 @@ fn format_body(
     struct LineInfo {
         line_start_index: usize,
         line_end_index: usize,
-        // How many spaces each character in the line take up when displayed
-        char_widths: Vec<usize>,
     }
 
     for (line, end_line) in CursorLines::new(slice.source) {
-        let line_length = line.chars().count();
-        let line_range = (current_index, current_index + line_length);
-        let char_widths = line
+        let line_length: usize = line
             .chars()
             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-            .chain(std::iter::once(1)) // treat the end of line as single-width
-            .collect::<Vec<_>>();
+            .sum();
+        let line_range = (current_index, current_index + line_length);
         body.push(DisplayLine::Source {
             lineno: Some(current_line),
             inline_marks: vec![],
@@ -978,7 +990,6 @@ fn format_body(
         line_info.push(LineInfo {
             line_start_index: line_range.0,
             line_end_index: line_range.1,
-            char_widths,
         });
         current_line += 1;
         current_index += line_length + end_line as usize;
@@ -991,7 +1002,6 @@ fn format_body(
         LineInfo {
             line_start_index,
             line_end_index,
-            char_widths,
         },
     ) in line_info.into_iter().enumerate()
     {
@@ -1012,16 +1022,8 @@ fn format_body(
                     if start >= line_start_index && end <= line_end_index
                         || start == line_end_index && end - start <= 1 =>
                 {
-                    let annotation_start_col = char_widths
-                        .iter()
-                        .take(start - line_start_index)
-                        .sum::<usize>()
-                        - margin_left;
-                    let annotation_end_col = char_widths
-                        .iter()
-                        .take(end - line_start_index)
-                        .sum::<usize>()
-                        - margin_left;
+                    let annotation_start_col = start - line_start_index - margin_left;
+                    let annotation_end_col = end - line_start_index - margin_left;
                     let range = (annotation_start_col, annotation_end_col);
                     body.insert(
                         body_idx + 1,
@@ -1064,10 +1066,7 @@ fn format_body(
                             });
                         }
                     } else {
-                        let annotation_start_col = char_widths
-                            .iter()
-                            .take(start - line_start_index)
-                            .sum::<usize>();
+                        let annotation_start_col = start - line_start_index;
                         let range = (annotation_start_col, annotation_start_col + 1);
                         body.insert(
                             body_idx + 1,
@@ -1125,11 +1124,7 @@ fn format_body(
                         });
                     }
 
-                    let end_mark = char_widths
-                        .iter()
-                        .take(end - line_start_index)
-                        .sum::<usize>()
-                        .saturating_sub(1);
+                    let end_mark = (end - line_start_index).saturating_sub(1);
                     let range = (end_mark - margin_left, (end_mark + 1) - margin_left);
                     body.insert(
                         body_idx + 1,
diff --git a/src/snippet.rs b/src/snippet.rs
index 02e70cc..f48eaba 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -70,6 +70,7 @@ pub enum AnnotationType {
 /// An annotation for a `Slice`.
 #[derive(Debug)]
 pub struct SourceAnnotation<'a> {
+    /// The byte range of the annotation in the `source` string
     pub range: (usize, usize),
     pub label: &'a str,
     pub annotation_type: AnnotationType,
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 97c7be3..2bee0b4 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -41,7 +41,7 @@ fn test_point_to_double_width_characters() {
             line_start: 1,
             origin: Some("<current file>"),
             annotations: vec![SourceAnnotation {
-                range: (6, 8),
+                range: (12, 16),
                 label: "world",
                 annotation_type: AnnotationType::Error,
             }],
@@ -69,7 +69,7 @@ fn test_point_to_double_width_characters_across_lines() {
             line_start: 1,
             origin: Some("<current file>"),
             annotations: vec![SourceAnnotation {
-                range: (2, 8),
+                range: (4, 15),
                 label: "Good morning",
                 annotation_type: AnnotationType::Error,
             }],
@@ -100,12 +100,12 @@ fn test_point_to_double_width_characters_multiple() {
             origin: Some("<current file>"),
             annotations: vec![
                 SourceAnnotation {
-                    range: (0, 3),
+                    range: (0, 6),
                     label: "Sushi1",
                     annotation_type: AnnotationType::Error,
                 },
                 SourceAnnotation {
-                    range: (6, 8),
+                    range: (11, 15),
                     label: "Sushi2",
                     annotation_type: AnnotationType::Note,
                 },
@@ -136,7 +136,7 @@ fn test_point_to_double_width_characters_mixed() {
             line_start: 1,
             origin: Some("<current file>"),
             annotations: vec![SourceAnnotation {
-                range: (6, 14),
+                range: (12, 23),
                 label: "New world",
                 annotation_type: AnnotationType::Error,
             }],

From c3bd0c3a63f983f5f2b4793a099972b1f6e97a9f Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 8 Mar 2024 13:35:01 -0700
Subject: [PATCH 108/302] fix!: Use explicit Range<usize>

BREAKING CHANGE: This changes (usize, usize) into Range<usize>
---
 benches/simple.rs             |  4 ++--
 examples/expected_type.rs     |  4 ++--
 examples/footer.rs            |  2 +-
 examples/format.rs            |  4 ++--
 src/renderer/display_list.rs  | 30 +++++++++++++++++-------------
 src/snippet.rs                |  4 +++-
 tests/fixtures/deserialize.rs |  3 ++-
 tests/formatter.rs            | 12 ++++++------
 8 files changed, 35 insertions(+), 28 deletions(-)

diff --git a/benches/simple.rs b/benches/simple.rs
index 3a40bbf..f6abcee 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -38,12 +38,12 @@ fn create_snippet(renderer: Renderer) {
                 SourceAnnotation {
                     label: "expected `Option<String>` because of return type",
                     annotation_type: AnnotationType::Warning,
-                    range: (5, 19),
+                    range: 5..19,
                 },
                 SourceAnnotation {
                     label: "expected enum `std::option::Option`",
                     annotation_type: AnnotationType::Error,
-                    range: (26, 724),
+                    range: 26..724,
                 },
             ],
         }],
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index bbd1fe6..613cf60 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -20,12 +20,12 @@ fn main() {
                 SourceAnnotation {
                     label: "",
                     annotation_type: AnnotationType::Error,
-                    range: (193, 195),
+                    range: 193..195,
                 },
                 SourceAnnotation {
                     label: "while parsing this struct",
                     annotation_type: AnnotationType::Info,
-                    range: (34, 50),
+                    range: 34..50,
                 },
             ],
         }],
diff --git a/examples/footer.rs b/examples/footer.rs
index ca02119..433aa83 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -21,7 +21,7 @@ fn main() {
             fold: false,
             annotations: vec![SourceAnnotation {
                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference",
-                range: (21, 24),
+                range: 21..24,
                 annotation_type: AnnotationType::Error,
             }],
         }],
diff --git a/examples/format.rs b/examples/format.rs
index 41f852e..a699f0a 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -32,12 +32,12 @@ fn main() {
                 SourceAnnotation {
                     label: "expected `Option<String>` because of return type",
                     annotation_type: AnnotationType::Warning,
-                    range: (5, 19),
+                    range: 5..19,
                 },
                 SourceAnnotation {
                     label: "expected enum `std::option::Option`",
                     annotation_type: AnnotationType::Error,
-                    range: (26, 724),
+                    range: 26..724,
                 },
             ],
         }],
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index d033946..f8241db 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -35,6 +35,7 @@ use crate::snippet;
 use itertools::FoldWhile::{Continue, Done};
 use itertools::Itertools;
 use std::fmt::{Display, Write};
+use std::ops::Range;
 use std::{cmp, fmt};
 
 use crate::renderer::{stylesheet::Stylesheet, Margin, Style};
@@ -768,7 +769,7 @@ fn format_slice(
     has_footer: bool,
     margin: Option<Margin>,
 ) -> Vec<DisplayLine<'_>> {
-    let main_range = slice.annotations.first().map(|x| x.range.0);
+    let main_range = slice.annotations.first().map(|x| x.range.start);
     let origin = slice.origin;
     let need_empty_header = origin.is_some() || is_first;
     let mut body = format_body(slice, need_empty_header, has_footer, margin);
@@ -951,8 +952,8 @@ fn format_body(
     let source_len = slice.source.len();
     if let Some(bigger) = slice.annotations.iter().find_map(|x| {
         // Allow highlighting one past the last character in the source.
-        if source_len + 1 < x.range.1 {
-            Some(x.range)
+        if source_len + 1 < x.range.end {
+            Some(&x.range)
         } else {
             None
         }
@@ -1017,8 +1018,8 @@ fn format_body(
                 _ => DisplayAnnotationType::from(annotation.annotation_type),
             };
             match annotation.range {
-                (start, _) if start > line_end_index => true,
-                (start, end)
+                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 =>
                 {
@@ -1047,7 +1048,7 @@ fn format_body(
                     annotation_line_count += 1;
                     false
                 }
-                (start, end)
+                Range { start, end }
                     if start >= line_start_index
                         && start <= line_end_index
                         && end > line_end_index =>
@@ -1091,7 +1092,7 @@ fn format_body(
                     }
                     true
                 }
-                (start, end) if start < line_start_index && end > line_end_index => {
+                Range { start, end } if start < line_start_index && end > line_end_index => {
                     if let DisplayLine::Source {
                         ref mut inline_marks,
                         ..
@@ -1106,7 +1107,7 @@ fn format_body(
                     }
                     true
                 }
-                (start, end)
+                Range { start, end }
                     if start < line_start_index
                         && end >= line_start_index
                         && end <= line_end_index =>
@@ -1375,7 +1376,7 @@ mod tests {
         let line_2 = "This is line 2";
         let source = [line_1, line_2].join("\n");
         // In line 2
-        let range = (22, 24);
+        let range = 22..24;
         let input = snippet::Snippet {
             title: None,
             footer: vec![],
@@ -1384,7 +1385,7 @@ mod tests {
                 line_start: 5402,
                 origin: None,
                 annotations: vec![snippet::SourceAnnotation {
-                    range,
+                    range: range.clone(),
                     label: "Test annotation",
                     annotation_type: snippet::AnnotationType::Info,
                 }],
@@ -1425,7 +1426,10 @@ mod tests {
                             style: DisplayTextStyle::Regular,
                         }],
                     },
-                    range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)),
+                    range: (
+                        range.start - (line_1.len() + 1),
+                        range.end - (line_1.len() + 1),
+                    ),
                     annotation_type: DisplayAnnotationType::Info,
                     annotation_part: DisplayAnnotationPart::Standalone,
                 },
@@ -1475,7 +1479,7 @@ mod tests {
             footer: vec![],
             slices: vec![snippet::Slice {
                 annotations: vec![snippet::SourceAnnotation {
-                    range: (0, source.len() + 2),
+                    range: 0..source.len() + 2,
                     label,
                     annotation_type: snippet::AnnotationType::Error,
                 }],
@@ -1502,7 +1506,7 @@ mod tests {
                 line_start: 1,
                 origin: Some("<current file>"),
                 annotations: vec![snippet::SourceAnnotation {
-                    range: (19, 23),
+                    range: 19..23,
                     label: "oops",
                     annotation_type: snippet::AnnotationType::Error,
                 }],
diff --git a/src/snippet.rs b/src/snippet.rs
index f48eaba..7f052f0 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -31,6 +31,8 @@
 //! };
 //! ```
 
+use std::ops::Range;
+
 /// Primary structure provided for formatting
 #[derive(Debug, Default)]
 pub struct Snippet<'a> {
@@ -71,7 +73,7 @@ pub enum AnnotationType {
 #[derive(Debug)]
 pub struct SourceAnnotation<'a> {
     /// The byte range of the annotation in the `source` string
-    pub range: (usize, usize),
+    pub range: Range<usize>,
     pub label: &'a str,
     pub annotation_type: AnnotationType,
 }
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 1763005..70e06ac 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -1,4 +1,5 @@
 use serde::{Deserialize, Deserializer, Serialize};
+use std::ops::Range;
 
 use annotate_snippets::{
     renderer::Margin, Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation,
@@ -122,7 +123,7 @@ where
 #[derive(Serialize, Deserialize)]
 #[serde(remote = "SourceAnnotation")]
 pub struct SourceAnnotationDef<'a> {
-    pub range: (usize, usize),
+    pub range: Range<usize>,
     #[serde(borrow)]
     pub label: &'a str,
     #[serde(with = "AnnotationTypeDef")]
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 2bee0b4..954204d 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -14,7 +14,7 @@ fn test_i_29() {
             line_start: 1,
             origin: Some("<current file>"),
             annotations: vec![SourceAnnotation {
-                range: (19, 23),
+                range: 19..23,
                 label: "oops",
                 annotation_type: AnnotationType::Error,
             }],
@@ -41,7 +41,7 @@ fn test_point_to_double_width_characters() {
             line_start: 1,
             origin: Some("<current file>"),
             annotations: vec![SourceAnnotation {
-                range: (12, 16),
+                range: 12..16,
                 label: "world",
                 annotation_type: AnnotationType::Error,
             }],
@@ -69,7 +69,7 @@ fn test_point_to_double_width_characters_across_lines() {
             line_start: 1,
             origin: Some("<current file>"),
             annotations: vec![SourceAnnotation {
-                range: (4, 15),
+                range: 4..15,
                 label: "Good morning",
                 annotation_type: AnnotationType::Error,
             }],
@@ -100,12 +100,12 @@ fn test_point_to_double_width_characters_multiple() {
             origin: Some("<current file>"),
             annotations: vec![
                 SourceAnnotation {
-                    range: (0, 6),
+                    range: 0..6,
                     label: "Sushi1",
                     annotation_type: AnnotationType::Error,
                 },
                 SourceAnnotation {
-                    range: (11, 15),
+                    range: 11..15,
                     label: "Sushi2",
                     annotation_type: AnnotationType::Note,
                 },
@@ -136,7 +136,7 @@ fn test_point_to_double_width_characters_mixed() {
             line_start: 1,
             origin: Some("<current file>"),
             annotations: vec![SourceAnnotation {
-                range: (12, 23),
+                range: 12..23,
                 label: "New world",
                 annotation_type: AnnotationType::Error,
             }],

From 5c566c0af4d4406b29265eed49dede25531602ef Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 9 Mar 2024 11:08:18 -0700
Subject: [PATCH 109/302] feat!: Move to builder pattern for snippet creation

BREAKING CHANGE: Snippets must be created using the builder pattern
---
 benches/simple.rs                             |  39 +---
 examples/expected_type.rs                     |  43 ++--
 examples/footer.rs                            |  42 ++--
 examples/format.rs                            |  39 +---
 examples/multislice.rs                        |  29 +--
 src/lib.rs                                    |   1 -
 src/renderer/display_list.rs                  | 217 ++++++++----------
 src/renderer/mod.rs                           |  29 +--
 src/snippet.rs                                | 194 ++++++++++++----
 tests/fixtures/deserialize.rs                 | 107 ++++++---
 tests/fixtures/no-color/issue_52.toml         |   1 +
 .../no-color/multiline_annotation.toml        |   6 +-
 .../no-color/multiline_annotation2.toml       |   5 +-
 .../no-color/multiline_annotation3.toml       |   5 +-
 .../no-color/multiple_annotations.svg         |  32 +--
 .../no-color/multiple_annotations.toml        |   4 +
 tests/fixtures/no-color/strip_line.toml       |   5 +-
 tests/fixtures/no-color/strip_line_char.toml  |   5 +-
 .../fixtures/no-color/strip_line_non_ws.toml  |   5 +-
 tests/formatter.rs                            | 127 +++-------
 20 files changed, 442 insertions(+), 493 deletions(-)

diff --git a/benches/simple.rs b/benches/simple.rs
index f6abcee..4eacfda 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -4,12 +4,10 @@ extern crate criterion;
 
 use criterion::{black_box, Criterion};
 
-use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
+use annotate_snippets::{Label, Renderer, Slice, Snippet};
 
 fn create_snippet(renderer: Renderer) {
-    let snippet = Snippet {
-        slices: vec![Slice {
-            source: r#") -> Option<String> {
+    let source = r#") -> Option<String> {
     for ann in annotations {
         match (ann.range.0, ann.range.1) {
             (None, None) => continue,
@@ -30,30 +28,15 @@ fn create_snippet(renderer: Renderer) {
             }
             _ => continue,
         }
-    }"#,
-            line_start: 51,
-            origin: Some("src/format.rs"),
-            fold: false,
-            annotations: vec![
-                SourceAnnotation {
-                    label: "expected `Option<String>` because of return type",
-                    annotation_type: AnnotationType::Warning,
-                    range: 5..19,
-                },
-                SourceAnnotation {
-                    label: "expected enum `std::option::Option`",
-                    annotation_type: AnnotationType::Error,
-                    range: 26..724,
-                },
-            ],
-        }],
-        title: Some(Annotation {
-            label: Some("mismatched types"),
-            id: Some("E0308"),
-            annotation_type: AnnotationType::Error,
-        }),
-        footer: vec![],
-    };
+    }"#;
+    let snippet = Snippet::error("mismatched types").id("E0308").slice(
+        Slice::new(source, 51)
+            .origin("src/format.rs")
+            .annotation(
+                Label::warning("expected `Option<String>` because of return type").span(5..19),
+            )
+            .annotation(Label::error("expected enum `std::option::Option`").span(26..724)),
+    );
 
     let _result = renderer.render(snippet).to_string();
 }
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 613cf60..99fb1bf 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,35 +1,22 @@
-use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
+use annotate_snippets::{Label, Renderer, Slice, Snippet};
 
 fn main() {
-    let snippet = Snippet {
-        title: Some(Annotation {
-            label: Some("expected type, found `22`"),
-            id: None,
-            annotation_type: AnnotationType::Error,
-        }),
-        footer: vec![],
-        slices: vec![Slice {
-            source: r#"                annotations: vec![SourceAnnotation {
+    let source = r#"                annotations: vec![SourceAnnotation {
                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
                     ,
-                range: <22, 25>,"#,
-            line_start: 26,
-            origin: Some("examples/footer.rs"),
-            fold: true,
-            annotations: vec![
-                SourceAnnotation {
-                    label: "",
-                    annotation_type: AnnotationType::Error,
-                    range: 193..195,
-                },
-                SourceAnnotation {
-                    label: "while parsing this struct",
-                    annotation_type: AnnotationType::Info,
-                    range: 34..50,
-                },
-            ],
-        }],
-    };
+                range: <22, 25>,"#;
+    let snippet = Snippet::error("expected type, found `22`").slice(
+        Slice::new(source, 26)
+            .origin("examples/footer.rs")
+            .fold(true)
+            .annotation(
+                Label::error(
+                    "expected struct `annotate_snippets::snippet::Slice`, found reference",
+                )
+                .span(193..195),
+            )
+            .annotation(Label::info("while parsing this struct").span(34..50)),
+    );
 
     let renderer = Renderer::plain();
     println!("{}", renderer.render(snippet));
diff --git a/examples/footer.rs b/examples/footer.rs
index 433aa83..d24b497 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,31 +1,21 @@
-use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
+use annotate_snippets::{Label, Renderer, Slice, Snippet};
 
 fn main() {
-    let snippet = Snippet {
-        title: Some(Annotation {
-            label: Some("mismatched types"),
-            id: Some("E0308"),
-            annotation_type: AnnotationType::Error,
-        }),
-        footer: vec![Annotation {
-            label: Some(
-                "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
-            ),
-            id: None,
-            annotation_type: AnnotationType::Note,
-        }],
-        slices: vec![Slice {
-            source: "        slices: vec![\"A\",",
-            line_start: 13,
-            origin: Some("src/multislice.rs"),
-            fold: false,
-            annotations: vec![SourceAnnotation {
-                label: "expected struct `annotate_snippets::snippet::Slice`, found reference",
-                range: 21..24,
-                annotation_type: AnnotationType::Error,
-            }],
-        }],
-    };
+    let snippet = Snippet::error("mismatched types")
+        .id("E0308")
+        .slice(
+            Slice::new("        slices: vec![\"A\",", 13)
+                .origin("src/multislice.rs")
+                .annotation(
+                    Label::error(
+                        "expected struct `annotate_snippets::snippet::Slice`, found reference",
+                    )
+                    .span(21..24),
+                ),
+        )
+        .footer(Label::note(
+            "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
+        ));
 
     let renderer = Renderer::plain();
     println!("{}", renderer.render(snippet));
diff --git a/examples/format.rs b/examples/format.rs
index a699f0a..5eb5a28 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,9 +1,7 @@
-use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
+use annotate_snippets::{Label, Renderer, Slice, Snippet};
 
 fn main() {
-    let snippet = Snippet {
-        slices: vec![Slice {
-            source: r#") -> Option<String> {
+    let source = r#") -> Option<String> {
     for ann in annotations {
         match (ann.range.0, ann.range.1) {
             (None, None) => continue,
@@ -24,30 +22,15 @@ fn main() {
             }
             _ => continue,
         }
-    }"#,
-            line_start: 51,
-            origin: Some("src/format.rs"),
-            fold: false,
-            annotations: vec![
-                SourceAnnotation {
-                    label: "expected `Option<String>` because of return type",
-                    annotation_type: AnnotationType::Warning,
-                    range: 5..19,
-                },
-                SourceAnnotation {
-                    label: "expected enum `std::option::Option`",
-                    annotation_type: AnnotationType::Error,
-                    range: 26..724,
-                },
-            ],
-        }],
-        title: Some(Annotation {
-            label: Some("mismatched types"),
-            id: Some("E0308"),
-            annotation_type: AnnotationType::Error,
-        }),
-        footer: vec![],
-    };
+    }"#;
+    let snippet = Snippet::error("mismatched types").id("E0308").slice(
+        Slice::new(source, 51)
+            .origin("src/format.rs")
+            .annotation(
+                Label::warning("expected `Option<String>` because of return type").span(5..19),
+            )
+            .annotation(Label::error("expected enum `std::option::Option`").span(26..724)),
+    );
 
     let renderer = Renderer::plain();
     println!("{}", renderer.render(snippet));
diff --git a/examples/multislice.rs b/examples/multislice.rs
index 63ebb65..f0de557 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,30 +1,9 @@
-use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet};
+use annotate_snippets::{Renderer, Slice, Snippet};
 
 fn main() {
-    let snippet = Snippet {
-        title: Some(Annotation {
-            label: Some("mismatched types"),
-            id: None,
-            annotation_type: AnnotationType::Error,
-        }),
-        footer: vec![],
-        slices: vec![
-            Slice {
-                source: "Foo",
-                line_start: 51,
-                origin: Some("src/format.rs"),
-                fold: false,
-                annotations: vec![],
-            },
-            Slice {
-                source: "Faa",
-                line_start: 129,
-                origin: Some("src/display.rs"),
-                fold: false,
-                annotations: vec![],
-            },
-        ],
-    };
+    let snippet = Snippet::error("mismatched types")
+        .slice(Slice::new("Foo", 51).origin("src/format.rs"))
+        .slice(Slice::new("Faa", 129).origin("src/display.rs"));
 
     let renderer = Renderer::plain();
     println!("{}", renderer.render(snippet));
diff --git a/src/lib.rs b/src/lib.rs
index 03ffbb6..2066e67 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,7 +48,6 @@
 //! ```text
 //! cargo add annotate-snippets --dev --feature testing-colors
 //! ```
-//!
 
 pub mod renderer;
 mod snippet;
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index f8241db..01484d4 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -108,6 +108,7 @@ impl<'a> DisplayList<'a> {
     pub(crate) fn new(
         snippet::Snippet {
             title,
+            id,
             footer,
             slices,
         }: snippet::Snippet<'a>,
@@ -116,9 +117,8 @@ impl<'a> DisplayList<'a> {
         margin: Option<Margin>,
     ) -> DisplayList<'a> {
         let mut body = vec![];
-        if let Some(annotation) = title {
-            body.push(format_title(annotation));
-        }
+
+        body.push(format_title(title, id));
 
         for (idx, slice) in slices.into_iter().enumerate() {
             body.append(&mut format_slice(
@@ -130,7 +130,7 @@ impl<'a> DisplayList<'a> {
         }
 
         for annotation in footer {
-            body.append(&mut format_annotation(annotation));
+            body.append(&mut format_footer(annotation));
         }
 
         Self {
@@ -733,26 +733,24 @@ fn format_label(
     result
 }
 
-fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> {
-    let label = annotation.label.unwrap_or_default();
+fn format_title<'a>(title: snippet::Label<'a>, id: Option<&'a str>) -> DisplayLine<'a> {
     DisplayLine::Raw(DisplayRawLine::Annotation {
         annotation: Annotation {
-            annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
-            id: annotation.id,
-            label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
+            annotation_type: DisplayAnnotationType::from(title.annotation_type),
+            id,
+            label: format_label(Some(title.label), Some(DisplayTextStyle::Emphasis)),
         },
         source_aligned: false,
         continuation: false,
     })
 }
 
-fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> {
+fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> {
     let mut result = vec![];
-    let label = annotation.label.unwrap_or_default();
-    for (i, line) in label.lines().enumerate() {
+    for (i, line) in footer.label.lines().enumerate() {
         result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
-                annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
+                annotation_type: DisplayAnnotationType::from(footer.annotation_type),
                 id: None,
                 label: format_label(Some(line), None),
             },
@@ -1222,15 +1220,7 @@ mod tests {
 
     #[test]
     fn test_format_title() {
-        let input = snippet::Snippet {
-            title: Some(snippet::Annotation {
-                id: Some("E0001"),
-                label: Some("This is a title"),
-                annotation_type: snippet::AnnotationType::Error,
-            }),
-            footer: vec![],
-            slices: vec![],
-        };
+        let input = snippet::Snippet::error("This is a title").id("E0001");
         let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
                 annotation_type: DisplayAnnotationType::Error,
@@ -1251,18 +1241,20 @@ mod tests {
         let line_1 = "This is line 1";
         let line_2 = "This is line 2";
         let source = [line_1, line_2].join("\n");
-        let input = snippet::Snippet {
-            title: None,
-            footer: vec![],
-            slices: vec![snippet::Slice {
-                source: &source,
-                line_start: 5402,
-                origin: None,
-                annotations: vec![],
-                fold: false,
-            }],
-        };
+        let input = snippet::Snippet::error("").slice(snippet::Slice::new(&source, 5402));
         let output = from_display_lines(vec![
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Error,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "",
+                        style: DisplayTextStyle::Emphasis,
+                    }],
+                },
+                source_aligned: false,
+                continuation: false,
+            }),
             DisplayLine::Source {
                 lineno: None,
                 inline_marks: vec![],
@@ -1299,27 +1291,22 @@ mod tests {
         let src_0_len = src_0.len();
         let src_1 = "This is slice 2";
         let src_1_len = src_1.len();
-        let input = snippet::Snippet {
-            title: None,
-            footer: vec![],
-            slices: vec![
-                snippet::Slice {
-                    source: src_0,
-                    line_start: 5402,
-                    origin: Some("file1.rs"),
-                    annotations: vec![],
-                    fold: false,
-                },
-                snippet::Slice {
-                    source: src_1,
-                    line_start: 2,
-                    origin: Some("file2.rs"),
-                    annotations: vec![],
-                    fold: false,
-                },
-            ],
-        };
+        let input = snippet::Snippet::error("")
+            .slice(snippet::Slice::new(src_0, 5402).origin("file1.rs"))
+            .slice(snippet::Slice::new(src_1, 2).origin("file2.rs"));
         let output = from_display_lines(vec![
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Error,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "",
+                        style: DisplayTextStyle::Emphasis,
+                    }],
+                },
+                source_aligned: false,
+                continuation: false,
+            }),
             DisplayLine::Raw(DisplayRawLine::Origin {
                 path: "file1.rs",
                 pos: None,
@@ -1377,22 +1364,23 @@ mod tests {
         let source = [line_1, line_2].join("\n");
         // In line 2
         let range = 22..24;
-        let input = snippet::Snippet {
-            title: None,
-            footer: vec![],
-            slices: vec![snippet::Slice {
-                source: &source,
-                line_start: 5402,
-                origin: None,
-                annotations: vec![snippet::SourceAnnotation {
-                    range: range.clone(),
-                    label: "Test annotation",
-                    annotation_type: snippet::AnnotationType::Info,
-                }],
-                fold: false,
-            }],
-        };
+        let input = snippet::Snippet::error("").slice(
+            snippet::Slice::new(&source, 5402)
+                .annotation(snippet::Label::info("Test annotation").span(range.clone())),
+        );
         let output = from_display_lines(vec![
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Error,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "",
+                        style: DisplayTextStyle::Emphasis,
+                    }],
+                },
+                source_aligned: false,
+                continuation: false,
+            }),
             DisplayLine::Source {
                 lineno: None,
                 inline_marks: vec![],
@@ -1445,27 +1433,34 @@ mod tests {
 
     #[test]
     fn test_format_label() {
-        let input = snippet::Snippet {
-            title: None,
-            footer: vec![snippet::Annotation {
-                id: None,
-                label: Some("This __is__ a title"),
-                annotation_type: snippet::AnnotationType::Error,
-            }],
-            slices: vec![],
-        };
-        let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::Error,
-                id: None,
-                label: vec![DisplayTextFragment {
-                    content: "This __is__ a title",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: true,
-            continuation: false,
-        })]);
+        let input =
+            snippet::Snippet::error("").footer(snippet::Label::error("This __is__ a title"));
+        let output = from_display_lines(vec![
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Error,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "",
+                        style: DisplayTextStyle::Emphasis,
+                    }],
+                },
+                source_aligned: false,
+                continuation: false,
+            }),
+            DisplayLine::Raw(DisplayRawLine::Annotation {
+                annotation: Annotation {
+                    annotation_type: DisplayAnnotationType::Error,
+                    id: None,
+                    label: vec![DisplayTextFragment {
+                        content: "This __is__ a title",
+                        style: DisplayTextStyle::Regular,
+                    }],
+                },
+                source_aligned: true,
+                continuation: false,
+            }),
+        ]);
         assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
     }
 
@@ -1474,45 +1469,21 @@ mod tests {
     fn test_i26() {
         let source = "short";
         let label = "label";
-        let input = snippet::Snippet {
-            title: None,
-            footer: vec![],
-            slices: vec![snippet::Slice {
-                annotations: vec![snippet::SourceAnnotation {
-                    range: 0..source.len() + 2,
-                    label,
-                    annotation_type: snippet::AnnotationType::Error,
-                }],
-                source,
-                line_start: 0,
-                origin: None,
-                fold: false,
-            }],
-        };
+        let input = snippet::Snippet::error("").slice(
+            snippet::Slice::new(source, 0)
+                .annotation(snippet::Label::error(label).span(0..source.len() + 2)),
+        );
         let _ = DisplayList::new(input, &STYLESHEET, false, None);
     }
 
     #[test]
     fn test_i_29() {
-        let snippets = snippet::Snippet {
-            title: Some(snippet::Annotation {
-                id: None,
-                label: Some("oops"),
-                annotation_type: snippet::AnnotationType::Error,
-            }),
-            footer: vec![],
-            slices: vec![snippet::Slice {
-                source: "First line\r\nSecond oops line",
-                line_start: 1,
-                origin: Some("<current file>"),
-                annotations: vec![snippet::SourceAnnotation {
-                    range: 19..23,
-                    label: "oops",
-                    annotation_type: snippet::AnnotationType::Error,
-                }],
-                fold: true,
-            }],
-        };
+        let snippets = snippet::Snippet::error("oops").slice(
+            snippet::Slice::new("First line\r\nSecond oops line", 1)
+                .origin("<current file>")
+                .fold(true)
+                .annotation(snippet::Label::error("oops").span(19..23)),
+        );
 
         let expected = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index b6108ce..7046407 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -2,31 +2,10 @@
 //!
 //! # Example
 //! ```
-//! use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet};
-//! let snippet = Snippet {
-//!     title: Some(Annotation {
-//!         label: Some("mismatched types"),
-//!         id: None,
-//!         annotation_type: AnnotationType::Error,
-//!     }),
-//!     footer: vec![],
-//!     slices: vec![
-//!         Slice {
-//!             source: "Foo",
-//!             line_start: 51,
-//!             origin: Some("src/format.rs"),
-//!             fold: false,
-//!             annotations: vec![],
-//!         },
-//!         Slice {
-//!             source: "Faa",
-//!             line_start: 129,
-//!             origin: Some("src/display.rs"),
-//!             fold: false,
-//!             annotations: vec![],
-//!         },
-//!     ],
-//!  };
+//! use annotate_snippets::{Renderer, Slice, Snippet};
+//! let snippet = Snippet::error("mismatched types")
+//!     .slice(Slice::new("Foo", 51).origin("src/format.rs"))
+//!     .slice(Slice::new("Faa", 129).origin("src/display.rs"));
 //!
 //!  let renderer = Renderer::styled();
 //!  println!("{}", renderer.render(snippet));
diff --git a/src/snippet.rs b/src/snippet.rs
index 7f052f0..e3a0bc0 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -5,40 +5,121 @@
 //! ```
 //! use annotate_snippets::*;
 //!
-//! Snippet {
-//!     title: Some(Annotation {
-//!         label: Some("mismatched types"),
-//!         id: None,
-//!         annotation_type: AnnotationType::Error,
-//!     }),
-//!     footer: vec![],
-//!     slices: vec![
-//!         Slice {
-//!             source: "Foo",
-//!             line_start: 51,
-//!             origin: Some("src/format.rs"),
-//!             fold: false,
-//!             annotations: vec![],
-//!         },
-//!         Slice {
-//!             source: "Faa",
-//!             line_start: 129,
-//!             origin: Some("src/display.rs"),
-//!             fold: false,
-//!             annotations: vec![],
-//!         },
-//!     ],
-//! };
+//! Snippet::error("mismatched types")
+//!     .slice(Slice::new("Foo", 51).origin("src/format.rs"))
+//!     .slice(Slice::new("Faa", 129).origin("src/display.rs"));
 //! ```
 
 use std::ops::Range;
 
 /// Primary structure provided for formatting
-#[derive(Debug, Default)]
 pub struct Snippet<'a> {
-    pub title: Option<Annotation<'a>>,
-    pub footer: Vec<Annotation<'a>>,
-    pub slices: Vec<Slice<'a>>,
+    pub(crate) title: Label<'a>,
+    pub(crate) id: Option<&'a str>,
+    pub(crate) slices: Vec<Slice<'a>>,
+    pub(crate) footer: Vec<Label<'a>>,
+}
+
+impl<'a> Snippet<'a> {
+    pub fn title(title: Label<'a>) -> Self {
+        Self {
+            title,
+            id: None,
+            slices: vec![],
+            footer: vec![],
+        }
+    }
+
+    pub fn error(title: &'a str) -> Self {
+        Self::title(Label::error(title))
+    }
+
+    pub fn warning(title: &'a str) -> Self {
+        Self::title(Label::warning(title))
+    }
+
+    pub fn info(title: &'a str) -> Self {
+        Self::title(Label::info(title))
+    }
+
+    pub fn note(title: &'a str) -> Self {
+        Self::title(Label::note(title))
+    }
+
+    pub fn help(title: &'a str) -> Self {
+        Self::title(Label::help(title))
+    }
+
+    pub fn id(mut self, id: &'a str) -> Self {
+        self.id = Some(id);
+        self
+    }
+
+    pub fn slice(mut self, slice: Slice<'a>) -> Self {
+        self.slices.push(slice);
+        self
+    }
+
+    pub fn footer(mut self, footer: Label<'a>) -> Self {
+        self.footer.push(footer);
+        self
+    }
+}
+
+pub struct Label<'a> {
+    pub(crate) annotation_type: AnnotationType,
+    pub(crate) label: &'a str,
+}
+
+impl<'a> Label<'a> {
+    pub fn new(annotation_type: AnnotationType, label: &'a str) -> Self {
+        Self {
+            annotation_type,
+            label,
+        }
+    }
+    pub fn error(label: &'a str) -> Self {
+        Self::new(AnnotationType::Error, label)
+    }
+
+    pub fn warning(label: &'a str) -> Self {
+        Self::new(AnnotationType::Warning, label)
+    }
+
+    pub fn info(label: &'a str) -> Self {
+        Self::new(AnnotationType::Info, label)
+    }
+
+    pub fn note(label: &'a str) -> Self {
+        Self::new(AnnotationType::Note, label)
+    }
+
+    pub fn help(label: &'a str) -> Self {
+        Self::new(AnnotationType::Help, label)
+    }
+
+    pub fn label(mut self, label: &'a str) -> Self {
+        self.label = label;
+        self
+    }
+
+    /// Create a [SourceAnnotation] with the given span for a [Slice]
+    pub fn span(&self, span: Range<usize>) -> SourceAnnotation<'a> {
+        SourceAnnotation {
+            range: span,
+            label: self.label,
+            annotation_type: self.annotation_type,
+        }
+    }
+}
+
+impl From<AnnotationType> for Label<'_> {
+    fn from(annotation_type: AnnotationType) -> Self {
+        Label {
+            annotation_type,
+            label: "",
+        }
+    }
 }
 
 /// Structure containing the slice of text to be annotated and
@@ -46,15 +127,39 @@ pub struct Snippet<'a> {
 ///
 /// One `Slice` is meant to represent a single, continuous,
 /// slice of source code that you want to annotate.
-#[derive(Debug)]
 pub struct Slice<'a> {
-    pub source: &'a str,
-    pub line_start: usize,
-    pub origin: Option<&'a str>,
-    pub annotations: Vec<SourceAnnotation<'a>>,
-    /// If set explicitly to `true`, the snippet will fold
-    /// parts of the slice that don't contain any annotations.
-    pub fold: bool,
+    pub(crate) source: &'a str,
+    pub(crate) line_start: usize,
+    pub(crate) origin: Option<&'a str>,
+    pub(crate) annotations: Vec<SourceAnnotation<'a>>,
+    pub(crate) fold: bool,
+}
+
+impl<'a> Slice<'a> {
+    pub fn new(source: &'a str, line_start: usize) -> Self {
+        Self {
+            source,
+            line_start,
+            origin: None,
+            annotations: vec![],
+            fold: false,
+        }
+    }
+
+    pub fn origin(mut self, origin: &'a str) -> Self {
+        self.origin = Some(origin);
+        self
+    }
+
+    pub fn annotation(mut self, annotation: SourceAnnotation<'a>) -> Self {
+        self.annotations.push(annotation);
+        self
+    }
+
+    pub fn fold(mut self, fold: bool) -> Self {
+        self.fold = fold;
+        self
+    }
 }
 
 /// Types of annotations.
@@ -70,19 +175,12 @@ pub enum AnnotationType {
 }
 
 /// An annotation for a `Slice`.
+///
+/// This gets created by [Label::span].
 #[derive(Debug)]
 pub struct SourceAnnotation<'a> {
     /// The byte range of the annotation in the `source` string
-    pub range: Range<usize>,
-    pub label: &'a str,
-    pub annotation_type: AnnotationType,
-}
-
-/// An annotation for a `Snippet`.
-#[derive(Debug)]
-pub struct Annotation<'a> {
-    /// Identifier of the annotation. Usually error code like "E0308".
-    pub id: Option<&'a str>,
-    pub label: Option<&'a str>,
-    pub annotation_type: AnnotationType,
+    pub(crate) range: Range<usize>,
+    pub(crate) label: &'a str,
+    pub(crate) annotation_type: AnnotationType,
 }
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 70e06ac..a01c343 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -2,7 +2,7 @@ use serde::{Deserialize, Deserializer, Serialize};
 use std::ops::Range;
 
 use annotate_snippets::{
-    renderer::Margin, Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation,
+    renderer::Margin, AnnotationType, Label, Renderer, Slice, Snippet, SourceAnnotation,
 };
 
 #[derive(Deserialize)]
@@ -15,14 +15,16 @@ pub struct Fixture<'a> {
 
 #[derive(Deserialize)]
 pub struct SnippetDef<'a> {
-    #[serde(deserialize_with = "deserialize_annotation")]
+    #[serde(deserialize_with = "deserialize_label")]
+    #[serde(borrow)]
+    pub title: Label<'a>,
     #[serde(default)]
     #[serde(borrow)]
-    pub title: Option<Annotation<'a>>,
-    #[serde(deserialize_with = "deserialize_annotations")]
+    pub id: Option<&'a str>,
+    #[serde(deserialize_with = "deserialize_labels")]
     #[serde(default)]
     #[serde(borrow)]
-    pub footer: Vec<Annotation<'a>>,
+    pub footer: Vec<Label<'a>>,
     #[serde(deserialize_with = "deserialize_slices")]
     #[serde(borrow)]
     pub slices: Vec<Slice<'a>>,
@@ -32,64 +34,72 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
     fn from(val: SnippetDef<'a>) -> Self {
         let SnippetDef {
             title,
+            id,
             footer,
             slices,
         } = val;
-        Snippet {
-            title,
-            footer,
-            slices,
+        let mut snippet = Snippet::title(title);
+        if let Some(id) = id {
+            snippet = snippet.id(id);
         }
+        snippet = slices
+            .into_iter()
+            .fold(snippet, |snippet, slice| snippet.slice(slice));
+        snippet = footer
+            .into_iter()
+            .fold(snippet, |snippet, label| snippet.footer(label));
+        snippet
     }
 }
 
-fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error>
+fn deserialize_label<'de, D>(deserializer: D) -> Result<Label<'de>, D::Error>
 where
     D: Deserializer<'de>,
 {
     #[derive(Deserialize)]
     struct Wrapper<'a>(
-        #[serde(with = "SliceDef")]
+        #[serde(with = "LabelDef")]
         #[serde(borrow)]
-        Slice<'a>,
+        LabelDef<'a>,
     );
 
-    let v = Vec::deserialize(deserializer)?;
-    Ok(v.into_iter().map(|Wrapper(a)| a).collect())
+    Wrapper::deserialize(deserializer)
+        .map(|Wrapper(label)| Label::new(label.annotation_type, label.label))
 }
 
-fn deserialize_annotation<'de, D>(deserializer: D) -> Result<Option<Annotation<'de>>, D::Error>
+fn deserialize_labels<'de, D>(deserializer: D) -> Result<Vec<Label<'de>>, D::Error>
 where
     D: Deserializer<'de>,
 {
     #[derive(Deserialize)]
     struct Wrapper<'a>(
-        #[serde(with = "AnnotationDef")]
+        #[serde(with = "LabelDef")]
         #[serde(borrow)]
-        Annotation<'a>,
+        LabelDef<'a>,
     );
 
-    Option::<Wrapper>::deserialize(deserializer)
-        .map(|opt_wrapped: Option<Wrapper>| opt_wrapped.map(|wrapped: Wrapper| wrapped.0))
+    let v = Vec::deserialize(deserializer)?;
+    Ok(v.into_iter()
+        .map(|Wrapper(a)| Label::new(a.annotation_type, a.label))
+        .collect())
 }
 
-fn deserialize_annotations<'de, D>(deserializer: D) -> Result<Vec<Annotation<'de>>, D::Error>
+fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error>
 where
     D: Deserializer<'de>,
 {
     #[derive(Deserialize)]
     struct Wrapper<'a>(
-        #[serde(with = "AnnotationDef")]
+        #[serde(with = "SliceDef")]
         #[serde(borrow)]
-        Annotation<'a>,
+        SliceDef<'a>,
     );
 
     let v = Vec::deserialize(deserializer)?;
-    Ok(v.into_iter().map(|Wrapper(a)| a).collect())
+    Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect())
 }
 
 #[derive(Deserialize)]
-#[serde(remote = "Slice")]
 pub struct SliceDef<'a> {
     #[serde(borrow)]
     pub source: &'a str,
@@ -103,6 +113,26 @@ pub struct SliceDef<'a> {
     pub fold: bool,
 }
 
+impl<'a> From<SliceDef<'a>> for Slice<'a> {
+    fn from(val: SliceDef<'a>) -> Self {
+        let SliceDef {
+            source,
+            line_start,
+            origin,
+            annotations,
+            fold,
+        } = val;
+        let mut slice = Slice::new(source, line_start).fold(fold);
+        if let Some(origin) = origin {
+            slice = slice.origin(origin)
+        }
+        slice = annotations
+            .into_iter()
+            .fold(slice, |slice, annotation| slice.annotation(annotation));
+        slice
+    }
+}
+
 fn deserialize_source_annotations<'de, D>(
     deserializer: D,
 ) -> Result<Vec<SourceAnnotation<'de>>, D::Error>
@@ -110,18 +140,13 @@ where
     D: Deserializer<'de>,
 {
     #[derive(Deserialize)]
-    struct Wrapper<'a>(
-        #[serde(with = "SourceAnnotationDef")]
-        #[serde(borrow)]
-        SourceAnnotation<'a>,
-    );
+    struct Wrapper<'a>(#[serde(borrow)] SourceAnnotationDef<'a>);
 
     let v = Vec::deserialize(deserializer)?;
-    Ok(v.into_iter().map(|Wrapper(a)| a).collect())
+    Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect())
 }
 
 #[derive(Serialize, Deserialize)]
-#[serde(remote = "SourceAnnotation")]
 pub struct SourceAnnotationDef<'a> {
     pub range: Range<usize>,
     #[serde(borrow)]
@@ -130,15 +155,23 @@ pub struct SourceAnnotationDef<'a> {
     pub annotation_type: AnnotationType,
 }
 
+impl<'a> From<SourceAnnotationDef<'a>> for SourceAnnotation<'a> {
+    fn from(val: SourceAnnotationDef<'a>) -> Self {
+        let SourceAnnotationDef {
+            range,
+            label,
+            annotation_type,
+        } = val;
+        Label::new(annotation_type, label).span(range)
+    }
+}
+
 #[derive(Serialize, Deserialize)]
-#[serde(remote = "Annotation")]
-pub struct AnnotationDef<'a> {
-    #[serde(borrow)]
-    pub id: Option<&'a str>,
-    #[serde(borrow)]
-    pub label: Option<&'a str>,
+pub struct LabelDef<'a> {
     #[serde(with = "AnnotationTypeDef")]
     pub annotation_type: AnnotationType,
+    #[serde(borrow)]
+    pub label: &'a str,
 }
 
 #[allow(dead_code)]
diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml
index d31e0cb..ea239dd 100644
--- a/tests/fixtures/no-color/issue_52.toml
+++ b/tests/fixtures/no-color/issue_52.toml
@@ -1,5 +1,6 @@
 [snippet.title]
 annotation_type = "Error"
+label = ""
 
 [[snippet.slices]]
 source = """
diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml
index 604e04b..48c0725 100644
--- a/tests/fixtures/no-color/multiline_annotation.toml
+++ b/tests/fixtures/no-color/multiline_annotation.toml
@@ -34,7 +34,7 @@ range = [5, 19]
 label = "expected enum `std::option::Option`, found ()"
 annotation_type = "Error"
 range = [22, 766]
-[snippet.title]
-label = "mismatched types"
+
+[snippet]
+title = { annotation_type = "Error", label = "mismatched types" }
 id = "E0308"
-annotation_type =  "Error"
diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml
index 3287fdc..89294bc 100644
--- a/tests/fixtures/no-color/multiline_annotation2.toml
+++ b/tests/fixtures/no-color/multiline_annotation2.toml
@@ -12,7 +12,6 @@ label = "missing fields `lineno`, `content`"
 annotation_type = "Error"
 range = [31, 128]
 
-[snippet.title]
-label = "pattern does not mention fields `lineno`, `content`"
+[snippet]
+title = { annotation_type = "Error", label = "pattern does not mention fields `lineno`, `content`" }
 id = "E0027"
-annotation_type = "Error"
diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml
index 9fe85fb..efc1f5f 100644
--- a/tests/fixtures/no-color/multiline_annotation3.toml
+++ b/tests/fixtures/no-color/multiline_annotation3.toml
@@ -12,7 +12,6 @@ label = "this should not be on separate lines"
 annotation_type = "Error"
 range = [11, 18]
 
-[snippet.title]
-label = "spacing error found"
+[snippet]
+title = { annotation_type = "Error", label = "spacing error found" }
 id = "E####"
-annotation_type = "Error"
diff --git a/tests/fixtures/no-color/multiple_annotations.svg b/tests/fixtures/no-color/multiple_annotations.svg
index 3f15144..18bca93 100644
--- a/tests/fixtures/no-color/multiple_annotations.svg
+++ b/tests/fixtures/no-color/multiple_annotations.svg
@@ -1,4 +1,4 @@
-<svg width="768px" height="272px" xmlns="http://www.w3.org/2000/svg">
+<svg width="768px" height="290px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -16,33 +16,35 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>    |</tspan>
+    <tspan x="10px" y="28px"><tspan>error</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> 96 | fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
+    <tspan x="10px" y="46px"><tspan>    |</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan> 97 |     if let Some(annotation) = main_annotation {</tspan>
+    <tspan x="10px" y="64px"><tspan> 96 | fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>    |                 ^^^^^^^^^^ Variable defined here</tspan>
+    <tspan x="10px" y="82px"><tspan> 97 |     if let Some(annotation) = main_annotation {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan> 98 |         result.push(format_title_line(</tspan>
+    <tspan x="10px" y="100px"><tspan>    |                 ^^^^^^^^^^ Variable defined here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan> 99 |             &amp;annotation.annotation_type,</tspan>
+    <tspan x="10px" y="118px"><tspan> 98 |         result.push(format_title_line(</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>    |              ^^^^^^^^^^ Referenced here</tspan>
+    <tspan x="10px" y="136px"><tspan> 99 |             &amp;annotation.annotation_type,</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>100 |             None,</tspan>
+    <tspan x="10px" y="154px"><tspan>    |              ^^^^^^^^^^ Referenced here</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>101 |             &amp;annotation.label,</tspan>
+    <tspan x="10px" y="172px"><tspan>100 |             None,</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan>    |              ^^^^^^^^^^ Referenced again here</tspan>
+    <tspan x="10px" y="190px"><tspan>101 |             &amp;annotation.label,</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>102 |         ));</tspan>
+    <tspan x="10px" y="208px"><tspan>    |              ^^^^^^^^^^ Referenced again here</tspan>
 </tspan>
-    <tspan x="10px" y="226px"><tspan>103 |     }</tspan>
+    <tspan x="10px" y="226px"><tspan>102 |         ));</tspan>
 </tspan>
-    <tspan x="10px" y="244px"><tspan>104 | }</tspan>
+    <tspan x="10px" y="244px"><tspan>103 |     }</tspan>
 </tspan>
-    <tspan x="10px" y="262px"><tspan>    |</tspan>
+    <tspan x="10px" y="262px"><tspan>104 | }</tspan>
+</tspan>
+    <tspan x="10px" y="280px"><tspan>    |</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml
index f037c9a..ae35cef 100644
--- a/tests/fixtures/no-color/multiple_annotations.toml
+++ b/tests/fixtures/no-color/multiple_annotations.toml
@@ -1,3 +1,7 @@
+[snippet.title]
+annotation_type = "Error"
+label = ""
+
 [[snippet.slices]]
 source = """
 fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {
diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml
index 4c609c4..9de50aa 100644
--- a/tests/fixtures/no-color/strip_line.toml
+++ b/tests/fixtures/no-color/strip_line.toml
@@ -1,7 +1,6 @@
-[snippet.title]
+[snippet]
+title = { annotation_type = "Error", label = "mismatched types" }
 id = "E0308"
-label = "mismatched types"
-annotation_type = "Error"
 
 [[snippet.slices]]
 source = "                                                                                                                                                                                    let _: () = 42;"
diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml
index 76ea749..72df2ab 100644
--- a/tests/fixtures/no-color/strip_line_char.toml
+++ b/tests/fixtures/no-color/strip_line_char.toml
@@ -1,7 +1,6 @@
-[snippet.title]
+[snippet]
+title = { annotation_type = "Error", label = "mismatched types" }
 id = "E0308"
-label = "mismatched types"
-annotation_type = "Error"
 
 [[snippet.slices]]
 source = "                                                                                                                                                                                    let _: () = 42ñ"
diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml
index c46d6e2..236d8f1 100644
--- a/tests/fixtures/no-color/strip_line_non_ws.toml
+++ b/tests/fixtures/no-color/strip_line_non_ws.toml
@@ -1,7 +1,6 @@
-[snippet.title]
+[snippet]
+title = { annotation_type = "Error", label = "mismatched types" }
 id = "E0308"
-label = "mismatched types"
-annotation_type = "Error"
 
 [[snippet.slices]]
 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 _: () = ();"
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 954204d..8f5c09f 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,26 +1,13 @@
-use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
+use annotate_snippets::{Label, Renderer, Slice, Snippet};
 
 #[test]
 fn test_i_29() {
-    let snippets = Snippet {
-        title: Some(Annotation {
-            id: None,
-            label: Some("oops"),
-            annotation_type: AnnotationType::Error,
-        }),
-        footer: vec![],
-        slices: vec![Slice {
-            source: "First line\r\nSecond oops line",
-            line_start: 1,
-            origin: Some("<current file>"),
-            annotations: vec![SourceAnnotation {
-                range: 19..23,
-                label: "oops",
-                annotation_type: AnnotationType::Error,
-            }],
-            fold: true,
-        }],
-    };
+    let snippets = Snippet::error("oops").slice(
+        Slice::new("First line\r\nSecond oops line", 1)
+            .origin("<current file>")
+            .annotation(Label::error("oops").span(19..23))
+            .fold(true),
+    );
     let expected = r#"error: oops
  --> <current file>:2:8
   |
@@ -35,23 +22,14 @@ fn test_i_29() {
 
 #[test]
 fn test_point_to_double_width_characters() {
-    let snippets = Snippet {
-        slices: vec![Slice {
-            source: "こんにちは、世界",
-            line_start: 1,
-            origin: Some("<current file>"),
-            annotations: vec![SourceAnnotation {
-                range: 12..16,
-                label: "world",
-                annotation_type: AnnotationType::Error,
-            }],
-            fold: false,
-        }],
-        title: None,
-        footer: vec![],
-    };
+    let snippets = Snippet::error("").slice(
+        Slice::new("こんにちは、世界", 1)
+            .origin("<current file>")
+            .annotation(Label::error("world").span(12..16)),
+    );
 
-    let expected = r#" --> <current file>:1:7
+    let expected = r#"error
+ --> <current file>:1:7
   |
 1 | こんにちは、世界
   |             ^^^^ world
@@ -63,23 +41,14 @@ fn test_point_to_double_width_characters() {
 
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
-    let snippets = Snippet {
-        slices: vec![Slice {
-            source: "おはよう\nございます",
-            line_start: 1,
-            origin: Some("<current file>"),
-            annotations: vec![SourceAnnotation {
-                range: 4..15,
-                label: "Good morning",
-                annotation_type: AnnotationType::Error,
-            }],
-            fold: false,
-        }],
-        title: None,
-        footer: vec![],
-    };
+    let snippets = Snippet::error("").slice(
+        Slice::new("おはよう\nございます", 1)
+            .origin("<current file>")
+            .annotation(Label::error("Good morning").span(4..15)),
+    );
 
-    let expected = r#" --> <current file>:1:3
+    let expected = r#"error
+ --> <current file>:1:3
   |
 1 |   おはよう
   |  _____^
@@ -93,30 +62,15 @@ fn test_point_to_double_width_characters_across_lines() {
 
 #[test]
 fn test_point_to_double_width_characters_multiple() {
-    let snippets = Snippet {
-        slices: vec![Slice {
-            source: "お寿司\n食べたい🍣",
-            line_start: 1,
-            origin: Some("<current file>"),
-            annotations: vec![
-                SourceAnnotation {
-                    range: 0..6,
-                    label: "Sushi1",
-                    annotation_type: AnnotationType::Error,
-                },
-                SourceAnnotation {
-                    range: 11..15,
-                    label: "Sushi2",
-                    annotation_type: AnnotationType::Note,
-                },
-            ],
-            fold: false,
-        }],
-        title: None,
-        footer: vec![],
-    };
+    let snippets = Snippet::error("").slice(
+        Slice::new("お寿司\n食べたい🍣", 1)
+            .origin("<current file>")
+            .annotation(Label::error("Sushi1").span(0..6))
+            .annotation(Label::note("Sushi2").span(11..15)),
+    );
 
-    let expected = r#" --> <current file>:1:1
+    let expected = r#"error
+ --> <current file>:1:1
   |
 1 | お寿司
   | ^^^^^^ Sushi1
@@ -130,23 +84,14 @@ fn test_point_to_double_width_characters_multiple() {
 
 #[test]
 fn test_point_to_double_width_characters_mixed() {
-    let snippets = Snippet {
-        slices: vec![Slice {
-            source: "こんにちは、新しいWorld!",
-            line_start: 1,
-            origin: Some("<current file>"),
-            annotations: vec![SourceAnnotation {
-                range: 12..23,
-                label: "New world",
-                annotation_type: AnnotationType::Error,
-            }],
-            fold: false,
-        }],
-        title: None,
-        footer: vec![],
-    };
+    let snippets = Snippet::error("").slice(
+        Slice::new("こんにちは、新しいWorld!", 1)
+            .origin("<current file>")
+            .annotation(Label::error("New world").span(12..23)),
+    );
 
-    let expected = r#" --> <current file>:1:7
+    let expected = r#"error
+ --> <current file>:1:7
   |
 1 | こんにちは、新しいWorld!
   |             ^^^^^^^^^^^ New world

From faddee328b98ea67fec5781debb15490a72d654b Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 11:32:41 -0500
Subject: [PATCH 110/302] docs: Improve cross-references

---
 src/snippet.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/snippet.rs b/src/snippet.rs
index e3a0bc0..37f431a 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -103,7 +103,7 @@ impl<'a> Label<'a> {
         self
     }
 
-    /// Create a [SourceAnnotation] with the given span for a [Slice]
+    /// Create a [`SourceAnnotation`] with the given span for a [`Slice`]
     pub fn span(&self, span: Range<usize>) -> SourceAnnotation<'a> {
         SourceAnnotation {
             range: span,
@@ -174,9 +174,9 @@ pub enum AnnotationType {
     Help,
 }
 
-/// An annotation for a `Slice`.
+/// An annotation for a [`Slice`].
 ///
-/// This gets created by [Label::span].
+/// This gets created by [`Label::span`].
 #[derive(Debug)]
 pub struct SourceAnnotation<'a> {
     /// The byte range of the annotation in the `source` string

From 2edd90c0709b1490a3cbc4d2a763b1f6e7eeba28 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 11 Mar 2024 18:16:51 +0000
Subject: [PATCH 111/302] chore(deps): update actions/setup-python action to v5

---
 .github/workflows/pre-commit.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 9a5c763..6eb6ca1 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -19,5 +19,5 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v4
-    - uses: actions/setup-python@v4
+    - uses: actions/setup-python@v5
     - uses: pre-commit/action@v3.0.1

From c8d8c65232cf988caa1a36d6cda2ee674548bc0b Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 14:33:17 -0500
Subject: [PATCH 112/302] test(examples): Verify output

---
 Cargo.lock                 | 37 +++++++++++++++++
 Cargo.toml                 |  3 +-
 examples/expected_type.rs  |  4 +-
 examples/expected_type.svg | 44 ++++++++++++++++++++
 examples/footer.rs         |  4 +-
 examples/footer.svg        | 43 +++++++++++++++++++
 examples/format.rs         |  4 +-
 examples/format.svg        | 85 ++++++++++++++++++++++++++++++++++++++
 examples/multislice.rs     |  4 +-
 examples/multislice.svg    | 44 ++++++++++++++++++++
 tests/examples.rs          | 37 +++++++++++++++++
 11 files changed, 300 insertions(+), 9 deletions(-)
 create mode 100644 examples/expected_type.svg
 create mode 100644 examples/footer.svg
 create mode 100644 examples/format.svg
 create mode 100644 examples/multislice.svg
 create mode 100644 tests/examples.rs

diff --git a/Cargo.lock b/Cargo.lock
index e507736..5672f61 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,6 +21,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 name = "annotate-snippets"
 version = "0.10.2"
 dependencies = [
+ "anstream",
  "anstyle",
  "criterion",
  "difference",
@@ -336,6 +337,18 @@ dependencies = [
  "rustversion",
 ]
 
+[[package]]
+name = "escargot"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f474c6844cbd04e783d0f25757583db4f491770ca618bedf2fb01815fc79939"
+dependencies = [
+ "log",
+ "once_cell",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "fastrand"
 version = "2.0.1"
@@ -542,6 +555,16 @@ version = "11.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
 
+[[package]]
+name = "os_pipe"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "plotters"
 version = "0.3.5"
@@ -734,15 +757,20 @@ dependencies = [
  "anstyle-svg",
  "content_inspector",
  "dunce",
+ "escargot",
  "filetime",
  "ignore",
+ "libc",
  "libtest-mimic",
  "normalize-line-endings",
+ "os_pipe",
  "serde_json",
  "similar",
  "snapbox-macros",
  "tempfile",
+ "wait-timeout",
  "walkdir",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -844,6 +872,15 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
+[[package]]
+name = "wait-timeout"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "walkdir"
 version = "2.4.0"
diff --git a/Cargo.toml b/Cargo.toml
index 0b19a89..5fbfc0b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,11 +29,12 @@ itertools = "0.12.1"
 unicode-width = "0.1.11"
 
 [dev-dependencies]
+anstream = "0.6.13"
 criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
 serde = { version = "1.0.197", features = ["derive"] }
-snapbox = { version = "0.5.8", features = ["diff", "harness", "path", "term-svg"] }
+snapbox = { version = "0.5.8", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] }
 toml = "0.5.11"
 
 [[bench]]
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 99fb1bf..adcbeff 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -18,6 +18,6 @@ fn main() {
             .annotation(Label::info("while parsing this struct").span(34..50)),
     );
 
-    let renderer = Renderer::plain();
-    println!("{}", renderer.render(snippet));
+    let renderer = Renderer::styled();
+    anstream::println!("{}", renderer.render(snippet));
 }
diff --git a/examples/expected_type.svg b/examples/expected_type.svg
new file mode 100644
index 0000000..ae06504
--- /dev/null
+++ b/examples/expected_type.svg
@@ -0,0 +1,44 @@
+<svg width="860px" height="200px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    .bold { font-weight: bold; }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected type, found `22`</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> examples/footer.rs:29:25</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">   |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan>                 annotations: vec![SourceAnnotation {</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-blue bold">                                   ----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info</tspan><tspan class="fg-bright-blue bold">: while parsing this struct</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>...</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-red bold">                         ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">   |</tspan>
+</tspan>
+    <tspan x="10px" y="190px">
+</tspan>
+  </text>
+
+</svg>
diff --git a/examples/footer.rs b/examples/footer.rs
index d24b497..6c45328 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -17,6 +17,6 @@ fn main() {
             "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
         ));
 
-    let renderer = Renderer::plain();
-    println!("{}", renderer.render(snippet));
+    let renderer = Renderer::styled();
+    anstream::println!("{}", renderer.render(snippet));
 }
diff --git a/examples/footer.svg b/examples/footer.svg
new file mode 100644
index 0000000..34f81c8
--- /dev/null
+++ b/examples/footer.svg
@@ -0,0 +1,43 @@
+<svg width="844px" height="182px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-green { fill: #55FF55 }
+    .fg-bright-red { fill: #FF5555 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    .bold { font-weight: bold; }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/multislice.rs:13:22</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">   |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13 |</tspan><tspan>         slices: vec!["A",</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-red bold">                      ^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">   |</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">=</tspan><tspan> </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>              found type: `__&amp;__snippet::Annotation`</tspan>
+</tspan>
+    <tspan x="10px" y="172px">
+</tspan>
+  </text>
+
+</svg>
diff --git a/examples/format.rs b/examples/format.rs
index 5eb5a28..1337812 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -32,6 +32,6 @@ fn main() {
             .annotation(Label::error("expected enum `std::option::Option`").span(26..724)),
     );
 
-    let renderer = Renderer::plain();
-    println!("{}", renderer.render(snippet));
+    let renderer = Renderer::styled();
+    anstream::println!("{}", renderer.render(snippet));
 }
diff --git a/examples/format.svg b/examples/format.svg
new file mode 100644
index 0000000..dd6c1c0
--- /dev/null
+++ b/examples/format.svg
@@ -0,0 +1,85 @@
+<svg width="740px" height="560px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
+    .fg-yellow { fill: #AA5500 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    .bold { font-weight: bold; }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs:51:6</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">   |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan>   ) -&gt; Option&lt;String&gt; {</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">   |</tspan><tspan>  </tspan><tspan class="fg-yellow bold">      --------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option&lt;String&gt;` because of return type</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan>       for ann in annotations {</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">   |</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">_____^</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">54 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (None, None) =&gt; continue,</tspan>
+</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">55 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt; end_index =&gt; continue,</tspan>
+</tspan>
+    <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">56 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt;= start_index =&gt; {</tspan>
+</tspan>
+    <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">57 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 let label = if let Some(ref label) = ann.label {</tspan>
+</tspan>
+    <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">58 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     format!(" {}", label)</tspan>
+</tspan>
+    <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">59 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 } else {</tspan>
+</tspan>
+    <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">60 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     String::from("")</tspan>
+</tspan>
+    <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 };</tspan>
+</tspan>
+    <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> </tspan>
+</tspan>
+    <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 return Some(format!(</tspan>
+</tspan>
+    <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">64 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "{}{}{}",</tspan>
+</tspan>
+    <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">65 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     " ".repeat(start - start_index),</tspan>
+</tspan>
+    <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">66 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "^".repeat(end - start),</tspan>
+</tspan>
+    <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">67 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     label</tspan>
+</tspan>
+    <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">68 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 ));</tspan>
+</tspan>
+    <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">69 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             }</tspan>
+</tspan>
+    <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">70 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             _ =&gt; continue,</tspan>
+</tspan>
+    <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         }</tspan>
+</tspan>
+    <tspan x="10px" y="496px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
+</tspan>
+    <tspan x="10px" y="514px"><tspan class="fg-bright-blue bold">   |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan class="fg-bright-red bold">____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan>
+</tspan>
+    <tspan x="10px" y="532px"><tspan class="fg-bright-blue bold">   |</tspan>
+</tspan>
+    <tspan x="10px" y="550px">
+</tspan>
+  </text>
+
+</svg>
diff --git a/examples/multislice.rs b/examples/multislice.rs
index f0de557..b6fcb3f 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -5,6 +5,6 @@ fn main() {
         .slice(Slice::new("Foo", 51).origin("src/format.rs"))
         .slice(Slice::new("Faa", 129).origin("src/display.rs"));
 
-    let renderer = Renderer::plain();
-    println!("{}", renderer.render(snippet));
+    let renderer = Renderer::styled();
+    anstream::println!("{}", renderer.render(snippet));
 }
diff --git a/examples/multislice.svg b/examples/multislice.svg
new file mode 100644
index 0000000..2ab959a
--- /dev/null
+++ b/examples/multislice.svg
@@ -0,0 +1,44 @@
+<svg width="740px" height="200px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    .bold { font-weight: bold; }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">    |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51 |</tspan><tspan> Foo</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">    |</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">    |</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">    |</tspan>
+</tspan>
+    <tspan x="10px" y="190px">
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/examples.rs b/tests/examples.rs
new file mode 100644
index 0000000..6025f5e
--- /dev/null
+++ b/tests/examples.rs
@@ -0,0 +1,37 @@
+#[test]
+fn expected_type() {
+    let target = "expected_type";
+    let expected = snapbox::file!["../examples/expected_type.svg": TermSvg];
+    assert_example(target, expected);
+}
+
+#[test]
+fn footer() {
+    let target = "footer";
+    let expected = snapbox::file!["../examples/footer.svg": TermSvg];
+    assert_example(target, expected);
+}
+
+#[test]
+fn format() {
+    let target = "format";
+    let expected = snapbox::file!["../examples/format.svg": TermSvg];
+    assert_example(target, expected);
+}
+
+#[test]
+fn multislice() {
+    let target = "multislice";
+    let expected = snapbox::file!["../examples/multislice.svg": TermSvg];
+    assert_example(target, expected);
+}
+
+#[track_caller]
+fn assert_example(target: &str, expected: snapbox::Data) {
+    let bin_path = snapbox::cmd::compile_example(target, ["--features=testing-colors"]).unwrap();
+    snapbox::cmd::Command::new(bin_path)
+        .env("CLICOLOR_FORCE", "1")
+        .assert()
+        .success()
+        .stdout_eq(expected);
+}

From 77050eafcfbadf42b5c8e09c892dadfc9e84753f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 14:39:11 -0500
Subject: [PATCH 113/302] docs: Shift example code from README to lib.rs

We now pull from an example
- We verify it builds (wasn't doing that with README)
- We show the output for the actual code (couldn't do that from a
  rustdoc code fence)
---
 README.md  | 65 ++++--------------------------------------------------
 src/lib.rs | 18 ++++-----------
 2 files changed, 8 insertions(+), 75 deletions(-)

diff --git a/README.md b/README.md
index f1b0352..21f95c4 100644
--- a/README.md
+++ b/README.md
@@ -3,73 +3,14 @@
 `annotate-snippets` is a Rust library for annotation of programming code slices.
 
 [![crates.io](https://img.shields.io/crates/v/annotate-snippets.svg)](https://crates.io/crates/annotate-snippets)
+[![documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation]
 ![build status](https://github.com/rust-lang/annotate-snippets-rs/actions/workflows/ci.yml/badge.svg)
 
 The library helps visualize meta information annotating source code slices.
 It takes a data structure called `Snippet` on the input and produces a `String`
 which may look like this:
 
-```text
-error[E0308]: mismatched types
-  --> src/format.rs:52:1
-   |
-51 |   ) -> Option<String> {
-   |        -------------- expected `Option<String>` because of return type
-52 | /     for ann in annotations {
-53 | |         match (ann.range.0, ann.range.1) {
-54 | |             (None, None) => continue,
-55 | |             (Some(start), Some(end)) if start > end_index => continue,
-...  |
-71 | |         }
-72 | |     }
-   | |_____^ expected enum `std::option::Option`, found ()
-```
-
-[Documentation][]
-
-[Documentation]: https://docs.rs/annotate-snippets/
-
-Usage
------
-
-```rust
-use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
-
-fn main() {
-    let snippet = Snippet {
-        title: Some(Annotation {
-            label: Some("expected type, found `22`"),
-            id: None,
-            annotation_type: AnnotationType::Error,
-        }),
-        footer: vec![],
-        slices: vec![Slice {
-            source: r#"                annotations: vec![SourceAnnotation {
-                label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
-                    ,
-                range: <22, 25>,"#,
-            line_start: 26,
-            origin: Some("examples/footer.rs"),
-            fold: true,
-            annotations: vec![
-                SourceAnnotation {
-                    label: "",
-                    annotation_type: AnnotationType::Error,
-                    range: (193, 195),
-                },
-                SourceAnnotation {
-                    label: "while parsing this struct",
-                    annotation_type: AnnotationType::Info,
-                    range: (34, 50),
-                },
-            ],
-        }],
-    };
-
-    let renderer = Renderer::plain();
-    println!("{}", renderer.render(snippet));
-}
-```
+![Screenshot](./examples/expected_type.svg)
 
 Local Development
 -----------------
@@ -80,3 +21,5 @@ Local Development
 When submitting a PR please use  [`cargo fmt`][] (nightly).
 
 [`cargo fmt`]: https://github.com/rust-lang/rustfmt
+
+[Documentation]: https://docs.rs/annotate-snippets/
diff --git a/src/lib.rs b/src/lib.rs
index 2066e67..8602c0a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,22 +7,12 @@
 //!
 //! # Example
 //!
-//! ```text
-//! error[E0308]: mismatched types
-//!   --> src/format.rs:52:1
-//!    |
-//! 51 |   ) -> Option<String> {
-//!    |        -------------- expected `Option<String>` because of return type
-//! 52 | /     for ann in annotations {
-//! 53 | |         match (ann.range.0, ann.range.1) {
-//! 54 | |             (None, None) => continue,
-//! 55 | |             (Some(start), Some(end)) if start > end_index => continue,
-//! ...  |
-//! 71 | |         }
-//! 72 | |     }
-//!    | |_____^ expected enum `std::option::Option`, found ()
+//! ```rust
+#![doc = include_str!("../examples/expected_type.rs")]
 //! ```
 //!
+#![doc = include_str!("../examples/expected_type.svg")]
+//!
 //! The crate uses a three stage process with two conversions between states:
 //!
 //! ```text

From 87bc645dcb2c2ccc07cb3b4af74c17103c9f618d Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 11:24:33 -0500
Subject: [PATCH 114/302] fix: Remove AnnotationType to Label conversion

I suggested this when a `Label`s title was an `Option`.
We shouldn't be creating labels with empty strings.
---
 src/snippet.rs | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/src/snippet.rs b/src/snippet.rs
index 37f431a..a254732 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -113,15 +113,6 @@ impl<'a> Label<'a> {
     }
 }
 
-impl From<AnnotationType> for Label<'_> {
-    fn from(annotation_type: AnnotationType) -> Self {
-        Label {
-            annotation_type,
-            label: "",
-        }
-    }
-}
-
 /// Structure containing the slice of text to be annotated and
 /// basic information about the location of the slice.
 ///

From b49f9471d920c7f561fa61970039b0ba44e448ac Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 14:12:18 -0500
Subject: [PATCH 115/302] fix!: Rename AnnotationType to Level

---
 src/renderer/display_list.rs                  | 54 +++++++------------
 src/snippet.rs                                | 25 ++++-----
 tests/fixtures/deserialize.rs                 | 23 ++++----
 tests/fixtures/no-color/issue_52.toml         |  4 +-
 tests/fixtures/no-color/issue_9.toml          |  8 +--
 .../no-color/multiline_annotation.toml        |  6 +--
 .../no-color/multiline_annotation2.toml       |  4 +-
 .../no-color/multiline_annotation3.toml       |  4 +-
 .../no-color/multiple_annotations.toml        |  8 +--
 tests/fixtures/no-color/one_past.toml         |  4 +-
 tests/fixtures/no-color/simple.toml           |  6 +--
 tests/fixtures/no-color/strip_line.toml       |  4 +-
 tests/fixtures/no-color/strip_line_char.toml  |  4 +-
 .../fixtures/no-color/strip_line_non_ws.toml  |  4 +-
 14 files changed, 70 insertions(+), 88 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 01484d4..f0bc537 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -649,14 +649,14 @@ pub enum DisplayAnnotationType {
     Help,
 }
 
-impl From<snippet::AnnotationType> for DisplayAnnotationType {
-    fn from(at: snippet::AnnotationType) -> Self {
+impl From<snippet::Level> for DisplayAnnotationType {
+    fn from(at: snippet::Level) -> Self {
         match at {
-            snippet::AnnotationType::Error => DisplayAnnotationType::Error,
-            snippet::AnnotationType::Warning => DisplayAnnotationType::Warning,
-            snippet::AnnotationType::Info => DisplayAnnotationType::Info,
-            snippet::AnnotationType::Note => DisplayAnnotationType::Note,
-            snippet::AnnotationType::Help => DisplayAnnotationType::Help,
+            snippet::Level::Error => DisplayAnnotationType::Error,
+            snippet::Level::Warning => DisplayAnnotationType::Warning,
+            snippet::Level::Info => DisplayAnnotationType::Info,
+            snippet::Level::Note => DisplayAnnotationType::Note,
+            snippet::Level::Help => DisplayAnnotationType::Help,
         }
     }
 }
@@ -736,7 +736,7 @@ fn format_label(
 fn format_title<'a>(title: snippet::Label<'a>, id: Option<&'a str>) -> DisplayLine<'a> {
     DisplayLine::Raw(DisplayRawLine::Annotation {
         annotation: Annotation {
-            annotation_type: DisplayAnnotationType::from(title.annotation_type),
+            annotation_type: DisplayAnnotationType::from(title.level),
             id,
             label: format_label(Some(title.label), Some(DisplayTextStyle::Emphasis)),
         },
@@ -750,7 +750,7 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> {
     for (i, line) in footer.label.lines().enumerate() {
         result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
-                annotation_type: DisplayAnnotationType::from(footer.annotation_type),
+                annotation_type: DisplayAnnotationType::from(footer.level),
                 id: None,
                 label: format_label(Some(line), None),
             },
@@ -1010,10 +1010,10 @@ fn format_body(
         // It would be nice to use filter_drain here once it's stable.
         annotations.retain(|annotation| {
             let body_idx = idx + annotation_line_count;
-            let annotation_type = match annotation.annotation_type {
-                snippet::AnnotationType::Error => DisplayAnnotationType::None,
-                snippet::AnnotationType::Warning => DisplayAnnotationType::None,
-                _ => DisplayAnnotationType::from(annotation.annotation_type),
+            let annotation_type = match annotation.level {
+                snippet::Level::Error => DisplayAnnotationType::None,
+                snippet::Level::Warning => DisplayAnnotationType::None,
+                _ => DisplayAnnotationType::from(annotation.level),
             };
             match annotation.range {
                 Range { start, .. } if start > line_end_index => true,
@@ -1036,9 +1036,7 @@ fn format_body(
                                     label: format_label(Some(annotation.label), None),
                                 },
                                 range,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
+                                annotation_type: DisplayAnnotationType::from(annotation.level),
                                 annotation_part: DisplayAnnotationPart::Standalone,
                             },
                         },
@@ -1059,9 +1057,7 @@ fn format_body(
                         {
                             inline_marks.push(DisplayMark {
                                 mark_type: DisplayMarkType::AnnotationStart,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
+                                annotation_type: DisplayAnnotationType::from(annotation.level),
                             });
                         }
                     } else {
@@ -1079,9 +1075,7 @@ fn format_body(
                                         label: vec![],
                                     },
                                     range,
-                                    annotation_type: DisplayAnnotationType::from(
-                                        annotation.annotation_type,
-                                    ),
+                                    annotation_type: DisplayAnnotationType::from(annotation.level),
                                     annotation_part: DisplayAnnotationPart::MultilineStart,
                                 },
                             },
@@ -1098,9 +1092,7 @@ fn format_body(
                     {
                         inline_marks.push(DisplayMark {
                             mark_type: DisplayMarkType::AnnotationThrough,
-                            annotation_type: DisplayAnnotationType::from(
-                                annotation.annotation_type,
-                            ),
+                            annotation_type: DisplayAnnotationType::from(annotation.level),
                         });
                     }
                     true
@@ -1117,9 +1109,7 @@ fn format_body(
                     {
                         inline_marks.push(DisplayMark {
                             mark_type: DisplayMarkType::AnnotationThrough,
-                            annotation_type: DisplayAnnotationType::from(
-                                annotation.annotation_type,
-                            ),
+                            annotation_type: DisplayAnnotationType::from(annotation.level),
                         });
                     }
 
@@ -1131,9 +1121,7 @@ fn format_body(
                             lineno: None,
                             inline_marks: vec![DisplayMark {
                                 mark_type: DisplayMarkType::AnnotationThrough,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
+                                annotation_type: DisplayAnnotationType::from(annotation.level),
                             }],
                             line: DisplaySourceLine::Annotation {
                                 annotation: Annotation {
@@ -1142,9 +1130,7 @@ fn format_body(
                                     label: format_label(Some(annotation.label), None),
                                 },
                                 range,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
+                                annotation_type: DisplayAnnotationType::from(annotation.level),
                                 annotation_part: DisplayAnnotationPart::MultilineEnd,
                             },
                         },
diff --git a/src/snippet.rs b/src/snippet.rs
index a254732..f8907b8 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -67,35 +67,32 @@ impl<'a> Snippet<'a> {
 }
 
 pub struct Label<'a> {
-    pub(crate) annotation_type: AnnotationType,
+    pub(crate) level: Level,
     pub(crate) label: &'a str,
 }
 
 impl<'a> Label<'a> {
-    pub fn new(annotation_type: AnnotationType, label: &'a str) -> Self {
-        Self {
-            annotation_type,
-            label,
-        }
+    pub fn new(level: Level, label: &'a str) -> Self {
+        Self { level, label }
     }
     pub fn error(label: &'a str) -> Self {
-        Self::new(AnnotationType::Error, label)
+        Self::new(Level::Error, label)
     }
 
     pub fn warning(label: &'a str) -> Self {
-        Self::new(AnnotationType::Warning, label)
+        Self::new(Level::Warning, label)
     }
 
     pub fn info(label: &'a str) -> Self {
-        Self::new(AnnotationType::Info, label)
+        Self::new(Level::Info, label)
     }
 
     pub fn note(label: &'a str) -> Self {
-        Self::new(AnnotationType::Note, label)
+        Self::new(Level::Note, label)
     }
 
     pub fn help(label: &'a str) -> Self {
-        Self::new(AnnotationType::Help, label)
+        Self::new(Level::Help, label)
     }
 
     pub fn label(mut self, label: &'a str) -> Self {
@@ -108,7 +105,7 @@ impl<'a> Label<'a> {
         SourceAnnotation {
             range: span,
             label: self.label,
-            annotation_type: self.annotation_type,
+            level: self.level,
         }
     }
 }
@@ -155,7 +152,7 @@ impl<'a> Slice<'a> {
 
 /// Types of annotations.
 #[derive(Debug, Clone, Copy, PartialEq)]
-pub enum AnnotationType {
+pub enum Level {
     /// Error annotations are displayed using red color and "^" character.
     Error,
     /// Warning annotations are displayed using blue color and "-" character.
@@ -173,5 +170,5 @@ pub struct SourceAnnotation<'a> {
     /// The byte range of the annotation in the `source` string
     pub(crate) range: Range<usize>,
     pub(crate) label: &'a str,
-    pub(crate) annotation_type: AnnotationType,
+    pub(crate) level: Level,
 }
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index a01c343..ebdf592 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -2,7 +2,7 @@ use serde::{Deserialize, Deserializer, Serialize};
 use std::ops::Range;
 
 use annotate_snippets::{
-    renderer::Margin, AnnotationType, Label, Renderer, Slice, Snippet, SourceAnnotation,
+    renderer::Margin, Label, Level, Renderer, Slice, Snippet, SourceAnnotation,
 };
 
 #[derive(Deserialize)]
@@ -63,8 +63,7 @@ where
         LabelDef<'a>,
     );
 
-    Wrapper::deserialize(deserializer)
-        .map(|Wrapper(label)| Label::new(label.annotation_type, label.label))
+    Wrapper::deserialize(deserializer).map(|Wrapper(label)| Label::new(label.level, label.label))
 }
 
 fn deserialize_labels<'de, D>(deserializer: D) -> Result<Vec<Label<'de>>, D::Error>
@@ -80,7 +79,7 @@ where
 
     let v = Vec::deserialize(deserializer)?;
     Ok(v.into_iter()
-        .map(|Wrapper(a)| Label::new(a.annotation_type, a.label))
+        .map(|Wrapper(a)| Label::new(a.level, a.label))
         .collect())
 }
 
@@ -151,8 +150,8 @@ pub struct SourceAnnotationDef<'a> {
     pub range: Range<usize>,
     #[serde(borrow)]
     pub label: &'a str,
-    #[serde(with = "AnnotationTypeDef")]
-    pub annotation_type: AnnotationType,
+    #[serde(with = "LevelDef")]
+    pub level: Level,
 }
 
 impl<'a> From<SourceAnnotationDef<'a>> for SourceAnnotation<'a> {
@@ -160,24 +159,24 @@ impl<'a> From<SourceAnnotationDef<'a>> for SourceAnnotation<'a> {
         let SourceAnnotationDef {
             range,
             label,
-            annotation_type,
+            level,
         } = val;
-        Label::new(annotation_type, label).span(range)
+        Label::new(level, label).span(range)
     }
 }
 
 #[derive(Serialize, Deserialize)]
 pub struct LabelDef<'a> {
-    #[serde(with = "AnnotationTypeDef")]
-    pub annotation_type: AnnotationType,
+    #[serde(with = "LevelDef")]
+    pub level: Level,
     #[serde(borrow)]
     pub label: &'a str,
 }
 
 #[allow(dead_code)]
 #[derive(Serialize, Deserialize)]
-#[serde(remote = "AnnotationType")]
-enum AnnotationTypeDef {
+#[serde(remote = "Level")]
+enum LevelDef {
     Error,
     Warning,
     Info,
diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml
index ea239dd..0c2378e 100644
--- a/tests/fixtures/no-color/issue_52.toml
+++ b/tests/fixtures/no-color/issue_52.toml
@@ -1,5 +1,5 @@
 [snippet.title]
-annotation_type = "Error"
+level = "Error"
 label = ""
 
 [[snippet.slices]]
@@ -13,5 +13,5 @@ origin = "path/to/error.rs"
 fold = true
 [[snippet.slices.annotations]]
 label = "error here"
-annotation_type = "Warning"
+level = "Warning"
 range = [2,16]
diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml
index ee1fe27..c6a1c45 100644
--- a/tests/fixtures/no-color/issue_9.toml
+++ b/tests/fixtures/no-color/issue_9.toml
@@ -1,6 +1,6 @@
 [snippet.title]
 label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
-annotation_type = "Error"
+level = "Error"
 
 [[snippet.slices]]
 source = "let x = vec![1];"
@@ -8,7 +8,7 @@ line_start = 4
 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs"
 [[snippet.slices.annotations]]
 label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait"
-annotation_type = "Warning"
+level = "Warning"
 range = [4, 5]
 
 [[snippet.slices]]
@@ -16,7 +16,7 @@ source = "let y = x;"
 line_start = 7
 [[snippet.slices.annotations]]
 label = "value moved here"
-annotation_type = "Warning"
+level = "Warning"
 range = [8, 9]
 
 [[snippet.slices]]
@@ -24,5 +24,5 @@ source = "x;"
 line_start = 9
 [[snippet.slices.annotations]]
 label = "value used here after move"
-annotation_type = "Error"
+level = "Error"
 range = [0, 1]
diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml
index 48c0725..64a3513 100644
--- a/tests/fixtures/no-color/multiline_annotation.toml
+++ b/tests/fixtures/no-color/multiline_annotation.toml
@@ -28,13 +28,13 @@ origin = "src/format.rs"
 fold = true
 [[snippet.slices.annotations]]
 label = "expected `std::option::Option<std::string::String>` because of return type"
-annotation_type = "Warning"
+level = "Warning"
 range = [5, 19]
 [[snippet.slices.annotations]]
 label = "expected enum `std::option::Option`, found ()"
-annotation_type = "Error"
+level = "Error"
 range = [22, 766]
 
 [snippet]
-title = { annotation_type = "Error", label = "mismatched types" }
+title = { level = "Error", label = "mismatched types" }
 id = "E0308"
diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml
index 89294bc..7c487ab 100644
--- a/tests/fixtures/no-color/multiline_annotation2.toml
+++ b/tests/fixtures/no-color/multiline_annotation2.toml
@@ -9,9 +9,9 @@ origin = "src/display_list.rs"
 fold = false
 [[snippet.slices.annotations]]
 label = "missing fields `lineno`, `content`"
-annotation_type = "Error"
+level = "Error"
 range = [31, 128]
 
 [snippet]
-title = { annotation_type = "Error", label = "pattern does not mention fields `lineno`, `content`" }
+title = { level = "Error", label = "pattern does not mention fields `lineno`, `content`" }
 id = "E0027"
diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml
index efc1f5f..3850eca 100644
--- a/tests/fixtures/no-color/multiline_annotation3.toml
+++ b/tests/fixtures/no-color/multiline_annotation3.toml
@@ -9,9 +9,9 @@ origin = "foo.txt"
 fold = false
 [[snippet.slices.annotations]]
 label = "this should not be on separate lines"
-annotation_type = "Error"
+level = "Error"
 range = [11, 18]
 
 [snippet]
-title = { annotation_type = "Error", label = "spacing error found" }
+title = { level = "Error", label = "spacing error found" }
 id = "E####"
diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml
index ae35cef..bbb2e28 100644
--- a/tests/fixtures/no-color/multiple_annotations.toml
+++ b/tests/fixtures/no-color/multiple_annotations.toml
@@ -1,5 +1,5 @@
 [snippet.title]
-annotation_type = "Error"
+level = "Error"
 label = ""
 
 [[snippet.slices]]
@@ -17,13 +17,13 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>
 line_start = 96
 [[snippet.slices.annotations]]
 label = "Variable defined here"
-annotation_type = "Error"
+level = "Error"
 range = [100, 110]
 [[snippet.slices.annotations]]
 label = "Referenced here"
-annotation_type = "Error"
+level = "Error"
 range = [184, 194]
 [[snippet.slices.annotations]]
 label = "Referenced again here"
-annotation_type = "Error"
+level = "Error"
 range = [243, 253]
diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml
index 62d1d42..d7eb041 100644
--- a/tests/fixtures/no-color/one_past.toml
+++ b/tests/fixtures/no-color/one_past.toml
@@ -1,6 +1,6 @@
 [snippet.title]
 label = "expected `.`, `=`"
-annotation_type = "Error"
+level = "Error"
 
 [[snippet.slices]]
 source = "asdf"
@@ -8,5 +8,5 @@ line_start = 1
 origin = "Cargo.toml"
 [[snippet.slices.annotations]]
 label = ""
-annotation_type = "Error"
+level = "Error"
 range = [4, 5]
diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml
index 2e9b6d8..0d592c1 100644
--- a/tests/fixtures/no-color/simple.toml
+++ b/tests/fixtures/no-color/simple.toml
@@ -7,12 +7,12 @@ line_start = 169
 origin = "src/format_color.rs"
 [[snippet.slices.annotations]]
 label = "unexpected token"
-annotation_type = "Error"
+level = "Error"
 range = [20, 23]
 [[snippet.slices.annotations]]
 label = "expected one of `.`, `;`, `?`, or an operator here"
-annotation_type = "Warning"
+level = "Warning"
 range = [10, 11]
 [snippet.title]
 label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
-annotation_type = "Error"
\ No newline at end of file
+level = "Error"
diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml
index 9de50aa..eedfcbb 100644
--- a/tests/fixtures/no-color/strip_line.toml
+++ b/tests/fixtures/no-color/strip_line.toml
@@ -1,5 +1,5 @@
 [snippet]
-title = { annotation_type = "Error", label = "mismatched types" }
+title = { level = "Error", label = "mismatched types" }
 id = "E0308"
 
 [[snippet.slices]]
@@ -9,7 +9,7 @@ origin = "$DIR/whitespace-trimming.rs"
 
 [[snippet.slices.annotations]]
 label = "expected (), found integer"
-annotation_type = "Error"
+level = "Error"
 range = [192, 194]
 
 [renderer]
diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml
index 72df2ab..dc51bb6 100644
--- a/tests/fixtures/no-color/strip_line_char.toml
+++ b/tests/fixtures/no-color/strip_line_char.toml
@@ -1,5 +1,5 @@
 [snippet]
-title = { annotation_type = "Error", label = "mismatched types" }
+title = { level = "Error", label = "mismatched types" }
 id = "E0308"
 
 [[snippet.slices]]
@@ -9,7 +9,7 @@ origin = "$DIR/whitespace-trimming.rs"
 
 [[snippet.slices.annotations]]
 label = "expected (), found integer"
-annotation_type = "Error"
+level = "Error"
 range = [192, 194]
 
 [renderer]
diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml
index 236d8f1..f56f55d 100644
--- a/tests/fixtures/no-color/strip_line_non_ws.toml
+++ b/tests/fixtures/no-color/strip_line_non_ws.toml
@@ -1,5 +1,5 @@
 [snippet]
-title = { annotation_type = "Error", label = "mismatched types" }
+title = { level = "Error", label = "mismatched types" }
 id = "E0308"
 
 [[snippet.slices]]
@@ -9,7 +9,7 @@ origin = "$DIR/non-whitespace-trimming.rs"
 
 [[snippet.slices.annotations]]
 label = "expected (), found integer"
-annotation_type = "Error"
+level = "Error"
 range = [240, 242]
 
 [renderer]

From bbf9c5fe27e83652433151cbfc7d6cafc02a8c47 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 14:13:31 -0500
Subject: [PATCH 116/302] fix!: Rename SourceAnnotation to Annotation

---
 src/snippet.rs                | 12 ++++++------
 tests/fixtures/deserialize.rs | 22 +++++++++-------------
 2 files changed, 15 insertions(+), 19 deletions(-)

diff --git a/src/snippet.rs b/src/snippet.rs
index f8907b8..dde5018 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -100,9 +100,9 @@ impl<'a> Label<'a> {
         self
     }
 
-    /// Create a [`SourceAnnotation`] with the given span for a [`Slice`]
-    pub fn span(&self, span: Range<usize>) -> SourceAnnotation<'a> {
-        SourceAnnotation {
+    /// Create a [`Annotation`] with the given span for a [`Slice`]
+    pub fn span(&self, span: Range<usize>) -> Annotation<'a> {
+        Annotation {
             range: span,
             label: self.label,
             level: self.level,
@@ -119,7 +119,7 @@ pub struct Slice<'a> {
     pub(crate) source: &'a str,
     pub(crate) line_start: usize,
     pub(crate) origin: Option<&'a str>,
-    pub(crate) annotations: Vec<SourceAnnotation<'a>>,
+    pub(crate) annotations: Vec<Annotation<'a>>,
     pub(crate) fold: bool,
 }
 
@@ -139,7 +139,7 @@ impl<'a> Slice<'a> {
         self
     }
 
-    pub fn annotation(mut self, annotation: SourceAnnotation<'a>) -> Self {
+    pub fn annotation(mut self, annotation: Annotation<'a>) -> Self {
         self.annotations.push(annotation);
         self
     }
@@ -166,7 +166,7 @@ pub enum Level {
 ///
 /// This gets created by [`Label::span`].
 #[derive(Debug)]
-pub struct SourceAnnotation<'a> {
+pub struct Annotation<'a> {
     /// The byte range of the annotation in the `source` string
     pub(crate) range: Range<usize>,
     pub(crate) label: &'a str,
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index ebdf592..bd94770 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -1,9 +1,7 @@
 use serde::{Deserialize, Deserializer, Serialize};
 use std::ops::Range;
 
-use annotate_snippets::{
-    renderer::Margin, Label, Level, Renderer, Slice, Snippet, SourceAnnotation,
-};
+use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Renderer, Slice, Snippet};
 
 #[derive(Deserialize)]
 pub struct Fixture<'a> {
@@ -105,9 +103,9 @@ pub struct SliceDef<'a> {
     pub line_start: usize,
     #[serde(borrow)]
     pub origin: Option<&'a str>,
-    #[serde(deserialize_with = "deserialize_source_annotations")]
+    #[serde(deserialize_with = "deserialize_annotations")]
     #[serde(borrow)]
-    pub annotations: Vec<SourceAnnotation<'a>>,
+    pub annotations: Vec<Annotation<'a>>,
     #[serde(default)]
     pub fold: bool,
 }
@@ -132,21 +130,19 @@ impl<'a> From<SliceDef<'a>> for Slice<'a> {
     }
 }
 
-fn deserialize_source_annotations<'de, D>(
-    deserializer: D,
-) -> Result<Vec<SourceAnnotation<'de>>, D::Error>
+fn deserialize_annotations<'de, D>(deserializer: D) -> Result<Vec<Annotation<'de>>, D::Error>
 where
     D: Deserializer<'de>,
 {
     #[derive(Deserialize)]
-    struct Wrapper<'a>(#[serde(borrow)] SourceAnnotationDef<'a>);
+    struct Wrapper<'a>(#[serde(borrow)] AnnotationDef<'a>);
 
     let v = Vec::deserialize(deserializer)?;
     Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect())
 }
 
 #[derive(Serialize, Deserialize)]
-pub struct SourceAnnotationDef<'a> {
+pub struct AnnotationDef<'a> {
     pub range: Range<usize>,
     #[serde(borrow)]
     pub label: &'a str,
@@ -154,9 +150,9 @@ pub struct SourceAnnotationDef<'a> {
     pub level: Level,
 }
 
-impl<'a> From<SourceAnnotationDef<'a>> for SourceAnnotation<'a> {
-    fn from(val: SourceAnnotationDef<'a>) -> Self {
-        let SourceAnnotationDef {
+impl<'a> From<AnnotationDef<'a>> for Annotation<'a> {
+    fn from(val: AnnotationDef<'a>) -> Self {
+        let AnnotationDef {
             range,
             label,
             level,

From 105da760b6e1bd4cfce4c642ac679ecf6011f511 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 15:13:58 -0500
Subject: [PATCH 117/302] fix!: Rename Snippet to Message

---
 benches/simple.rs                             |  6 ++---
 examples/expected_type.rs                     |  6 ++---
 examples/footer.rs                            |  6 ++---
 examples/format.rs                            |  6 ++---
 examples/multislice.rs                        |  6 ++---
 src/lib.rs                                    |  4 +--
 src/renderer/display_list.rs                  | 18 ++++++-------
 src/renderer/mod.rs                           | 14 +++++-----
 src/snippet.rs                                |  6 ++---
 tests/fixtures/deserialize.rs                 | 26 +++++++++----------
 tests/fixtures/main.rs                        |  8 +++---
 tests/fixtures/no-color/issue_52.toml         |  6 ++---
 tests/fixtures/no-color/issue_9.toml          | 14 +++++-----
 .../no-color/multiline_annotation.toml        |  8 +++---
 .../no-color/multiline_annotation2.toml       |  6 ++---
 .../no-color/multiline_annotation3.toml       |  6 ++---
 .../no-color/multiple_annotations.toml        | 10 +++----
 tests/fixtures/no-color/one_past.toml         |  6 ++---
 tests/fixtures/no-color/simple.toml           |  8 +++---
 tests/fixtures/no-color/strip_line.toml       |  6 ++---
 tests/fixtures/no-color/strip_line_char.toml  |  6 ++---
 .../fixtures/no-color/strip_line_non_ws.toml  |  6 ++---
 tests/formatter.rs                            | 12 ++++-----
 23 files changed, 100 insertions(+), 100 deletions(-)

diff --git a/benches/simple.rs b/benches/simple.rs
index 4eacfda..75a852f 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -4,7 +4,7 @@ extern crate criterion;
 
 use criterion::{black_box, Criterion};
 
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Label, Message, Renderer, Slice};
 
 fn create_snippet(renderer: Renderer) {
     let source = r#") -> Option<String> {
@@ -29,7 +29,7 @@ fn create_snippet(renderer: Renderer) {
             _ => continue,
         }
     }"#;
-    let snippet = Snippet::error("mismatched types").id("E0308").slice(
+    let message = Message::error("mismatched types").id("E0308").slice(
         Slice::new(source, 51)
             .origin("src/format.rs")
             .annotation(
@@ -38,7 +38,7 @@ fn create_snippet(renderer: Renderer) {
             .annotation(Label::error("expected enum `std::option::Option`").span(26..724)),
     );
 
-    let _result = renderer.render(snippet).to_string();
+    let _result = renderer.render(message).to_string();
 }
 
 pub fn criterion_benchmark(c: &mut Criterion) {
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index adcbeff..ccdb835 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,11 +1,11 @@
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Label, Message, Renderer, Slice};
 
 fn main() {
     let source = r#"                annotations: vec![SourceAnnotation {
                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
                     ,
                 range: <22, 25>,"#;
-    let snippet = Snippet::error("expected type, found `22`").slice(
+    let message = Message::error("expected type, found `22`").slice(
         Slice::new(source, 26)
             .origin("examples/footer.rs")
             .fold(true)
@@ -19,5 +19,5 @@ fn main() {
     );
 
     let renderer = Renderer::styled();
-    anstream::println!("{}", renderer.render(snippet));
+    anstream::println!("{}", renderer.render(message));
 }
diff --git a/examples/footer.rs b/examples/footer.rs
index 6c45328..924fae2 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,7 +1,7 @@
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Label, Message, Renderer, Slice};
 
 fn main() {
-    let snippet = Snippet::error("mismatched types")
+    let message = Message::error("mismatched types")
         .id("E0308")
         .slice(
             Slice::new("        slices: vec![\"A\",", 13)
@@ -18,5 +18,5 @@ fn main() {
         ));
 
     let renderer = Renderer::styled();
-    anstream::println!("{}", renderer.render(snippet));
+    anstream::println!("{}", renderer.render(message));
 }
diff --git a/examples/format.rs b/examples/format.rs
index 1337812..85e9a82 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Label, Message, Renderer, Slice};
 
 fn main() {
     let source = r#") -> Option<String> {
@@ -23,7 +23,7 @@ fn main() {
             _ => continue,
         }
     }"#;
-    let snippet = Snippet::error("mismatched types").id("E0308").slice(
+    let message = Message::error("mismatched types").id("E0308").slice(
         Slice::new(source, 51)
             .origin("src/format.rs")
             .annotation(
@@ -33,5 +33,5 @@ fn main() {
     );
 
     let renderer = Renderer::styled();
-    anstream::println!("{}", renderer.render(snippet));
+    anstream::println!("{}", renderer.render(message));
 }
diff --git a/examples/multislice.rs b/examples/multislice.rs
index b6fcb3f..48e7372 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,10 +1,10 @@
-use annotate_snippets::{Renderer, Slice, Snippet};
+use annotate_snippets::{Message, Renderer, Slice};
 
 fn main() {
-    let snippet = Snippet::error("mismatched types")
+    let message = Message::error("mismatched types")
         .slice(Slice::new("Foo", 51).origin("src/format.rs"))
         .slice(Slice::new("Faa", 129).origin("src/display.rs"));
 
     let renderer = Renderer::styled();
-    anstream::println!("{}", renderer.render(snippet));
+    anstream::println!("{}", renderer.render(message));
 }
diff --git a/src/lib.rs b/src/lib.rs
index 8602c0a..a2e4231 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,10 +16,10 @@
 //! The crate uses a three stage process with two conversions between states:
 //!
 //! ```text
-//! Snippet --> Renderer --> impl Display
+//! Message --> Renderer --> impl Display
 //! ```
 //!
-//! The input type - [Snippet] is a structure designed
+//! The input type - [Message] is a structure designed
 //! to align with likely output from any parser whose code snippet is to be
 //! annotated.
 //!
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index f0bc537..07216c3 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -106,12 +106,12 @@ impl<'a> DisplayList<'a> {
     const WARNING_TXT: &'static str = "warning";
 
     pub(crate) fn new(
-        snippet::Snippet {
+        snippet::Message {
             title,
             id,
             footer,
             slices,
-        }: snippet::Snippet<'a>,
+        }: snippet::Message<'a>,
         stylesheet: &'a Stylesheet,
         anonymized_line_numbers: bool,
         margin: Option<Margin>,
@@ -1206,7 +1206,7 @@ mod tests {
 
     #[test]
     fn test_format_title() {
-        let input = snippet::Snippet::error("This is a title").id("E0001");
+        let input = snippet::Message::error("This is a title").id("E0001");
         let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
                 annotation_type: DisplayAnnotationType::Error,
@@ -1227,7 +1227,7 @@ mod tests {
         let line_1 = "This is line 1";
         let line_2 = "This is line 2";
         let source = [line_1, line_2].join("\n");
-        let input = snippet::Snippet::error("").slice(snippet::Slice::new(&source, 5402));
+        let input = snippet::Message::error("").slice(snippet::Slice::new(&source, 5402));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1277,7 +1277,7 @@ mod tests {
         let src_0_len = src_0.len();
         let src_1 = "This is slice 2";
         let src_1_len = src_1.len();
-        let input = snippet::Snippet::error("")
+        let input = snippet::Message::error("")
             .slice(snippet::Slice::new(src_0, 5402).origin("file1.rs"))
             .slice(snippet::Slice::new(src_1, 2).origin("file2.rs"));
         let output = from_display_lines(vec![
@@ -1350,7 +1350,7 @@ mod tests {
         let source = [line_1, line_2].join("\n");
         // In line 2
         let range = 22..24;
-        let input = snippet::Snippet::error("").slice(
+        let input = snippet::Message::error("").slice(
             snippet::Slice::new(&source, 5402)
                 .annotation(snippet::Label::info("Test annotation").span(range.clone())),
         );
@@ -1420,7 +1420,7 @@ mod tests {
     #[test]
     fn test_format_label() {
         let input =
-            snippet::Snippet::error("").footer(snippet::Label::error("This __is__ a title"));
+            snippet::Message::error("").footer(snippet::Label::error("This __is__ a title"));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1455,7 +1455,7 @@ mod tests {
     fn test_i26() {
         let source = "short";
         let label = "label";
-        let input = snippet::Snippet::error("").slice(
+        let input = snippet::Message::error("").slice(
             snippet::Slice::new(source, 0)
                 .annotation(snippet::Label::error(label).span(0..source.len() + 2)),
         );
@@ -1464,7 +1464,7 @@ mod tests {
 
     #[test]
     fn test_i_29() {
-        let snippets = snippet::Snippet::error("oops").slice(
+        let snippets = snippet::Message::error("oops").slice(
             snippet::Slice::new("First line\r\nSecond oops line", 1)
                 .origin("<current file>")
                 .fold(true)
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 7046407..ef24cfd 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1,9 +1,9 @@
-//! The renderer for [`Snippet`]s
+//! The renderer for [`Message`]s
 //!
 //! # Example
 //! ```
-//! use annotate_snippets::{Renderer, Slice, Snippet};
-//! let snippet = Snippet::error("mismatched types")
+//! use annotate_snippets::{Renderer, Slice, Message};
+//! let snippet = Message::error("mismatched types")
 //!     .slice(Slice::new("Foo", 51).origin("src/format.rs"))
 //!     .slice(Slice::new("Faa", 129).origin("src/display.rs"));
 //!
@@ -14,14 +14,14 @@ mod display_list;
 mod margin;
 pub(crate) mod stylesheet;
 
-use crate::snippet::Snippet;
+use crate::snippet::Message;
 pub use anstyle::*;
 use display_list::DisplayList;
 pub use margin::Margin;
 use std::fmt::Display;
 use stylesheet::Stylesheet;
 
-/// A renderer for [`Snippet`]s
+/// A renderer for [`Message`]s
 #[derive(Clone)]
 pub struct Renderer {
     anonymized_line_numbers: bool,
@@ -165,9 +165,9 @@ impl Renderer {
     }
 
     /// Render a snippet into a `Display`able object
-    pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a {
+    pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a {
         DisplayList::new(
-            snippet,
+            msg,
             &self.stylesheet,
             self.anonymized_line_numbers,
             self.margin,
diff --git a/src/snippet.rs b/src/snippet.rs
index dde5018..61253ca 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -5,7 +5,7 @@
 //! ```
 //! use annotate_snippets::*;
 //!
-//! Snippet::error("mismatched types")
+//! Message::error("mismatched types")
 //!     .slice(Slice::new("Foo", 51).origin("src/format.rs"))
 //!     .slice(Slice::new("Faa", 129).origin("src/display.rs"));
 //! ```
@@ -13,14 +13,14 @@
 use std::ops::Range;
 
 /// Primary structure provided for formatting
-pub struct Snippet<'a> {
+pub struct Message<'a> {
     pub(crate) title: Label<'a>,
     pub(crate) id: Option<&'a str>,
     pub(crate) slices: Vec<Slice<'a>>,
     pub(crate) footer: Vec<Label<'a>>,
 }
 
-impl<'a> Snippet<'a> {
+impl<'a> Message<'a> {
     pub fn title(title: Label<'a>) -> Self {
         Self {
             title,
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index bd94770..6bee419 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -1,18 +1,18 @@
 use serde::{Deserialize, Deserializer, Serialize};
 use std::ops::Range;
 
-use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Renderer, Slice, Snippet};
+use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Slice};
 
 #[derive(Deserialize)]
 pub struct Fixture<'a> {
     #[serde(default)]
     pub renderer: RendererDef,
     #[serde(borrow)]
-    pub snippet: SnippetDef<'a>,
+    pub message: MessageDef<'a>,
 }
 
 #[derive(Deserialize)]
-pub struct SnippetDef<'a> {
+pub struct MessageDef<'a> {
     #[serde(deserialize_with = "deserialize_label")]
     #[serde(borrow)]
     pub title: Label<'a>,
@@ -28,25 +28,25 @@ pub struct SnippetDef<'a> {
     pub slices: Vec<Slice<'a>>,
 }
 
-impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
-    fn from(val: SnippetDef<'a>) -> Self {
-        let SnippetDef {
+impl<'a> From<MessageDef<'a>> for Message<'a> {
+    fn from(val: MessageDef<'a>) -> Self {
+        let MessageDef {
             title,
             id,
             footer,
             slices,
         } = val;
-        let mut snippet = Snippet::title(title);
+        let mut message = Message::title(title);
         if let Some(id) = id {
-            snippet = snippet.id(id);
+            message = message.id(id);
         }
-        snippet = slices
+        message = slices
             .into_iter()
-            .fold(snippet, |snippet, slice| snippet.slice(slice));
-        snippet = footer
+            .fold(message, |message, slice| message.slice(slice));
+        message = footer
             .into_iter()
-            .fold(snippet, |snippet, label| snippet.footer(label));
-        snippet
+            .fold(message, |message, label| message.footer(label));
+        message
     }
 }
 
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index c320407..841b363 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -1,7 +1,7 @@
 mod deserialize;
 
 use crate::deserialize::Fixture;
-use annotate_snippets::{Renderer, Snippet};
+use annotate_snippets::{Message, Renderer};
 use snapbox::data::DataFormat;
 use snapbox::Data;
 use std::error::Error;
@@ -26,8 +26,8 @@ fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case {
 
 fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> {
     let src = std::fs::read_to_string(input_path)?;
-    let (renderer, snippet): (Renderer, Snippet<'_>) =
-        toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?;
-    let actual = renderer.render(snippet).to_string();
+    let (renderer, message): (Renderer, Message<'_>) =
+        toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.message.into()))?;
+    let actual = renderer.render(message).to_string();
     Ok(Data::from(actual).coerce_to(DataFormat::TermSvg))
 }
diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml
index 0c2378e..749324e 100644
--- a/tests/fixtures/no-color/issue_52.toml
+++ b/tests/fixtures/no-color/issue_52.toml
@@ -1,8 +1,8 @@
-[snippet.title]
+[message.title]
 level = "Error"
 label = ""
 
-[[snippet.slices]]
+[[message.slices]]
 source = """
 
 
@@ -11,7 +11,7 @@ invalid syntax
 line_start = 1
 origin = "path/to/error.rs"
 fold = true
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "error here"
 level = "Warning"
 range = [2,16]
diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml
index c6a1c45..e534505 100644
--- a/tests/fixtures/no-color/issue_9.toml
+++ b/tests/fixtures/no-color/issue_9.toml
@@ -1,28 +1,28 @@
-[snippet.title]
+[message.title]
 label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 level = "Error"
 
-[[snippet.slices]]
+[[message.slices]]
 source = "let x = vec![1];"
 line_start = 4
 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs"
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait"
 level = "Warning"
 range = [4, 5]
 
-[[snippet.slices]]
+[[message.slices]]
 source = "let y = x;"
 line_start = 7
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "value moved here"
 level = "Warning"
 range = [8, 9]
 
-[[snippet.slices]]
+[[message.slices]]
 source = "x;"
 line_start = 9
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "value used here after move"
 level = "Error"
 range = [0, 1]
diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml
index 64a3513..55b0513 100644
--- a/tests/fixtures/no-color/multiline_annotation.toml
+++ b/tests/fixtures/no-color/multiline_annotation.toml
@@ -1,4 +1,4 @@
-[[snippet.slices]]
+[[message.slices]]
 source = """
 ) -> Option<String> {
     for ann in annotations {
@@ -26,15 +26,15 @@ source = """
 line_start = 51
 origin = "src/format.rs"
 fold = true
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "expected `std::option::Option<std::string::String>` because of return type"
 level = "Warning"
 range = [5, 19]
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "expected enum `std::option::Option`, found ()"
 level = "Error"
 range = [22, 766]
 
-[snippet]
+[message]
 title = { level = "Error", label = "mismatched types" }
 id = "E0308"
diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml
index 7c487ab..b1506ef 100644
--- a/tests/fixtures/no-color/multiline_annotation2.toml
+++ b/tests/fixtures/no-color/multiline_annotation2.toml
@@ -1,4 +1,4 @@
-[[snippet.slices]]
+[[message.slices]]
 source = """
                         if let DisplayLine::Source {
                             ref mut inline_marks,
@@ -7,11 +7,11 @@ source = """
 line_start = 139
 origin = "src/display_list.rs"
 fold = false
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "missing fields `lineno`, `content`"
 level = "Error"
 range = [31, 128]
 
-[snippet]
+[message]
 title = { level = "Error", label = "pattern does not mention fields `lineno`, `content`" }
 id = "E0027"
diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml
index 3850eca..a5f0400 100644
--- a/tests/fixtures/no-color/multiline_annotation3.toml
+++ b/tests/fixtures/no-color/multiline_annotation3.toml
@@ -1,4 +1,4 @@
-[[snippet.slices]]
+[[message.slices]]
 source = """
 This is an exampl
 e of an edge case of an annotation overflowing
@@ -7,11 +7,11 @@ to exactly one character on next line.
 line_start = 26
 origin = "foo.txt"
 fold = false
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "this should not be on separate lines"
 level = "Error"
 range = [11, 18]
 
-[snippet]
+[message]
 title = { level = "Error", label = "spacing error found" }
 id = "E####"
diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml
index bbb2e28..1c97a27 100644
--- a/tests/fixtures/no-color/multiple_annotations.toml
+++ b/tests/fixtures/no-color/multiple_annotations.toml
@@ -1,8 +1,8 @@
-[snippet.title]
+[message.title]
 level = "Error"
 label = ""
 
-[[snippet.slices]]
+[[message.slices]]
 source = """
 fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {
     if let Some(annotation) = main_annotation {
@@ -15,15 +15,15 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>
 }
 """
 line_start = 96
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "Variable defined here"
 level = "Error"
 range = [100, 110]
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "Referenced here"
 level = "Error"
 range = [184, 194]
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "Referenced again here"
 level = "Error"
 range = [243, 253]
diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml
index d7eb041..6dbee4b 100644
--- a/tests/fixtures/no-color/one_past.toml
+++ b/tests/fixtures/no-color/one_past.toml
@@ -1,12 +1,12 @@
-[snippet.title]
+[message.title]
 label = "expected `.`, `=`"
 level = "Error"
 
-[[snippet.slices]]
+[[message.slices]]
 source = "asdf"
 line_start = 1
 origin = "Cargo.toml"
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = ""
 level = "Error"
 range = [4, 5]
diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml
index 0d592c1..c511ef9 100644
--- a/tests/fixtures/no-color/simple.toml
+++ b/tests/fixtures/no-color/simple.toml
@@ -1,18 +1,18 @@
-[[snippet.slices]]
+[[message.slices]]
 source = """
         })
 
         for line in &self.body {"""
 line_start = 169
 origin = "src/format_color.rs"
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "unexpected token"
 level = "Error"
 range = [20, 23]
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "expected one of `.`, `;`, `?`, or an operator here"
 level = "Warning"
 range = [10, 11]
-[snippet.title]
+[message.title]
 label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 level = "Error"
diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml
index eedfcbb..0260125 100644
--- a/tests/fixtures/no-color/strip_line.toml
+++ b/tests/fixtures/no-color/strip_line.toml
@@ -1,13 +1,13 @@
-[snippet]
+[message]
 title = { level = "Error", label = "mismatched types" }
 id = "E0308"
 
-[[snippet.slices]]
+[[message.slices]]
 source = "                                                                                                                                                                                    let _: () = 42;"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "expected (), found integer"
 level = "Error"
 range = [192, 194]
diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml
index dc51bb6..70ee2e3 100644
--- a/tests/fixtures/no-color/strip_line_char.toml
+++ b/tests/fixtures/no-color/strip_line_char.toml
@@ -1,13 +1,13 @@
-[snippet]
+[message]
 title = { level = "Error", label = "mismatched types" }
 id = "E0308"
 
-[[snippet.slices]]
+[[message.slices]]
 source = "                                                                                                                                                                                    let _: () = 42ñ"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "expected (), found integer"
 level = "Error"
 range = [192, 194]
diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml
index f56f55d..e133bbc 100644
--- a/tests/fixtures/no-color/strip_line_non_ws.toml
+++ b/tests/fixtures/no-color/strip_line_non_ws.toml
@@ -1,13 +1,13 @@
-[snippet]
+[message]
 title = { level = "Error", label = "mismatched types" }
 id = "E0308"
 
-[[snippet.slices]]
+[[message.slices]]
 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"
 
-[[snippet.slices.annotations]]
+[[message.slices.annotations]]
 label = "expected (), found integer"
 level = "Error"
 range = [240, 242]
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 8f5c09f..4b90366 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,8 +1,8 @@
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Label, Message, Renderer, Slice};
 
 #[test]
 fn test_i_29() {
-    let snippets = Snippet::error("oops").slice(
+    let snippets = Message::error("oops").slice(
         Slice::new("First line\r\nSecond oops line", 1)
             .origin("<current file>")
             .annotation(Label::error("oops").span(19..23))
@@ -22,7 +22,7 @@ fn test_i_29() {
 
 #[test]
 fn test_point_to_double_width_characters() {
-    let snippets = Snippet::error("").slice(
+    let snippets = Message::error("").slice(
         Slice::new("こんにちは、世界", 1)
             .origin("<current file>")
             .annotation(Label::error("world").span(12..16)),
@@ -41,7 +41,7 @@ fn test_point_to_double_width_characters() {
 
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
-    let snippets = Snippet::error("").slice(
+    let snippets = Message::error("").slice(
         Slice::new("おはよう\nございます", 1)
             .origin("<current file>")
             .annotation(Label::error("Good morning").span(4..15)),
@@ -62,7 +62,7 @@ fn test_point_to_double_width_characters_across_lines() {
 
 #[test]
 fn test_point_to_double_width_characters_multiple() {
-    let snippets = Snippet::error("").slice(
+    let snippets = Message::error("").slice(
         Slice::new("お寿司\n食べたい🍣", 1)
             .origin("<current file>")
             .annotation(Label::error("Sushi1").span(0..6))
@@ -84,7 +84,7 @@ fn test_point_to_double_width_characters_multiple() {
 
 #[test]
 fn test_point_to_double_width_characters_mixed() {
-    let snippets = Snippet::error("").slice(
+    let snippets = Message::error("").slice(
         Slice::new("こんにちは、新しいWorld!", 1)
             .origin("<current file>")
             .annotation(Label::error("New world").span(12..23)),

From 1c18950300cf8b93d92d89e9797ed0bae02c0a37 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 15:18:05 -0500
Subject: [PATCH 118/302] fix!: Rename Slice to Snippet

---
 benches/simple.rs                             |  6 +--
 examples/expected_type.rs                     |  6 +--
 examples/footer.rs                            |  6 +--
 examples/format.rs                            |  6 +--
 examples/multislice.rs                        |  6 +--
 src/renderer/display_list.rs                  | 48 +++++++++----------
 src/renderer/mod.rs                           |  6 +--
 src/snippet.rs                                | 22 ++++-----
 tests/fixtures/deserialize.rs                 | 38 ++++++++-------
 tests/fixtures/no-color/issue_52.toml         |  4 +-
 tests/fixtures/no-color/issue_9.toml          | 12 ++---
 .../no-color/multiline_annotation.toml        |  6 +--
 .../no-color/multiline_annotation2.toml       |  4 +-
 .../no-color/multiline_annotation3.toml       |  4 +-
 .../no-color/multiple_annotations.toml        |  8 ++--
 tests/fixtures/no-color/one_past.toml         |  4 +-
 tests/fixtures/no-color/simple.toml           |  6 +--
 tests/fixtures/no-color/strip_line.toml       |  4 +-
 tests/fixtures/no-color/strip_line_char.toml  |  4 +-
 .../fixtures/no-color/strip_line_non_ws.toml  |  4 +-
 tests/formatter.rs                            | 22 ++++-----
 21 files changed, 114 insertions(+), 112 deletions(-)

diff --git a/benches/simple.rs b/benches/simple.rs
index 75a852f..a22b42d 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -4,7 +4,7 @@ extern crate criterion;
 
 use criterion::{black_box, Criterion};
 
-use annotate_snippets::{Label, Message, Renderer, Slice};
+use annotate_snippets::{Label, Message, Renderer, Snippet};
 
 fn create_snippet(renderer: Renderer) {
     let source = r#") -> Option<String> {
@@ -29,8 +29,8 @@ fn create_snippet(renderer: Renderer) {
             _ => continue,
         }
     }"#;
-    let message = Message::error("mismatched types").id("E0308").slice(
-        Slice::new(source, 51)
+    let message = Message::error("mismatched types").id("E0308").snippet(
+        Snippet::new(source, 51)
             .origin("src/format.rs")
             .annotation(
                 Label::warning("expected `Option<String>` because of return type").span(5..19),
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index ccdb835..d15cabb 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,12 +1,12 @@
-use annotate_snippets::{Label, Message, Renderer, Slice};
+use annotate_snippets::{Label, Message, Renderer, Snippet};
 
 fn main() {
     let source = r#"                annotations: vec![SourceAnnotation {
                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
                     ,
                 range: <22, 25>,"#;
-    let message = Message::error("expected type, found `22`").slice(
-        Slice::new(source, 26)
+    let message = Message::error("expected type, found `22`").snippet(
+        Snippet::new(source, 26)
             .origin("examples/footer.rs")
             .fold(true)
             .annotation(
diff --git a/examples/footer.rs b/examples/footer.rs
index 924fae2..e82d1a9 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,10 +1,10 @@
-use annotate_snippets::{Label, Message, Renderer, Slice};
+use annotate_snippets::{Label, Message, Renderer, Snippet};
 
 fn main() {
     let message = Message::error("mismatched types")
         .id("E0308")
-        .slice(
-            Slice::new("        slices: vec![\"A\",", 13)
+        .snippet(
+            Snippet::new("        slices: vec![\"A\",", 13)
                 .origin("src/multislice.rs")
                 .annotation(
                     Label::error(
diff --git a/examples/format.rs b/examples/format.rs
index 85e9a82..ad6b6de 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Label, Message, Renderer, Slice};
+use annotate_snippets::{Label, Message, Renderer, Snippet};
 
 fn main() {
     let source = r#") -> Option<String> {
@@ -23,8 +23,8 @@ fn main() {
             _ => continue,
         }
     }"#;
-    let message = Message::error("mismatched types").id("E0308").slice(
-        Slice::new(source, 51)
+    let message = Message::error("mismatched types").id("E0308").snippet(
+        Snippet::new(source, 51)
             .origin("src/format.rs")
             .annotation(
                 Label::warning("expected `Option<String>` because of return type").span(5..19),
diff --git a/examples/multislice.rs b/examples/multislice.rs
index 48e7372..28e8fde 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,9 +1,9 @@
-use annotate_snippets::{Message, Renderer, Slice};
+use annotate_snippets::{Message, Renderer, Snippet};
 
 fn main() {
     let message = Message::error("mismatched types")
-        .slice(Slice::new("Foo", 51).origin("src/format.rs"))
-        .slice(Slice::new("Faa", 129).origin("src/display.rs"));
+        .snippet(Snippet::new("Foo", 51).origin("src/format.rs"))
+        .snippet(Snippet::new("Faa", 129).origin("src/display.rs"));
 
     let renderer = Renderer::styled();
     anstream::println!("{}", renderer.render(message));
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 07216c3..e02e176 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -110,7 +110,7 @@ impl<'a> DisplayList<'a> {
             title,
             id,
             footer,
-            slices,
+            snippets,
         }: snippet::Message<'a>,
         stylesheet: &'a Stylesheet,
         anonymized_line_numbers: bool,
@@ -120,9 +120,9 @@ impl<'a> DisplayList<'a> {
 
         body.push(format_title(title, id));
 
-        for (idx, slice) in slices.into_iter().enumerate() {
+        for (idx, snippet) in snippets.into_iter().enumerate() {
             body.append(&mut format_slice(
-                slice,
+                snippet,
                 idx == 0,
                 !footer.is_empty(),
                 margin,
@@ -542,7 +542,7 @@ pub enum DisplayLine<'a> {
 /// A source line.
 #[derive(Debug, PartialEq)]
 pub enum DisplaySourceLine<'a> {
-    /// A line with the content of the Slice.
+    /// A line with the content of the Snippet.
     Content {
         text: &'a str,
         range: (usize, usize), // meta information for annotation placement.
@@ -762,15 +762,15 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> {
 }
 
 fn format_slice(
-    slice: snippet::Slice<'_>,
+    snippet: snippet::Snippet<'_>,
     is_first: bool,
     has_footer: bool,
     margin: Option<Margin>,
 ) -> Vec<DisplayLine<'_>> {
-    let main_range = slice.annotations.first().map(|x| x.range.start);
-    let origin = slice.origin;
+    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 mut body = format_body(slice, need_empty_header, has_footer, margin);
+    let mut body = format_body(snippet, need_empty_header, has_footer, margin);
     let header = format_header(origin, main_range, &body, is_first);
     let mut result = vec![];
 
@@ -942,13 +942,13 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
 }
 
 fn format_body(
-    slice: snippet::Slice<'_>,
+    snippet: snippet::Snippet<'_>,
     need_empty_header: bool,
     has_footer: bool,
     margin: Option<Margin>,
 ) -> Vec<DisplayLine<'_>> {
-    let source_len = slice.source.len();
-    if let Some(bigger) = slice.annotations.iter().find_map(|x| {
+    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.
         if source_len + 1 < x.range.end {
             Some(&x.range)
@@ -963,7 +963,7 @@ fn format_body(
     }
 
     let mut body = vec![];
-    let mut current_line = slice.line_start;
+    let mut current_line = snippet.line_start;
     let mut current_index = 0;
     let mut line_info = vec![];
 
@@ -972,7 +972,7 @@ fn format_body(
         line_end_index: usize,
     }
 
-    for (line, end_line) in CursorLines::new(slice.source) {
+    for (line, end_line) in CursorLines::new(snippet.source) {
         let line_length: usize = line
             .chars()
             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
@@ -995,7 +995,7 @@ fn format_body(
     }
 
     let mut annotation_line_count = 0;
-    let mut annotations = slice.annotations;
+    let mut annotations = snippet.annotations;
     for (
         idx,
         LineInfo {
@@ -1143,7 +1143,7 @@ fn format_body(
         });
     }
 
-    if slice.fold {
+    if snippet.fold {
         body = fold_body(body);
     }
 
@@ -1227,7 +1227,7 @@ mod tests {
         let line_1 = "This is line 1";
         let line_2 = "This is line 2";
         let source = [line_1, line_2].join("\n");
-        let input = snippet::Message::error("").slice(snippet::Slice::new(&source, 5402));
+        let input = snippet::Message::error("").snippet(snippet::Snippet::new(&source, 5402));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1278,8 +1278,8 @@ mod tests {
         let src_1 = "This is slice 2";
         let src_1_len = src_1.len();
         let input = snippet::Message::error("")
-            .slice(snippet::Slice::new(src_0, 5402).origin("file1.rs"))
-            .slice(snippet::Slice::new(src_1, 2).origin("file2.rs"));
+            .snippet(snippet::Snippet::new(src_0, 5402).origin("file1.rs"))
+            .snippet(snippet::Snippet::new(src_1, 2).origin("file2.rs"));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1350,8 +1350,8 @@ mod tests {
         let source = [line_1, line_2].join("\n");
         // In line 2
         let range = 22..24;
-        let input = snippet::Message::error("").slice(
-            snippet::Slice::new(&source, 5402)
+        let input = snippet::Message::error("").snippet(
+            snippet::Snippet::new(&source, 5402)
                 .annotation(snippet::Label::info("Test annotation").span(range.clone())),
         );
         let output = from_display_lines(vec![
@@ -1455,8 +1455,8 @@ mod tests {
     fn test_i26() {
         let source = "short";
         let label = "label";
-        let input = snippet::Message::error("").slice(
-            snippet::Slice::new(source, 0)
+        let input = snippet::Message::error("").snippet(
+            snippet::Snippet::new(source, 0)
                 .annotation(snippet::Label::error(label).span(0..source.len() + 2)),
         );
         let _ = DisplayList::new(input, &STYLESHEET, false, None);
@@ -1464,8 +1464,8 @@ mod tests {
 
     #[test]
     fn test_i_29() {
-        let snippets = snippet::Message::error("oops").slice(
-            snippet::Slice::new("First line\r\nSecond oops line", 1)
+        let snippets = snippet::Message::error("oops").snippet(
+            snippet::Snippet::new("First line\r\nSecond oops line", 1)
                 .origin("<current file>")
                 .fold(true)
                 .annotation(snippet::Label::error("oops").span(19..23)),
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index ef24cfd..5ce7508 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -2,10 +2,10 @@
 //!
 //! # Example
 //! ```
-//! use annotate_snippets::{Renderer, Slice, Message};
+//! use annotate_snippets::{Renderer, Snippet, Message};
 //! let snippet = Message::error("mismatched types")
-//!     .slice(Slice::new("Foo", 51).origin("src/format.rs"))
-//!     .slice(Slice::new("Faa", 129).origin("src/display.rs"));
+//!     .snippet(Snippet::new("Foo", 51).origin("src/format.rs"))
+//!     .snippet(Snippet::new("Faa", 129).origin("src/display.rs"));
 //!
 //!  let renderer = Renderer::styled();
 //!  println!("{}", renderer.render(snippet));
diff --git a/src/snippet.rs b/src/snippet.rs
index 61253ca..f8832c6 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -6,8 +6,8 @@
 //! use annotate_snippets::*;
 //!
 //! Message::error("mismatched types")
-//!     .slice(Slice::new("Foo", 51).origin("src/format.rs"))
-//!     .slice(Slice::new("Faa", 129).origin("src/display.rs"));
+//!     .snippet(Snippet::new("Foo", 51).origin("src/format.rs"))
+//!     .snippet(Snippet::new("Faa", 129).origin("src/display.rs"));
 //! ```
 
 use std::ops::Range;
@@ -16,7 +16,7 @@ use std::ops::Range;
 pub struct Message<'a> {
     pub(crate) title: Label<'a>,
     pub(crate) id: Option<&'a str>,
-    pub(crate) slices: Vec<Slice<'a>>,
+    pub(crate) snippets: Vec<Snippet<'a>>,
     pub(crate) footer: Vec<Label<'a>>,
 }
 
@@ -25,7 +25,7 @@ impl<'a> Message<'a> {
         Self {
             title,
             id: None,
-            slices: vec![],
+            snippets: vec![],
             footer: vec![],
         }
     }
@@ -55,8 +55,8 @@ impl<'a> Message<'a> {
         self
     }
 
-    pub fn slice(mut self, slice: Slice<'a>) -> Self {
-        self.slices.push(slice);
+    pub fn snippet(mut self, slice: Snippet<'a>) -> Self {
+        self.snippets.push(slice);
         self
     }
 
@@ -100,7 +100,7 @@ impl<'a> Label<'a> {
         self
     }
 
-    /// Create a [`Annotation`] with the given span for a [`Slice`]
+    /// Create a [`Annotation`] with the given span for a [`Snippet`]
     pub fn span(&self, span: Range<usize>) -> Annotation<'a> {
         Annotation {
             range: span,
@@ -113,9 +113,9 @@ impl<'a> Label<'a> {
 /// Structure containing the slice of text to be annotated and
 /// basic information about the location of the slice.
 ///
-/// One `Slice` is meant to represent a single, continuous,
+/// One `Snippet` is meant to represent a single, continuous,
 /// slice of source code that you want to annotate.
-pub struct Slice<'a> {
+pub struct Snippet<'a> {
     pub(crate) source: &'a str,
     pub(crate) line_start: usize,
     pub(crate) origin: Option<&'a str>,
@@ -123,7 +123,7 @@ pub struct Slice<'a> {
     pub(crate) fold: bool,
 }
 
-impl<'a> Slice<'a> {
+impl<'a> Snippet<'a> {
     pub fn new(source: &'a str, line_start: usize) -> Self {
         Self {
             source,
@@ -162,7 +162,7 @@ pub enum Level {
     Help,
 }
 
-/// An annotation for a [`Slice`].
+/// An annotation for a [`Snippet`].
 ///
 /// This gets created by [`Label::span`].
 #[derive(Debug)]
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 6bee419..99d1467 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -1,7 +1,7 @@
 use serde::{Deserialize, Deserializer, Serialize};
 use std::ops::Range;
 
-use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Slice};
+use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Snippet};
 
 #[derive(Deserialize)]
 pub struct Fixture<'a> {
@@ -23,9 +23,9 @@ pub struct MessageDef<'a> {
     #[serde(default)]
     #[serde(borrow)]
     pub footer: Vec<Label<'a>>,
-    #[serde(deserialize_with = "deserialize_slices")]
+    #[serde(deserialize_with = "deserialize_snippets")]
     #[serde(borrow)]
-    pub slices: Vec<Slice<'a>>,
+    pub snippets: Vec<Snippet<'a>>,
 }
 
 impl<'a> From<MessageDef<'a>> for Message<'a> {
@@ -34,15 +34,15 @@ impl<'a> From<MessageDef<'a>> for Message<'a> {
             title,
             id,
             footer,
-            slices,
+            snippets,
         } = val;
         let mut message = Message::title(title);
         if let Some(id) = id {
             message = message.id(id);
         }
-        message = slices
+        message = snippets
             .into_iter()
-            .fold(message, |message, slice| message.slice(slice));
+            .fold(message, |message, snippet| message.snippet(snippet));
         message = footer
             .into_iter()
             .fold(message, |message, label| message.footer(label));
@@ -81,15 +81,15 @@ where
         .collect())
 }
 
-fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error>
+fn deserialize_snippets<'de, D>(deserializer: D) -> Result<Vec<Snippet<'de>>, D::Error>
 where
     D: Deserializer<'de>,
 {
     #[derive(Deserialize)]
     struct Wrapper<'a>(
-        #[serde(with = "SliceDef")]
+        #[serde(with = "SnippetDef")]
         #[serde(borrow)]
-        SliceDef<'a>,
+        SnippetDef<'a>,
     );
 
     let v = Vec::deserialize(deserializer)?;
@@ -97,7 +97,7 @@ where
 }
 
 #[derive(Deserialize)]
-pub struct SliceDef<'a> {
+pub struct SnippetDef<'a> {
     #[serde(borrow)]
     pub source: &'a str,
     pub line_start: usize,
@@ -110,23 +110,25 @@ pub struct SliceDef<'a> {
     pub fold: bool,
 }
 
-impl<'a> From<SliceDef<'a>> for Slice<'a> {
-    fn from(val: SliceDef<'a>) -> Self {
-        let SliceDef {
+impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
+    fn from(val: SnippetDef<'a>) -> Self {
+        let SnippetDef {
             source,
             line_start,
             origin,
             annotations,
             fold,
         } = val;
-        let mut slice = Slice::new(source, line_start).fold(fold);
+        let mut snippet = Snippet::new(source, line_start).fold(fold);
         if let Some(origin) = origin {
-            slice = slice.origin(origin)
+            snippet = snippet.origin(origin)
         }
-        slice = annotations
+        snippet = annotations
             .into_iter()
-            .fold(slice, |slice, annotation| slice.annotation(annotation));
-        slice
+            .fold(snippet, |snippet, annotation| {
+                snippet.annotation(annotation)
+            });
+        snippet
     }
 }
 
diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml
index 749324e..9729dcc 100644
--- a/tests/fixtures/no-color/issue_52.toml
+++ b/tests/fixtures/no-color/issue_52.toml
@@ -2,7 +2,7 @@
 level = "Error"
 label = ""
 
-[[message.slices]]
+[[message.snippets]]
 source = """
 
 
@@ -11,7 +11,7 @@ invalid syntax
 line_start = 1
 origin = "path/to/error.rs"
 fold = true
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "error here"
 level = "Warning"
 range = [2,16]
diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml
index e534505..04ca88c 100644
--- a/tests/fixtures/no-color/issue_9.toml
+++ b/tests/fixtures/no-color/issue_9.toml
@@ -2,27 +2,27 @@
 label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 level = "Error"
 
-[[message.slices]]
+[[message.snippets]]
 source = "let x = vec![1];"
 line_start = 4
 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs"
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait"
 level = "Warning"
 range = [4, 5]
 
-[[message.slices]]
+[[message.snippets]]
 source = "let y = x;"
 line_start = 7
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "value moved here"
 level = "Warning"
 range = [8, 9]
 
-[[message.slices]]
+[[message.snippets]]
 source = "x;"
 line_start = 9
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "value used here after move"
 level = "Error"
 range = [0, 1]
diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml
index 55b0513..d20716d 100644
--- a/tests/fixtures/no-color/multiline_annotation.toml
+++ b/tests/fixtures/no-color/multiline_annotation.toml
@@ -1,4 +1,4 @@
-[[message.slices]]
+[[message.snippets]]
 source = """
 ) -> Option<String> {
     for ann in annotations {
@@ -26,11 +26,11 @@ source = """
 line_start = 51
 origin = "src/format.rs"
 fold = true
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected `std::option::Option<std::string::String>` because of return type"
 level = "Warning"
 range = [5, 19]
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected enum `std::option::Option`, found ()"
 level = "Error"
 range = [22, 766]
diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml
index b1506ef..db7da7b 100644
--- a/tests/fixtures/no-color/multiline_annotation2.toml
+++ b/tests/fixtures/no-color/multiline_annotation2.toml
@@ -1,4 +1,4 @@
-[[message.slices]]
+[[message.snippets]]
 source = """
                         if let DisplayLine::Source {
                             ref mut inline_marks,
@@ -7,7 +7,7 @@ source = """
 line_start = 139
 origin = "src/display_list.rs"
 fold = false
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "missing fields `lineno`, `content`"
 level = "Error"
 range = [31, 128]
diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml
index a5f0400..76c6bc5 100644
--- a/tests/fixtures/no-color/multiline_annotation3.toml
+++ b/tests/fixtures/no-color/multiline_annotation3.toml
@@ -1,4 +1,4 @@
-[[message.slices]]
+[[message.snippets]]
 source = """
 This is an exampl
 e of an edge case of an annotation overflowing
@@ -7,7 +7,7 @@ to exactly one character on next line.
 line_start = 26
 origin = "foo.txt"
 fold = false
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "this should not be on separate lines"
 level = "Error"
 range = [11, 18]
diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml
index 1c97a27..d9adbfa 100644
--- a/tests/fixtures/no-color/multiple_annotations.toml
+++ b/tests/fixtures/no-color/multiple_annotations.toml
@@ -2,7 +2,7 @@
 level = "Error"
 label = ""
 
-[[message.slices]]
+[[message.snippets]]
 source = """
 fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {
     if let Some(annotation) = main_annotation {
@@ -15,15 +15,15 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>
 }
 """
 line_start = 96
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "Variable defined here"
 level = "Error"
 range = [100, 110]
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "Referenced here"
 level = "Error"
 range = [184, 194]
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "Referenced again here"
 level = "Error"
 range = [243, 253]
diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml
index 6dbee4b..a84fa88 100644
--- a/tests/fixtures/no-color/one_past.toml
+++ b/tests/fixtures/no-color/one_past.toml
@@ -2,11 +2,11 @@
 label = "expected `.`, `=`"
 level = "Error"
 
-[[message.slices]]
+[[message.snippets]]
 source = "asdf"
 line_start = 1
 origin = "Cargo.toml"
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = ""
 level = "Error"
 range = [4, 5]
diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml
index c511ef9..70a65df 100644
--- a/tests/fixtures/no-color/simple.toml
+++ b/tests/fixtures/no-color/simple.toml
@@ -1,15 +1,15 @@
-[[message.slices]]
+[[message.snippets]]
 source = """
         })
 
         for line in &self.body {"""
 line_start = 169
 origin = "src/format_color.rs"
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "unexpected token"
 level = "Error"
 range = [20, 23]
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected one of `.`, `;`, `?`, or an operator here"
 level = "Warning"
 range = [10, 11]
diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml
index 0260125..21eb897 100644
--- a/tests/fixtures/no-color/strip_line.toml
+++ b/tests/fixtures/no-color/strip_line.toml
@@ -2,12 +2,12 @@
 title = { level = "Error", label = "mismatched types" }
 id = "E0308"
 
-[[message.slices]]
+[[message.snippets]]
 source = "                                                                                                                                                                                    let _: () = 42;"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected (), found integer"
 level = "Error"
 range = [192, 194]
diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml
index 70ee2e3..d7daae0 100644
--- a/tests/fixtures/no-color/strip_line_char.toml
+++ b/tests/fixtures/no-color/strip_line_char.toml
@@ -2,12 +2,12 @@
 title = { level = "Error", label = "mismatched types" }
 id = "E0308"
 
-[[message.slices]]
+[[message.snippets]]
 source = "                                                                                                                                                                                    let _: () = 42ñ"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected (), found integer"
 level = "Error"
 range = [192, 194]
diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml
index e133bbc..3afb6ba 100644
--- a/tests/fixtures/no-color/strip_line_non_ws.toml
+++ b/tests/fixtures/no-color/strip_line_non_ws.toml
@@ -2,12 +2,12 @@
 title = { level = "Error", label = "mismatched types" }
 id = "E0308"
 
-[[message.slices]]
+[[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 _: () = ();"
 line_start = 4
 origin = "$DIR/non-whitespace-trimming.rs"
 
-[[message.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected (), found integer"
 level = "Error"
 range = [240, 242]
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 4b90366..54663cb 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,9 +1,9 @@
-use annotate_snippets::{Label, Message, Renderer, Slice};
+use annotate_snippets::{Label, Message, Renderer, Snippet};
 
 #[test]
 fn test_i_29() {
-    let snippets = Message::error("oops").slice(
-        Slice::new("First line\r\nSecond oops line", 1)
+    let snippets = Message::error("oops").snippet(
+        Snippet::new("First line\r\nSecond oops line", 1)
             .origin("<current file>")
             .annotation(Label::error("oops").span(19..23))
             .fold(true),
@@ -22,8 +22,8 @@ fn test_i_29() {
 
 #[test]
 fn test_point_to_double_width_characters() {
-    let snippets = Message::error("").slice(
-        Slice::new("こんにちは、世界", 1)
+    let snippets = Message::error("").snippet(
+        Snippet::new("こんにちは、世界", 1)
             .origin("<current file>")
             .annotation(Label::error("world").span(12..16)),
     );
@@ -41,8 +41,8 @@ fn test_point_to_double_width_characters() {
 
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
-    let snippets = Message::error("").slice(
-        Slice::new("おはよう\nございます", 1)
+    let snippets = Message::error("").snippet(
+        Snippet::new("おはよう\nございます", 1)
             .origin("<current file>")
             .annotation(Label::error("Good morning").span(4..15)),
     );
@@ -62,8 +62,8 @@ fn test_point_to_double_width_characters_across_lines() {
 
 #[test]
 fn test_point_to_double_width_characters_multiple() {
-    let snippets = Message::error("").slice(
-        Slice::new("お寿司\n食べたい🍣", 1)
+    let snippets = Message::error("").snippet(
+        Snippet::new("お寿司\n食べたい🍣", 1)
             .origin("<current file>")
             .annotation(Label::error("Sushi1").span(0..6))
             .annotation(Label::note("Sushi2").span(11..15)),
@@ -84,8 +84,8 @@ fn test_point_to_double_width_characters_multiple() {
 
 #[test]
 fn test_point_to_double_width_characters_mixed() {
-    let snippets = Message::error("").slice(
-        Slice::new("こんにちは、新しいWorld!", 1)
+    let snippets = Message::error("").snippet(
+        Snippet::new("こんにちは、新しいWorld!", 1)
             .origin("<current file>")
             .annotation(Label::error("New world").span(12..23)),
     );

From 95645d15d89fe29f43c8737e4f6791f11a55b934 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 15:19:10 -0500
Subject: [PATCH 119/302] refactor: Re-order Snippet fields according to render
 layout

---
 src/snippet.rs | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/snippet.rs b/src/snippet.rs
index f8832c6..153fc7f 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -116,19 +116,21 @@ impl<'a> Label<'a> {
 /// One `Snippet` is meant to represent a single, continuous,
 /// slice of source code that you want to annotate.
 pub struct Snippet<'a> {
-    pub(crate) source: &'a str,
-    pub(crate) line_start: usize,
     pub(crate) origin: Option<&'a str>,
+    pub(crate) line_start: usize,
+
+    pub(crate) source: &'a str,
     pub(crate) annotations: Vec<Annotation<'a>>,
+
     pub(crate) fold: bool,
 }
 
 impl<'a> Snippet<'a> {
     pub fn new(source: &'a str, line_start: usize) -> Self {
         Self {
-            source,
-            line_start,
             origin: None,
+            line_start,
+            source,
             annotations: vec![],
             fold: false,
         }

From 91bd796dde322ef4573c1ca707ccf99898938c07 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 15:22:01 -0500
Subject: [PATCH 120/302] refactor: Re-order structs logically

---
 src/snippet.rs | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/snippet.rs b/src/snippet.rs
index 153fc7f..bfe849b 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -152,6 +152,17 @@ impl<'a> Snippet<'a> {
     }
 }
 
+/// An annotation for a [`Snippet`].
+///
+/// This gets created by [`Label::span`].
+#[derive(Debug)]
+pub struct Annotation<'a> {
+    /// The byte range of the annotation in the `source` string
+    pub(crate) range: Range<usize>,
+    pub(crate) label: &'a str,
+    pub(crate) level: Level,
+}
+
 /// Types of annotations.
 #[derive(Debug, Clone, Copy, PartialEq)]
 pub enum Level {
@@ -163,14 +174,3 @@ pub enum Level {
     Note,
     Help,
 }
-
-/// An annotation for a [`Snippet`].
-///
-/// This gets created by [`Label::span`].
-#[derive(Debug)]
-pub struct Annotation<'a> {
-    /// The byte range of the annotation in the `source` string
-    pub(crate) range: Range<usize>,
-    pub(crate) label: &'a str,
-    pub(crate) level: Level,
-}

From b821bd397e2118a4d786231e1ca9ace28c2632a9 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 15:28:58 -0500
Subject: [PATCH 121/302] fix!: Default Snippet::line_start

---
 benches/simple.rs             |  3 ++-
 examples/expected_type.rs     |  3 ++-
 examples/footer.rs            |  3 ++-
 examples/format.rs            |  3 ++-
 examples/multislice.rs        |  4 ++--
 src/renderer/display_list.rs  | 24 ++++++++++++++++++------
 src/renderer/mod.rs           |  4 ++--
 src/snippet.rs                | 13 +++++++++----
 tests/fixtures/deserialize.rs |  2 +-
 tests/formatter.rs            | 10 +++++-----
 10 files changed, 45 insertions(+), 24 deletions(-)

diff --git a/benches/simple.rs b/benches/simple.rs
index a22b42d..26ff151 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -30,7 +30,8 @@ fn create_snippet(renderer: Renderer) {
         }
     }"#;
     let message = Message::error("mismatched types").id("E0308").snippet(
-        Snippet::new(source, 51)
+        Snippet::new(source)
+            .line_start(51)
             .origin("src/format.rs")
             .annotation(
                 Label::warning("expected `Option<String>` because of return type").span(5..19),
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index d15cabb..73d3425 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -6,7 +6,8 @@ fn main() {
                     ,
                 range: <22, 25>,"#;
     let message = Message::error("expected type, found `22`").snippet(
-        Snippet::new(source, 26)
+        Snippet::new(source)
+            .line_start(26)
             .origin("examples/footer.rs")
             .fold(true)
             .annotation(
diff --git a/examples/footer.rs b/examples/footer.rs
index e82d1a9..482487d 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -4,7 +4,8 @@ fn main() {
     let message = Message::error("mismatched types")
         .id("E0308")
         .snippet(
-            Snippet::new("        slices: vec![\"A\",", 13)
+            Snippet::new("        slices: vec![\"A\",")
+                .line_start(13)
                 .origin("src/multislice.rs")
                 .annotation(
                     Label::error(
diff --git a/examples/format.rs b/examples/format.rs
index ad6b6de..646eefd 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -24,7 +24,8 @@ fn main() {
         }
     }"#;
     let message = Message::error("mismatched types").id("E0308").snippet(
-        Snippet::new(source, 51)
+        Snippet::new(source)
+            .line_start(51)
             .origin("src/format.rs")
             .annotation(
                 Label::warning("expected `Option<String>` because of return type").span(5..19),
diff --git a/examples/multislice.rs b/examples/multislice.rs
index 28e8fde..39ca1a0 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -2,8 +2,8 @@ use annotate_snippets::{Message, Renderer, Snippet};
 
 fn main() {
     let message = Message::error("mismatched types")
-        .snippet(Snippet::new("Foo", 51).origin("src/format.rs"))
-        .snippet(Snippet::new("Faa", 129).origin("src/display.rs"));
+        .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs"))
+        .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs"));
 
     let renderer = Renderer::styled();
     anstream::println!("{}", renderer.render(message));
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index e02e176..13e97c8 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1227,7 +1227,8 @@ mod tests {
         let line_1 = "This is line 1";
         let line_2 = "This is line 2";
         let source = [line_1, line_2].join("\n");
-        let input = snippet::Message::error("").snippet(snippet::Snippet::new(&source, 5402));
+        let input =
+            snippet::Message::error("").snippet(snippet::Snippet::new(&source).line_start(5402));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1278,8 +1279,16 @@ mod tests {
         let src_1 = "This is slice 2";
         let src_1_len = src_1.len();
         let input = snippet::Message::error("")
-            .snippet(snippet::Snippet::new(src_0, 5402).origin("file1.rs"))
-            .snippet(snippet::Snippet::new(src_1, 2).origin("file2.rs"));
+            .snippet(
+                snippet::Snippet::new(src_0)
+                    .line_start(5402)
+                    .origin("file1.rs"),
+            )
+            .snippet(
+                snippet::Snippet::new(src_1)
+                    .line_start(2)
+                    .origin("file2.rs"),
+            );
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1351,7 +1360,8 @@ mod tests {
         // In line 2
         let range = 22..24;
         let input = snippet::Message::error("").snippet(
-            snippet::Snippet::new(&source, 5402)
+            snippet::Snippet::new(&source)
+                .line_start(5402)
                 .annotation(snippet::Label::info("Test annotation").span(range.clone())),
         );
         let output = from_display_lines(vec![
@@ -1456,7 +1466,8 @@ mod tests {
         let source = "short";
         let label = "label";
         let input = snippet::Message::error("").snippet(
-            snippet::Snippet::new(source, 0)
+            snippet::Snippet::new(source)
+                .line_start(0)
                 .annotation(snippet::Label::error(label).span(0..source.len() + 2)),
         );
         let _ = DisplayList::new(input, &STYLESHEET, false, None);
@@ -1465,7 +1476,8 @@ mod tests {
     #[test]
     fn test_i_29() {
         let snippets = snippet::Message::error("oops").snippet(
-            snippet::Snippet::new("First line\r\nSecond oops line", 1)
+            snippet::Snippet::new("First line\r\nSecond oops line")
+                .line_start(1)
                 .origin("<current file>")
                 .fold(true)
                 .annotation(snippet::Label::error("oops").span(19..23)),
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 5ce7508..1974b6f 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -4,8 +4,8 @@
 //! ```
 //! use annotate_snippets::{Renderer, Snippet, Message};
 //! let snippet = Message::error("mismatched types")
-//!     .snippet(Snippet::new("Foo", 51).origin("src/format.rs"))
-//!     .snippet(Snippet::new("Faa", 129).origin("src/display.rs"));
+//!     .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs"))
+//!     .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs"));
 //!
 //!  let renderer = Renderer::styled();
 //!  println!("{}", renderer.render(snippet));
diff --git a/src/snippet.rs b/src/snippet.rs
index bfe849b..e32a5a3 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -6,8 +6,8 @@
 //! use annotate_snippets::*;
 //!
 //! Message::error("mismatched types")
-//!     .snippet(Snippet::new("Foo", 51).origin("src/format.rs"))
-//!     .snippet(Snippet::new("Faa", 129).origin("src/display.rs"));
+//!     .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs"))
+//!     .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs"));
 //! ```
 
 use std::ops::Range;
@@ -126,16 +126,21 @@ pub struct Snippet<'a> {
 }
 
 impl<'a> Snippet<'a> {
-    pub fn new(source: &'a str, line_start: usize) -> Self {
+    pub fn new(source: &'a str) -> Self {
         Self {
             origin: None,
-            line_start,
+            line_start: 1,
             source,
             annotations: vec![],
             fold: false,
         }
     }
 
+    pub fn line_start(mut self, line_start: usize) -> Self {
+        self.line_start = line_start;
+        self
+    }
+
     pub fn origin(mut self, origin: &'a str) -> Self {
         self.origin = Some(origin);
         self
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 99d1467..36349a9 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -119,7 +119,7 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
             annotations,
             fold,
         } = val;
-        let mut snippet = Snippet::new(source, line_start).fold(fold);
+        let mut snippet = Snippet::new(source).line_start(line_start).fold(fold);
         if let Some(origin) = origin {
             snippet = snippet.origin(origin)
         }
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 54663cb..ebf7604 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -3,7 +3,7 @@ use annotate_snippets::{Label, Message, Renderer, Snippet};
 #[test]
 fn test_i_29() {
     let snippets = Message::error("oops").snippet(
-        Snippet::new("First line\r\nSecond oops line", 1)
+        Snippet::new("First line\r\nSecond oops line")
             .origin("<current file>")
             .annotation(Label::error("oops").span(19..23))
             .fold(true),
@@ -23,7 +23,7 @@ fn test_i_29() {
 #[test]
 fn test_point_to_double_width_characters() {
     let snippets = Message::error("").snippet(
-        Snippet::new("こんにちは、世界", 1)
+        Snippet::new("こんにちは、世界")
             .origin("<current file>")
             .annotation(Label::error("world").span(12..16)),
     );
@@ -42,7 +42,7 @@ fn test_point_to_double_width_characters() {
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
     let snippets = Message::error("").snippet(
-        Snippet::new("おはよう\nございます", 1)
+        Snippet::new("おはよう\nございます")
             .origin("<current file>")
             .annotation(Label::error("Good morning").span(4..15)),
     );
@@ -63,7 +63,7 @@ fn test_point_to_double_width_characters_across_lines() {
 #[test]
 fn test_point_to_double_width_characters_multiple() {
     let snippets = Message::error("").snippet(
-        Snippet::new("お寿司\n食べたい🍣", 1)
+        Snippet::new("お寿司\n食べたい🍣")
             .origin("<current file>")
             .annotation(Label::error("Sushi1").span(0..6))
             .annotation(Label::note("Sushi2").span(11..15)),
@@ -85,7 +85,7 @@ fn test_point_to_double_width_characters_multiple() {
 #[test]
 fn test_point_to_double_width_characters_mixed() {
     let snippets = Message::error("").snippet(
-        Snippet::new("こんにちは、新しいWorld!", 1)
+        Snippet::new("こんにちは、新しいWorld!")
             .origin("<current file>")
             .annotation(Label::error("New world").span(12..23)),
     );

From 39672b2b5da27ac820af3a12db78bf8995c82e5f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 15:50:02 -0500
Subject: [PATCH 122/302] fix!: Move Message contruction to Level

This fits with the session-type / type-state style builder and allows
Diagnoatic adapters, like rustc, to leverage it vs the hard-coded
functions.
---
 benches/simple.rs                             |  4 +-
 examples/expected_type.rs                     |  4 +-
 examples/footer.rs                            |  5 +-
 examples/format.rs                            |  4 +-
 examples/multislice.rs                        |  5 +-
 src/renderer/display_list.rs                  | 32 ++++++++-----
 src/renderer/mod.rs                           |  4 +-
 src/snippet.rs                                | 48 +++++++------------
 tests/fixtures/deserialize.rs                 | 22 ++-------
 tests/fixtures/no-color/issue_52.toml         |  4 +-
 tests/fixtures/no-color/issue_9.toml          |  4 +-
 .../no-color/multiline_annotation.toml        |  9 ++--
 .../no-color/multiline_annotation2.toml       |  9 ++--
 .../no-color/multiline_annotation3.toml       |  9 ++--
 .../no-color/multiple_annotations.toml        |  4 +-
 tests/fixtures/no-color/one_past.toml         |  4 +-
 tests/fixtures/no-color/simple.toml           |  7 +--
 tests/fixtures/no-color/strip_line.toml       |  3 +-
 tests/fixtures/no-color/strip_line_char.toml  |  3 +-
 .../fixtures/no-color/strip_line_non_ws.toml  |  3 +-
 tests/formatter.rs                            | 12 ++---
 21 files changed, 96 insertions(+), 103 deletions(-)

diff --git a/benches/simple.rs b/benches/simple.rs
index 26ff151..59d02a0 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -4,7 +4,7 @@ extern crate criterion;
 
 use criterion::{black_box, Criterion};
 
-use annotate_snippets::{Label, Message, Renderer, Snippet};
+use annotate_snippets::{Label, Level, Renderer, Snippet};
 
 fn create_snippet(renderer: Renderer) {
     let source = r#") -> Option<String> {
@@ -29,7 +29,7 @@ fn create_snippet(renderer: Renderer) {
             _ => continue,
         }
     }"#;
-    let message = Message::error("mismatched types").id("E0308").snippet(
+    let message = Level::Error.title("mismatched types").id("E0308").snippet(
         Snippet::new(source)
             .line_start(51)
             .origin("src/format.rs")
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 73d3425..3938d5f 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,11 +1,11 @@
-use annotate_snippets::{Label, Message, Renderer, Snippet};
+use annotate_snippets::{Label, Level, Renderer, Snippet};
 
 fn main() {
     let source = r#"                annotations: vec![SourceAnnotation {
                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
                     ,
                 range: <22, 25>,"#;
-    let message = Message::error("expected type, found `22`").snippet(
+    let message = Level::Error.title("expected type, found `22`").snippet(
         Snippet::new(source)
             .line_start(26)
             .origin("examples/footer.rs")
diff --git a/examples/footer.rs b/examples/footer.rs
index 482487d..e873546 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,7 +1,8 @@
-use annotate_snippets::{Label, Message, Renderer, Snippet};
+use annotate_snippets::{Label, Level, Renderer, Snippet};
 
 fn main() {
-    let message = Message::error("mismatched types")
+    let message = Level::Error
+        .title("mismatched types")
         .id("E0308")
         .snippet(
             Snippet::new("        slices: vec![\"A\",")
diff --git a/examples/format.rs b/examples/format.rs
index 646eefd..7d19b87 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Label, Message, Renderer, Snippet};
+use annotate_snippets::{Label, Level, Renderer, Snippet};
 
 fn main() {
     let source = r#") -> Option<String> {
@@ -23,7 +23,7 @@ fn main() {
             _ => continue,
         }
     }"#;
-    let message = Message::error("mismatched types").id("E0308").snippet(
+    let message = Level::Error.title("mismatched types").id("E0308").snippet(
         Snippet::new(source)
             .line_start(51)
             .origin("src/format.rs")
diff --git a/examples/multislice.rs b/examples/multislice.rs
index 39ca1a0..38afbd4 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,7 +1,8 @@
-use annotate_snippets::{Message, Renderer, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 fn main() {
-    let message = Message::error("mismatched types")
+    let message = Level::Error
+        .title("mismatched types")
         .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs"))
         .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs"));
 
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 13e97c8..2ba7f83 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -107,8 +107,9 @@ impl<'a> DisplayList<'a> {
 
     pub(crate) fn new(
         snippet::Message {
-            title,
+            level,
             id,
+            title,
             footer,
             snippets,
         }: snippet::Message<'a>,
@@ -118,7 +119,13 @@ impl<'a> DisplayList<'a> {
     ) -> DisplayList<'a> {
         let mut body = vec![];
 
-        body.push(format_title(title, id));
+        body.push(format_title(
+            snippet::Label {
+                level,
+                label: title,
+            },
+            id,
+        ));
 
         for (idx, snippet) in snippets.into_iter().enumerate() {
             body.append(&mut format_slice(
@@ -1206,7 +1213,7 @@ mod tests {
 
     #[test]
     fn test_format_title() {
-        let input = snippet::Message::error("This is a title").id("E0001");
+        let input = snippet::Level::Error.title("This is a title").id("E0001");
         let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
                 annotation_type: DisplayAnnotationType::Error,
@@ -1227,8 +1234,9 @@ mod tests {
         let line_1 = "This is line 1";
         let line_2 = "This is line 2";
         let source = [line_1, line_2].join("\n");
-        let input =
-            snippet::Message::error("").snippet(snippet::Snippet::new(&source).line_start(5402));
+        let input = snippet::Level::Error
+            .title("")
+            .snippet(snippet::Snippet::new(&source).line_start(5402));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1278,7 +1286,8 @@ mod tests {
         let src_0_len = src_0.len();
         let src_1 = "This is slice 2";
         let src_1_len = src_1.len();
-        let input = snippet::Message::error("")
+        let input = snippet::Level::Error
+            .title("")
             .snippet(
                 snippet::Snippet::new(src_0)
                     .line_start(5402)
@@ -1359,7 +1368,7 @@ mod tests {
         let source = [line_1, line_2].join("\n");
         // In line 2
         let range = 22..24;
-        let input = snippet::Message::error("").snippet(
+        let input = snippet::Level::Error.title("").snippet(
             snippet::Snippet::new(&source)
                 .line_start(5402)
                 .annotation(snippet::Label::info("Test annotation").span(range.clone())),
@@ -1429,8 +1438,9 @@ mod tests {
 
     #[test]
     fn test_format_label() {
-        let input =
-            snippet::Message::error("").footer(snippet::Label::error("This __is__ a title"));
+        let input = snippet::Level::Error
+            .title("")
+            .footer(snippet::Label::error("This __is__ a title"));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1465,7 +1475,7 @@ mod tests {
     fn test_i26() {
         let source = "short";
         let label = "label";
-        let input = snippet::Message::error("").snippet(
+        let input = snippet::Level::Error.title("").snippet(
             snippet::Snippet::new(source)
                 .line_start(0)
                 .annotation(snippet::Label::error(label).span(0..source.len() + 2)),
@@ -1475,7 +1485,7 @@ mod tests {
 
     #[test]
     fn test_i_29() {
-        let snippets = snippet::Message::error("oops").snippet(
+        let snippets = snippet::Level::Error.title("oops").snippet(
             snippet::Snippet::new("First line\r\nSecond oops line")
                 .line_start(1)
                 .origin("<current file>")
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 1974b6f..29f3dae 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -2,8 +2,8 @@
 //!
 //! # Example
 //! ```
-//! use annotate_snippets::{Renderer, Snippet, Message};
-//! let snippet = Message::error("mismatched types")
+//! use annotate_snippets::{Renderer, Snippet, Level};
+//! let snippet = Level::Error.title("mismatched types")
 //!     .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs"))
 //!     .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs"));
 //!
diff --git a/src/snippet.rs b/src/snippet.rs
index e32a5a3..d2906d4 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -5,7 +5,7 @@
 //! ```
 //! use annotate_snippets::*;
 //!
-//! Message::error("mismatched types")
+//! Level::Error.title("mismatched types")
 //!     .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs"))
 //!     .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs"));
 //! ```
@@ -13,43 +13,17 @@
 use std::ops::Range;
 
 /// Primary structure provided for formatting
+///
+/// See [`Level::title`] to create a [`Message`]
 pub struct Message<'a> {
-    pub(crate) title: Label<'a>,
+    pub(crate) level: Level,
     pub(crate) id: Option<&'a str>,
+    pub(crate) title: &'a str,
     pub(crate) snippets: Vec<Snippet<'a>>,
     pub(crate) footer: Vec<Label<'a>>,
 }
 
 impl<'a> Message<'a> {
-    pub fn title(title: Label<'a>) -> Self {
-        Self {
-            title,
-            id: None,
-            snippets: vec![],
-            footer: vec![],
-        }
-    }
-
-    pub fn error(title: &'a str) -> Self {
-        Self::title(Label::error(title))
-    }
-
-    pub fn warning(title: &'a str) -> Self {
-        Self::title(Label::warning(title))
-    }
-
-    pub fn info(title: &'a str) -> Self {
-        Self::title(Label::info(title))
-    }
-
-    pub fn note(title: &'a str) -> Self {
-        Self::title(Label::note(title))
-    }
-
-    pub fn help(title: &'a str) -> Self {
-        Self::title(Label::help(title))
-    }
-
     pub fn id(mut self, id: &'a str) -> Self {
         self.id = Some(id);
         self
@@ -179,3 +153,15 @@ pub enum Level {
     Note,
     Help,
 }
+
+impl Level {
+    pub fn title(self, title: &str) -> Message<'_> {
+        Message {
+            level: self,
+            id: None,
+            title,
+            snippets: vec![],
+            footer: vec![],
+        }
+    }
+}
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 36349a9..7e64945 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -13,9 +13,10 @@ pub struct Fixture<'a> {
 
 #[derive(Deserialize)]
 pub struct MessageDef<'a> {
-    #[serde(deserialize_with = "deserialize_label")]
+    #[serde(with = "LevelDef")]
+    pub level: Level,
     #[serde(borrow)]
-    pub title: Label<'a>,
+    pub title: &'a str,
     #[serde(default)]
     #[serde(borrow)]
     pub id: Option<&'a str>,
@@ -31,12 +32,13 @@ pub struct MessageDef<'a> {
 impl<'a> From<MessageDef<'a>> for Message<'a> {
     fn from(val: MessageDef<'a>) -> Self {
         let MessageDef {
+            level,
             title,
             id,
             footer,
             snippets,
         } = val;
-        let mut message = Message::title(title);
+        let mut message = level.title(title);
         if let Some(id) = id {
             message = message.id(id);
         }
@@ -50,20 +52,6 @@ impl<'a> From<MessageDef<'a>> for Message<'a> {
     }
 }
 
-fn deserialize_label<'de, D>(deserializer: D) -> Result<Label<'de>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    #[derive(Deserialize)]
-    struct Wrapper<'a>(
-        #[serde(with = "LabelDef")]
-        #[serde(borrow)]
-        LabelDef<'a>,
-    );
-
-    Wrapper::deserialize(deserializer).map(|Wrapper(label)| Label::new(label.level, label.label))
-}
-
 fn deserialize_labels<'de, D>(deserializer: D) -> Result<Vec<Label<'de>>, D::Error>
 where
     D: Deserializer<'de>,
diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml
index 9729dcc..1e81a71 100644
--- a/tests/fixtures/no-color/issue_52.toml
+++ b/tests/fixtures/no-color/issue_52.toml
@@ -1,6 +1,6 @@
-[message.title]
+[message]
 level = "Error"
-label = ""
+title = ""
 
 [[message.snippets]]
 source = """
diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml
index 04ca88c..1f35243 100644
--- a/tests/fixtures/no-color/issue_9.toml
+++ b/tests/fixtures/no-color/issue_9.toml
@@ -1,6 +1,6 @@
-[message.title]
-label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
+[message]
 level = "Error"
+title = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 
 [[message.snippets]]
 source = "let x = vec![1];"
diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml
index d20716d..09fc7d4 100644
--- a/tests/fixtures/no-color/multiline_annotation.toml
+++ b/tests/fixtures/no-color/multiline_annotation.toml
@@ -1,3 +1,8 @@
+[message]
+level = "Error"
+id = "E0308"
+title = "mismatched types"
+
 [[message.snippets]]
 source = """
 ) -> Option<String> {
@@ -34,7 +39,3 @@ range = [5, 19]
 label = "expected enum `std::option::Option`, found ()"
 level = "Error"
 range = [22, 766]
-
-[message]
-title = { level = "Error", label = "mismatched types" }
-id = "E0308"
diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml
index db7da7b..671b534 100644
--- a/tests/fixtures/no-color/multiline_annotation2.toml
+++ b/tests/fixtures/no-color/multiline_annotation2.toml
@@ -1,3 +1,8 @@
+[message]
+level = "Error"
+id = "E0027"
+title = "pattern does not mention fields `lineno`, `content`"
+
 [[message.snippets]]
 source = """
                         if let DisplayLine::Source {
@@ -11,7 +16,3 @@ fold = false
 label = "missing fields `lineno`, `content`"
 level = "Error"
 range = [31, 128]
-
-[message]
-title = { level = "Error", label = "pattern does not mention fields `lineno`, `content`" }
-id = "E0027"
diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml
index 76c6bc5..dd85332 100644
--- a/tests/fixtures/no-color/multiline_annotation3.toml
+++ b/tests/fixtures/no-color/multiline_annotation3.toml
@@ -1,3 +1,8 @@
+[message]
+level = "Error"
+id = "E####"
+title = "spacing error found"
+
 [[message.snippets]]
 source = """
 This is an exampl
@@ -11,7 +16,3 @@ fold = false
 label = "this should not be on separate lines"
 level = "Error"
 range = [11, 18]
-
-[message]
-title = { level = "Error", label = "spacing error found" }
-id = "E####"
diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml
index d9adbfa..842b137 100644
--- a/tests/fixtures/no-color/multiple_annotations.toml
+++ b/tests/fixtures/no-color/multiple_annotations.toml
@@ -1,6 +1,6 @@
-[message.title]
+[message]
 level = "Error"
-label = ""
+title = ""
 
 [[message.snippets]]
 source = """
diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml
index a84fa88..b681c29 100644
--- a/tests/fixtures/no-color/one_past.toml
+++ b/tests/fixtures/no-color/one_past.toml
@@ -1,6 +1,6 @@
-[message.title]
-label = "expected `.`, `=`"
+[message]
 level = "Error"
+title = "expected `.`, `=`"
 
 [[message.snippets]]
 source = "asdf"
diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml
index 70a65df..76b5bac 100644
--- a/tests/fixtures/no-color/simple.toml
+++ b/tests/fixtures/no-color/simple.toml
@@ -1,3 +1,7 @@
+[message]
+level = "Error"
+title = "expected one of `.`, `;`, `?`, or an operator, found `for`"
+
 [[message.snippets]]
 source = """
         })
@@ -13,6 +17,3 @@ range = [20, 23]
 label = "expected one of `.`, `;`, `?`, or an operator here"
 level = "Warning"
 range = [10, 11]
-[message.title]
-label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
-level = "Error"
diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml
index 21eb897..d44024b 100644
--- a/tests/fixtures/no-color/strip_line.toml
+++ b/tests/fixtures/no-color/strip_line.toml
@@ -1,6 +1,7 @@
 [message]
-title = { level = "Error", label = "mismatched types" }
+level = "Error"
 id = "E0308"
+title = "mismatched types"
 
 [[message.snippets]]
 source = "                                                                                                                                                                                    let _: () = 42;"
diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml
index d7daae0..e3f7482 100644
--- a/tests/fixtures/no-color/strip_line_char.toml
+++ b/tests/fixtures/no-color/strip_line_char.toml
@@ -1,6 +1,7 @@
 [message]
-title = { level = "Error", label = "mismatched types" }
+level = "Error"
 id = "E0308"
+title = "mismatched types" 
 
 [[message.snippets]]
 source = "                                                                                                                                                                                    let _: () = 42ñ"
diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml
index 3afb6ba..2985177 100644
--- a/tests/fixtures/no-color/strip_line_non_ws.toml
+++ b/tests/fixtures/no-color/strip_line_non_ws.toml
@@ -1,6 +1,7 @@
 [message]
-title = { level = "Error", label = "mismatched types" }
+level = "Error"
 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 _: () = ();"
diff --git a/tests/formatter.rs b/tests/formatter.rs
index ebf7604..3224175 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,8 +1,8 @@
-use annotate_snippets::{Label, Message, Renderer, Snippet};
+use annotate_snippets::{Label, Level, Renderer, Snippet};
 
 #[test]
 fn test_i_29() {
-    let snippets = Message::error("oops").snippet(
+    let snippets = Level::Error.title("oops").snippet(
         Snippet::new("First line\r\nSecond oops line")
             .origin("<current file>")
             .annotation(Label::error("oops").span(19..23))
@@ -22,7 +22,7 @@ fn test_i_29() {
 
 #[test]
 fn test_point_to_double_width_characters() {
-    let snippets = Message::error("").snippet(
+    let snippets = Level::Error.title("").snippet(
         Snippet::new("こんにちは、世界")
             .origin("<current file>")
             .annotation(Label::error("world").span(12..16)),
@@ -41,7 +41,7 @@ fn test_point_to_double_width_characters() {
 
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
-    let snippets = Message::error("").snippet(
+    let snippets = Level::Error.title("").snippet(
         Snippet::new("おはよう\nございます")
             .origin("<current file>")
             .annotation(Label::error("Good morning").span(4..15)),
@@ -62,7 +62,7 @@ fn test_point_to_double_width_characters_across_lines() {
 
 #[test]
 fn test_point_to_double_width_characters_multiple() {
-    let snippets = Message::error("").snippet(
+    let snippets = Level::Error.title("").snippet(
         Snippet::new("お寿司\n食べたい🍣")
             .origin("<current file>")
             .annotation(Label::error("Sushi1").span(0..6))
@@ -84,7 +84,7 @@ fn test_point_to_double_width_characters_multiple() {
 
 #[test]
 fn test_point_to_double_width_characters_mixed() {
-    let snippets = Message::error("").snippet(
+    let snippets = Level::Error.title("").snippet(
         Snippet::new("こんにちは、新しいWorld!")
             .origin("<current file>")
             .annotation(Label::error("New world").span(12..23)),

From 86823c4b3aedf5cad62b871d33b6aa6be56a1323 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 15:59:45 -0500
Subject: [PATCH 123/302] fix!: Rename Snippet::new to Snippet::source

---
 benches/simple.rs             |  2 +-
 examples/expected_type.rs     |  2 +-
 examples/footer.rs            |  2 +-
 examples/format.rs            |  2 +-
 examples/multislice.rs        | 12 ++++++++++--
 src/renderer/display_list.rs  | 12 ++++++------
 src/renderer/mod.rs           |  4 ++--
 src/snippet.rs                |  6 +++---
 tests/fixtures/deserialize.rs |  2 +-
 tests/formatter.rs            | 10 +++++-----
 10 files changed, 31 insertions(+), 23 deletions(-)

diff --git a/benches/simple.rs b/benches/simple.rs
index 59d02a0..ab57302 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -30,7 +30,7 @@ fn create_snippet(renderer: Renderer) {
         }
     }"#;
     let message = Level::Error.title("mismatched types").id("E0308").snippet(
-        Snippet::new(source)
+        Snippet::source(source)
             .line_start(51)
             .origin("src/format.rs")
             .annotation(
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 3938d5f..942728f 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -6,7 +6,7 @@ fn main() {
                     ,
                 range: <22, 25>,"#;
     let message = Level::Error.title("expected type, found `22`").snippet(
-        Snippet::new(source)
+        Snippet::source(source)
             .line_start(26)
             .origin("examples/footer.rs")
             .fold(true)
diff --git a/examples/footer.rs b/examples/footer.rs
index e873546..858f811 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -5,7 +5,7 @@ fn main() {
         .title("mismatched types")
         .id("E0308")
         .snippet(
-            Snippet::new("        slices: vec![\"A\",")
+            Snippet::source("        slices: vec![\"A\",")
                 .line_start(13)
                 .origin("src/multislice.rs")
                 .annotation(
diff --git a/examples/format.rs b/examples/format.rs
index 7d19b87..34a76f4 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -24,7 +24,7 @@ fn main() {
         }
     }"#;
     let message = Level::Error.title("mismatched types").id("E0308").snippet(
-        Snippet::new(source)
+        Snippet::source(source)
             .line_start(51)
             .origin("src/format.rs")
             .annotation(
diff --git a/examples/multislice.rs b/examples/multislice.rs
index 38afbd4..ea31bbd 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -3,8 +3,16 @@ use annotate_snippets::{Level, Renderer, Snippet};
 fn main() {
     let message = Level::Error
         .title("mismatched types")
-        .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs"))
-        .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs"));
+        .snippet(
+            Snippet::source("Foo")
+                .line_start(51)
+                .origin("src/format.rs"),
+        )
+        .snippet(
+            Snippet::source("Faa")
+                .line_start(129)
+                .origin("src/display.rs"),
+        );
 
     let renderer = Renderer::styled();
     anstream::println!("{}", renderer.render(message));
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 2ba7f83..b4c50a9 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1236,7 +1236,7 @@ mod tests {
         let source = [line_1, line_2].join("\n");
         let input = snippet::Level::Error
             .title("")
-            .snippet(snippet::Snippet::new(&source).line_start(5402));
+            .snippet(snippet::Snippet::source(&source).line_start(5402));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1289,12 +1289,12 @@ mod tests {
         let input = snippet::Level::Error
             .title("")
             .snippet(
-                snippet::Snippet::new(src_0)
+                snippet::Snippet::source(src_0)
                     .line_start(5402)
                     .origin("file1.rs"),
             )
             .snippet(
-                snippet::Snippet::new(src_1)
+                snippet::Snippet::source(src_1)
                     .line_start(2)
                     .origin("file2.rs"),
             );
@@ -1369,7 +1369,7 @@ mod tests {
         // In line 2
         let range = 22..24;
         let input = snippet::Level::Error.title("").snippet(
-            snippet::Snippet::new(&source)
+            snippet::Snippet::source(&source)
                 .line_start(5402)
                 .annotation(snippet::Label::info("Test annotation").span(range.clone())),
         );
@@ -1476,7 +1476,7 @@ mod tests {
         let source = "short";
         let label = "label";
         let input = snippet::Level::Error.title("").snippet(
-            snippet::Snippet::new(source)
+            snippet::Snippet::source(source)
                 .line_start(0)
                 .annotation(snippet::Label::error(label).span(0..source.len() + 2)),
         );
@@ -1486,7 +1486,7 @@ mod tests {
     #[test]
     fn test_i_29() {
         let snippets = snippet::Level::Error.title("oops").snippet(
-            snippet::Snippet::new("First line\r\nSecond oops line")
+            snippet::Snippet::source("First line\r\nSecond oops line")
                 .line_start(1)
                 .origin("<current file>")
                 .fold(true)
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 29f3dae..5f9394d 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -4,8 +4,8 @@
 //! ```
 //! use annotate_snippets::{Renderer, Snippet, Level};
 //! let snippet = Level::Error.title("mismatched types")
-//!     .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs"))
-//!     .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs"));
+//!     .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs"))
+//!     .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
 //!
 //!  let renderer = Renderer::styled();
 //!  println!("{}", renderer.render(snippet));
diff --git a/src/snippet.rs b/src/snippet.rs
index d2906d4..e0a05ea 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -6,8 +6,8 @@
 //! use annotate_snippets::*;
 //!
 //! Level::Error.title("mismatched types")
-//!     .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs"))
-//!     .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs"));
+//!     .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs"))
+//!     .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
 //! ```
 
 use std::ops::Range;
@@ -100,7 +100,7 @@ pub struct Snippet<'a> {
 }
 
 impl<'a> Snippet<'a> {
-    pub fn new(source: &'a str) -> Self {
+    pub fn source(source: &'a str) -> Self {
         Self {
             origin: None,
             line_start: 1,
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 7e64945..f5bb411 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -107,7 +107,7 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
             annotations,
             fold,
         } = val;
-        let mut snippet = Snippet::new(source).line_start(line_start).fold(fold);
+        let mut snippet = Snippet::source(source).line_start(line_start).fold(fold);
         if let Some(origin) = origin {
             snippet = snippet.origin(origin)
         }
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 3224175..c905b1c 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -3,7 +3,7 @@ use annotate_snippets::{Label, Level, Renderer, Snippet};
 #[test]
 fn test_i_29() {
     let snippets = Level::Error.title("oops").snippet(
-        Snippet::new("First line\r\nSecond oops line")
+        Snippet::source("First line\r\nSecond oops line")
             .origin("<current file>")
             .annotation(Label::error("oops").span(19..23))
             .fold(true),
@@ -23,7 +23,7 @@ fn test_i_29() {
 #[test]
 fn test_point_to_double_width_characters() {
     let snippets = Level::Error.title("").snippet(
-        Snippet::new("こんにちは、世界")
+        Snippet::source("こんにちは、世界")
             .origin("<current file>")
             .annotation(Label::error("world").span(12..16)),
     );
@@ -42,7 +42,7 @@ fn test_point_to_double_width_characters() {
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
     let snippets = Level::Error.title("").snippet(
-        Snippet::new("おはよう\nございます")
+        Snippet::source("おはよう\nございます")
             .origin("<current file>")
             .annotation(Label::error("Good morning").span(4..15)),
     );
@@ -63,7 +63,7 @@ fn test_point_to_double_width_characters_across_lines() {
 #[test]
 fn test_point_to_double_width_characters_multiple() {
     let snippets = Level::Error.title("").snippet(
-        Snippet::new("お寿司\n食べたい🍣")
+        Snippet::source("お寿司\n食べたい🍣")
             .origin("<current file>")
             .annotation(Label::error("Sushi1").span(0..6))
             .annotation(Label::note("Sushi2").span(11..15)),
@@ -85,7 +85,7 @@ fn test_point_to_double_width_characters_multiple() {
 #[test]
 fn test_point_to_double_width_characters_mixed() {
     let snippets = Level::Error.title("").snippet(
-        Snippet::new("こんにちは、新しいWorld!")
+        Snippet::source("こんにちは、新しいWorld!")
             .origin("<current file>")
             .annotation(Label::error("New world").span(12..23)),
     );

From c821084068a1acd2688b6c8d0b3423e143d359e2 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 16:13:40 -0500
Subject: [PATCH 124/302] fix!: Make Annotation labels optional

---
 benches/simple.rs             | 12 +++++++++---
 examples/expected_type.rs     | 11 +++++------
 examples/footer.rs            | 30 ++++++++++++++----------------
 examples/format.rs            | 12 +++++++++---
 src/renderer/display_list.rs  | 14 +++++++++-----
 src/snippet.rs                | 29 ++++++++++++++++++-----------
 tests/fixtures/deserialize.rs |  2 +-
 tests/formatter.rs            | 14 +++++++-------
 8 files changed, 72 insertions(+), 52 deletions(-)

diff --git a/benches/simple.rs b/benches/simple.rs
index ab57302..fccb70b 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -4,7 +4,7 @@ extern crate criterion;
 
 use criterion::{black_box, Criterion};
 
-use annotate_snippets::{Label, Level, Renderer, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 fn create_snippet(renderer: Renderer) {
     let source = r#") -> Option<String> {
@@ -34,9 +34,15 @@ fn create_snippet(renderer: Renderer) {
             .line_start(51)
             .origin("src/format.rs")
             .annotation(
-                Label::warning("expected `Option<String>` because of return type").span(5..19),
+                Level::Warning
+                    .span(5..19)
+                    .label("expected `Option<String>` because of return type"),
             )
-            .annotation(Label::error("expected enum `std::option::Option`").span(26..724)),
+            .annotation(
+                Level::Error
+                    .span(26..724)
+                    .label("expected enum `std::option::Option`"),
+            ),
     );
 
     let _result = renderer.render(message).to_string();
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 942728f..0184dee 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Label, Level, Renderer, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 fn main() {
     let source = r#"                annotations: vec![SourceAnnotation {
@@ -11,12 +11,11 @@ fn main() {
             .origin("examples/footer.rs")
             .fold(true)
             .annotation(
-                Label::error(
-                    "expected struct `annotate_snippets::snippet::Slice`, found reference",
-                )
-                .span(193..195),
+                Level::Error
+                    .span(193..195)
+                    .label("expected struct `annotate_snippets::snippet::Slice`, found reference"),
             )
-            .annotation(Label::info("while parsing this struct").span(34..50)),
+            .annotation(Level::Info.span(34..50).label("while parsing this struct")),
     );
 
     let renderer = Renderer::styled();
diff --git a/examples/footer.rs b/examples/footer.rs
index 858f811..8b4d078 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,23 +1,21 @@
 use annotate_snippets::{Label, Level, Renderer, Snippet};
 
 fn main() {
-    let message = Level::Error
-        .title("mismatched types")
-        .id("E0308")
-        .snippet(
-            Snippet::source("        slices: vec![\"A\",")
-                .line_start(13)
-                .origin("src/multislice.rs")
-                .annotation(
-                    Label::error(
+    let message =
+        Level::Error
+            .title("mismatched types")
+            .id("E0308")
+            .snippet(
+                Snippet::source("        slices: vec![\"A\",")
+                    .line_start(13)
+                    .origin("src/multislice.rs")
+                    .annotation(Level::Error.span(21..24).label(
                         "expected struct `annotate_snippets::snippet::Slice`, found reference",
-                    )
-                    .span(21..24),
-                ),
-        )
-        .footer(Label::note(
-            "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
-        ));
+                    )),
+            )
+            .footer(Label::note(
+                "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
+            ));
 
     let renderer = Renderer::styled();
     anstream::println!("{}", renderer.render(message));
diff --git a/examples/format.rs b/examples/format.rs
index 34a76f4..1606777 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Label, Level, Renderer, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 fn main() {
     let source = r#") -> Option<String> {
@@ -28,9 +28,15 @@ fn main() {
             .line_start(51)
             .origin("src/format.rs")
             .annotation(
-                Label::warning("expected `Option<String>` because of return type").span(5..19),
+                Level::Warning
+                    .span(5..19)
+                    .label("expected `Option<String>` because of return type"),
             )
-            .annotation(Label::error("expected enum `std::option::Option`").span(26..724)),
+            .annotation(
+                Level::Error
+                    .span(26..724)
+                    .label("expected enum `std::option::Option`"),
+            ),
     );
 
     let renderer = Renderer::styled();
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index b4c50a9..d54282f 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1040,7 +1040,7 @@ fn format_body(
                                 annotation: Annotation {
                                     annotation_type,
                                     id: None,
-                                    label: format_label(Some(annotation.label), None),
+                                    label: format_label(annotation.label, None),
                                 },
                                 range,
                                 annotation_type: DisplayAnnotationType::from(annotation.level),
@@ -1134,7 +1134,7 @@ fn format_body(
                                 annotation: Annotation {
                                     annotation_type,
                                     id: None,
-                                    label: format_label(Some(annotation.label), None),
+                                    label: format_label(annotation.label, None),
                                 },
                                 range,
                                 annotation_type: DisplayAnnotationType::from(annotation.level),
@@ -1371,7 +1371,11 @@ mod tests {
         let input = snippet::Level::Error.title("").snippet(
             snippet::Snippet::source(&source)
                 .line_start(5402)
-                .annotation(snippet::Label::info("Test annotation").span(range.clone())),
+                .annotation(
+                    snippet::Level::Info
+                        .span(range.clone())
+                        .label("Test annotation"),
+                ),
         );
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
@@ -1478,7 +1482,7 @@ mod tests {
         let input = snippet::Level::Error.title("").snippet(
             snippet::Snippet::source(source)
                 .line_start(0)
-                .annotation(snippet::Label::error(label).span(0..source.len() + 2)),
+                .annotation(snippet::Level::Error.span(0..source.len() + 2).label(label)),
         );
         let _ = DisplayList::new(input, &STYLESHEET, false, None);
     }
@@ -1490,7 +1494,7 @@ mod tests {
                 .line_start(1)
                 .origin("<current file>")
                 .fold(true)
-                .annotation(snippet::Label::error("oops").span(19..23)),
+                .annotation(snippet::Level::Error.span(19..23).label("oops")),
         );
 
         let expected = from_display_lines(vec![
diff --git a/src/snippet.rs b/src/snippet.rs
index e0a05ea..7b21402 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -73,15 +73,6 @@ impl<'a> Label<'a> {
         self.label = label;
         self
     }
-
-    /// Create a [`Annotation`] with the given span for a [`Snippet`]
-    pub fn span(&self, span: Range<usize>) -> Annotation<'a> {
-        Annotation {
-            range: span,
-            label: self.label,
-            level: self.level,
-        }
-    }
 }
 
 /// Structure containing the slice of text to be annotated and
@@ -133,15 +124,22 @@ impl<'a> Snippet<'a> {
 
 /// An annotation for a [`Snippet`].
 ///
-/// This gets created by [`Label::span`].
+/// See [`Level::span`] to create a [`Annotation`]
 #[derive(Debug)]
 pub struct Annotation<'a> {
     /// The byte range of the annotation in the `source` string
     pub(crate) range: Range<usize>,
-    pub(crate) label: &'a str,
+    pub(crate) label: Option<&'a str>,
     pub(crate) level: Level,
 }
 
+impl<'a> Annotation<'a> {
+    pub fn label(mut self, label: &'a str) -> Self {
+        self.label = Some(label);
+        self
+    }
+}
+
 /// Types of annotations.
 #[derive(Debug, Clone, Copy, PartialEq)]
 pub enum Level {
@@ -164,4 +162,13 @@ impl Level {
             footer: vec![],
         }
     }
+
+    /// Create a [`Annotation`] with the given span for a [`Snippet`]
+    pub fn span<'a>(self, span: Range<usize>) -> Annotation<'a> {
+        Annotation {
+            range: span,
+            label: None,
+            level: self,
+        }
+    }
 }
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index f5bb411..a22f215 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -147,7 +147,7 @@ impl<'a> From<AnnotationDef<'a>> for Annotation<'a> {
             label,
             level,
         } = val;
-        Label::new(level, label).span(range)
+        level.span(range).label(label)
     }
 }
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index c905b1c..76dcb27 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,11 +1,11 @@
-use annotate_snippets::{Label, Level, Renderer, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 #[test]
 fn test_i_29() {
     let snippets = Level::Error.title("oops").snippet(
         Snippet::source("First line\r\nSecond oops line")
             .origin("<current file>")
-            .annotation(Label::error("oops").span(19..23))
+            .annotation(Level::Error.span(19..23).label("oops"))
             .fold(true),
     );
     let expected = r#"error: oops
@@ -25,7 +25,7 @@ fn test_point_to_double_width_characters() {
     let snippets = Level::Error.title("").snippet(
         Snippet::source("こんにちは、世界")
             .origin("<current file>")
-            .annotation(Label::error("world").span(12..16)),
+            .annotation(Level::Error.span(12..16).label("world")),
     );
 
     let expected = r#"error
@@ -44,7 +44,7 @@ fn test_point_to_double_width_characters_across_lines() {
     let snippets = Level::Error.title("").snippet(
         Snippet::source("おはよう\nございます")
             .origin("<current file>")
-            .annotation(Label::error("Good morning").span(4..15)),
+            .annotation(Level::Error.span(4..15).label("Good morning")),
     );
 
     let expected = r#"error
@@ -65,8 +65,8 @@ fn test_point_to_double_width_characters_multiple() {
     let snippets = Level::Error.title("").snippet(
         Snippet::source("お寿司\n食べたい🍣")
             .origin("<current file>")
-            .annotation(Label::error("Sushi1").span(0..6))
-            .annotation(Label::note("Sushi2").span(11..15)),
+            .annotation(Level::Error.span(0..6).label("Sushi1"))
+            .annotation(Level::Note.span(11..15).label("Sushi2")),
     );
 
     let expected = r#"error
@@ -87,7 +87,7 @@ fn test_point_to_double_width_characters_mixed() {
     let snippets = Level::Error.title("").snippet(
         Snippet::source("こんにちは、新しいWorld!")
             .origin("<current file>")
-            .annotation(Label::error("New world").span(12..23)),
+            .annotation(Level::Error.span(12..23).label("New world")),
     );
 
     let expected = r#"error

From 4ceb316f8d9e9aefdc84c6dfe33d2c6618ee881b Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 11 Mar 2024 16:24:07 -0500
Subject: [PATCH 125/302] feat: Allow bulk-adding of snippets, footers,
 annotations

This will make it easier for diagnostic systems to convert their
messages to a `Message`
---
 src/snippet.rs                | 15 +++++++++++++++
 tests/fixtures/deserialize.rs | 14 +++-----------
 2 files changed, 18 insertions(+), 11 deletions(-)

diff --git a/src/snippet.rs b/src/snippet.rs
index 7b21402..9ce1031 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -34,10 +34,20 @@ impl<'a> Message<'a> {
         self
     }
 
+    pub fn snippets(mut self, slice: impl IntoIterator<Item = Snippet<'a>>) -> Self {
+        self.snippets.extend(slice);
+        self
+    }
+
     pub fn footer(mut self, footer: Label<'a>) -> Self {
         self.footer.push(footer);
         self
     }
+
+    pub fn footers(mut self, footer: impl IntoIterator<Item = Label<'a>>) -> Self {
+        self.footer.extend(footer);
+        self
+    }
 }
 
 pub struct Label<'a> {
@@ -116,6 +126,11 @@ impl<'a> Snippet<'a> {
         self
     }
 
+    pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self {
+        self.annotations.extend(annotation);
+        self
+    }
+
     pub fn fold(mut self, fold: bool) -> Self {
         self.fold = fold;
         self
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index a22f215..6bfe76f 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -42,12 +42,8 @@ impl<'a> From<MessageDef<'a>> for Message<'a> {
         if let Some(id) = id {
             message = message.id(id);
         }
-        message = snippets
-            .into_iter()
-            .fold(message, |message, snippet| message.snippet(snippet));
-        message = footer
-            .into_iter()
-            .fold(message, |message, label| message.footer(label));
+        message = message.snippets(snippets);
+        message = message.footers(footer);
         message
     }
 }
@@ -111,11 +107,7 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
         if let Some(origin) = origin {
             snippet = snippet.origin(origin)
         }
-        snippet = annotations
-            .into_iter()
-            .fold(snippet, |snippet, annotation| {
-                snippet.annotation(annotation)
-            });
+        snippet = snippet.annotations(annotations);
         snippet
     }
 }

From 9cdd503a0a52961b9536a460e3bbc325f6efdc69 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 12 Mar 2024 11:13:12 -0500
Subject: [PATCH 126/302] docs: Describe fold

---
 src/snippet.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/snippet.rs b/src/snippet.rs
index 9ce1031..6ba2d2d 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -131,6 +131,7 @@ impl<'a> Snippet<'a> {
         self
     }
 
+    /// Hide lines without [`Annotation`]s
     pub fn fold(mut self, fold: bool) -> Self {
         self.fold = fold;
         self

From 24c305668ceefca18633eca5f8c317fd3d10676f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 12 Mar 2024 14:49:02 -0500
Subject: [PATCH 127/302] refactor(render): Decouple title from Label

---
 src/renderer/display_list.rs | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index d54282f..b75c809 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -119,13 +119,7 @@ impl<'a> DisplayList<'a> {
     ) -> DisplayList<'a> {
         let mut body = vec![];
 
-        body.push(format_title(
-            snippet::Label {
-                level,
-                label: title,
-            },
-            id,
-        ));
+        body.push(format_title(level, id, title));
 
         for (idx, snippet) in snippets.into_iter().enumerate() {
             body.append(&mut format_slice(
@@ -740,12 +734,12 @@ fn format_label(
     result
 }
 
-fn format_title<'a>(title: snippet::Label<'a>, id: Option<&'a str>) -> DisplayLine<'a> {
+fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> {
     DisplayLine::Raw(DisplayRawLine::Annotation {
         annotation: Annotation {
-            annotation_type: DisplayAnnotationType::from(title.level),
+            annotation_type: DisplayAnnotationType::from(level),
             id,
-            label: format_label(Some(title.label), Some(DisplayTextStyle::Emphasis)),
+            label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
         },
         source_aligned: false,
         continuation: false,

From 568b1a30d93f9067ea20e1a61256b6484ba6b5d2 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 12 Mar 2024 14:52:28 -0500
Subject: [PATCH 128/302] refactor(render): Emphasize top-down order

---
 src/renderer/display_list.rs | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index b75c809..2bbc290 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -719,21 +719,6 @@ impl<'a> Iterator for CursorLines<'a> {
     }
 }
 
-fn format_label(
-    label: Option<&str>,
-    style: Option<DisplayTextStyle>,
-) -> Vec<DisplayTextFragment<'_>> {
-    let mut result = vec![];
-    if let Some(label) = label {
-        let element_style = style.unwrap_or(DisplayTextStyle::Regular);
-        result.push(DisplayTextFragment {
-            content: label,
-            style: element_style,
-        });
-    }
-    result
-}
-
 fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> {
     DisplayLine::Raw(DisplayRawLine::Annotation {
         annotation: Annotation {
@@ -762,6 +747,21 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> {
     result
 }
 
+fn format_label(
+    label: Option<&str>,
+    style: Option<DisplayTextStyle>,
+) -> Vec<DisplayTextFragment<'_>> {
+    let mut result = vec![];
+    if let Some(label) = label {
+        let element_style = style.unwrap_or(DisplayTextStyle::Regular);
+        result.push(DisplayTextFragment {
+            content: label,
+            style: element_style,
+        });
+    }
+    result
+}
+
 fn format_slice(
     snippet: snippet::Snippet<'_>,
     is_first: bool,

From 20842e5afa1540fb25e3dadab643bdd69c32c47c Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 12 Mar 2024 14:53:11 -0500
Subject: [PATCH 129/302] refactor(render): Update fn name for slice -> snippet

---
 src/renderer/display_list.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 2bbc290..5c2581c 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -122,7 +122,7 @@ impl<'a> DisplayList<'a> {
         body.push(format_title(level, id, title));
 
         for (idx, snippet) in snippets.into_iter().enumerate() {
-            body.append(&mut format_slice(
+            body.append(&mut format_snippet(
                 snippet,
                 idx == 0,
                 !footer.is_empty(),
@@ -762,7 +762,7 @@ fn format_label(
     result
 }
 
-fn format_slice(
+fn format_snippet(
     snippet: snippet::Snippet<'_>,
     is_first: bool,
     has_footer: bool,

From 25d519a5071a2175631fef5ce10ade7f72b22192 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 12 Mar 2024 15:01:52 -0500
Subject: [PATCH 130/302] refactor(render): Pull out Message formatting

---
 src/renderer/display_list.rs | 55 +++++++++++++++++++++---------------
 1 file changed, 32 insertions(+), 23 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 5c2581c..c3890ba 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -106,33 +106,12 @@ impl<'a> DisplayList<'a> {
     const WARNING_TXT: &'static str = "warning";
 
     pub(crate) fn new(
-        snippet::Message {
-            level,
-            id,
-            title,
-            footer,
-            snippets,
-        }: snippet::Message<'a>,
+        message: snippet::Message<'a>,
         stylesheet: &'a Stylesheet,
         anonymized_line_numbers: bool,
         margin: Option<Margin>,
     ) -> DisplayList<'a> {
-        let mut body = vec![];
-
-        body.push(format_title(level, id, title));
-
-        for (idx, snippet) in snippets.into_iter().enumerate() {
-            body.append(&mut format_snippet(
-                snippet,
-                idx == 0,
-                !footer.is_empty(),
-                margin,
-            ));
-        }
-
-        for annotation in footer {
-            body.append(&mut format_footer(annotation));
-        }
+        let body = format_message(message, margin);
 
         Self {
             body,
@@ -719,6 +698,36 @@ impl<'a> Iterator for CursorLines<'a> {
     }
 }
 
+fn format_message<'a>(
+    snippet::Message {
+        level,
+        id,
+        title,
+        footer,
+        snippets,
+    }: snippet::Message<'a>,
+    margin: Option<Margin>,
+) -> Vec<DisplayLine<'a>> {
+    let mut body = vec![];
+
+    body.push(format_title(level, id, title));
+
+    for (idx, snippet) in snippets.into_iter().enumerate() {
+        body.append(&mut format_snippet(
+            snippet,
+            idx == 0,
+            !footer.is_empty(),
+            margin,
+        ));
+    }
+
+    for annotation in footer {
+        body.append(&mut format_footer(annotation));
+    }
+
+    body
+}
+
 fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> {
     DisplayLine::Raw(DisplayRawLine::Annotation {
         annotation: Annotation {

From ce736be5742f873a03627cad0340964e2d44c61d Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 12 Mar 2024 15:13:10 -0500
Subject: [PATCH 131/302] fix!: Generalize footer to be any `Message`

Fixes #96
---
 examples/footer.rs            |  4 ++--
 src/renderer/display_list.rs  | 31 ++++++++++++++++----------
 src/snippet.rs                | 41 +++--------------------------------
 tests/fixtures/deserialize.rs | 24 +++-----------------
 4 files changed, 28 insertions(+), 72 deletions(-)

diff --git a/examples/footer.rs b/examples/footer.rs
index 8b4d078..3580905 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Label, Level, Renderer, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 fn main() {
     let message =
@@ -13,7 +13,7 @@ fn main() {
                         "expected struct `annotate_snippets::snippet::Slice`, found reference",
                     )),
             )
-            .footer(Label::note(
+            .footer(Level::Note.title(
                 "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
             ));
 
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index c3890ba..7403636 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -111,7 +111,7 @@ impl<'a> DisplayList<'a> {
         anonymized_line_numbers: bool,
         margin: Option<Margin>,
     ) -> DisplayList<'a> {
-        let body = format_message(message, margin);
+        let body = format_message(message, margin, true);
 
         Self {
             body,
@@ -698,19 +698,24 @@ impl<'a> Iterator for CursorLines<'a> {
     }
 }
 
-fn format_message<'a>(
+fn format_message(
     snippet::Message {
         level,
         id,
         title,
         footer,
         snippets,
-    }: snippet::Message<'a>,
+    }: snippet::Message<'_>,
     margin: Option<Margin>,
-) -> Vec<DisplayLine<'a>> {
+    primary: bool,
+) -> Vec<DisplayLine<'_>> {
     let mut body = vec![];
 
-    body.push(format_title(level, id, title));
+    if !snippets.is_empty() || primary {
+        body.push(format_title(level, id, title));
+    } else {
+        body.append(&mut format_footer(level, id, title));
+    }
 
     for (idx, snippet) in snippets.into_iter().enumerate() {
         body.append(&mut format_snippet(
@@ -722,7 +727,7 @@ fn format_message<'a>(
     }
 
     for annotation in footer {
-        body.append(&mut format_footer(annotation));
+        body.append(&mut format_message(annotation, margin, false));
     }
 
     body
@@ -740,13 +745,17 @@ fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) ->
     })
 }
 
-fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> {
+fn format_footer<'a>(
+    level: crate::Level,
+    id: Option<&'a str>,
+    label: &'a str,
+) -> Vec<DisplayLine<'a>> {
     let mut result = vec![];
-    for (i, line) in footer.label.lines().enumerate() {
+    for (i, line) in label.lines().enumerate() {
         result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
-                annotation_type: DisplayAnnotationType::from(footer.level),
-                id: None,
+                annotation_type: DisplayAnnotationType::from(level),
+                id,
                 label: format_label(Some(line), None),
             },
             source_aligned: true,
@@ -1447,7 +1456,7 @@ mod tests {
     fn test_format_label() {
         let input = snippet::Level::Error
             .title("")
-            .footer(snippet::Label::error("This __is__ a title"));
+            .footer(snippet::Level::Error.title("This __is__ a title"));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
diff --git a/src/snippet.rs b/src/snippet.rs
index 6ba2d2d..e7d4bef 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -20,7 +20,7 @@ pub struct Message<'a> {
     pub(crate) id: Option<&'a str>,
     pub(crate) title: &'a str,
     pub(crate) snippets: Vec<Snippet<'a>>,
-    pub(crate) footer: Vec<Label<'a>>,
+    pub(crate) footer: Vec<Message<'a>>,
 }
 
 impl<'a> Message<'a> {
@@ -39,52 +39,17 @@ impl<'a> Message<'a> {
         self
     }
 
-    pub fn footer(mut self, footer: Label<'a>) -> Self {
+    pub fn footer(mut self, footer: Message<'a>) -> Self {
         self.footer.push(footer);
         self
     }
 
-    pub fn footers(mut self, footer: impl IntoIterator<Item = Label<'a>>) -> Self {
+    pub fn footers(mut self, footer: impl IntoIterator<Item = Message<'a>>) -> Self {
         self.footer.extend(footer);
         self
     }
 }
 
-pub struct Label<'a> {
-    pub(crate) level: Level,
-    pub(crate) label: &'a str,
-}
-
-impl<'a> Label<'a> {
-    pub fn new(level: Level, label: &'a str) -> Self {
-        Self { level, label }
-    }
-    pub fn error(label: &'a str) -> Self {
-        Self::new(Level::Error, label)
-    }
-
-    pub fn warning(label: &'a str) -> Self {
-        Self::new(Level::Warning, label)
-    }
-
-    pub fn info(label: &'a str) -> Self {
-        Self::new(Level::Info, label)
-    }
-
-    pub fn note(label: &'a str) -> Self {
-        Self::new(Level::Note, label)
-    }
-
-    pub fn help(label: &'a str) -> Self {
-        Self::new(Level::Help, label)
-    }
-
-    pub fn label(mut self, label: &'a str) -> Self {
-        self.label = label;
-        self
-    }
-}
-
 /// Structure containing the slice of text to be annotated and
 /// basic information about the location of the slice.
 ///
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 6bfe76f..165c341 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -1,7 +1,7 @@
 use serde::{Deserialize, Deserializer, Serialize};
 use std::ops::Range;
 
-use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Snippet};
+use annotate_snippets::{renderer::Margin, Annotation, Level, Message, Renderer, Snippet};
 
 #[derive(Deserialize)]
 pub struct Fixture<'a> {
@@ -20,10 +20,9 @@ pub struct MessageDef<'a> {
     #[serde(default)]
     #[serde(borrow)]
     pub id: Option<&'a str>,
-    #[serde(deserialize_with = "deserialize_labels")]
     #[serde(default)]
     #[serde(borrow)]
-    pub footer: Vec<Label<'a>>,
+    pub footer: Vec<MessageDef<'a>>,
     #[serde(deserialize_with = "deserialize_snippets")]
     #[serde(borrow)]
     pub snippets: Vec<Snippet<'a>>,
@@ -43,28 +42,11 @@ impl<'a> From<MessageDef<'a>> for Message<'a> {
             message = message.id(id);
         }
         message = message.snippets(snippets);
-        message = message.footers(footer);
+        message = message.footers(footer.into_iter().map(Into::into));
         message
     }
 }
 
-fn deserialize_labels<'de, D>(deserializer: D) -> Result<Vec<Label<'de>>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    #[derive(Deserialize)]
-    struct Wrapper<'a>(
-        #[serde(with = "LabelDef")]
-        #[serde(borrow)]
-        LabelDef<'a>,
-    );
-
-    let v = Vec::deserialize(deserializer)?;
-    Ok(v.into_iter()
-        .map(|Wrapper(a)| Label::new(a.level, a.label))
-        .collect())
-}
-
 fn deserialize_snippets<'de, D>(deserializer: D) -> Result<Vec<Snippet<'de>>, D::Error>
 where
     D: Deserializer<'de>,

From 535e146651e68f183682083cca6bb58c37e8b481 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 12 Mar 2024 15:16:05 -0500
Subject: [PATCH 132/302] refactor(render): Prefer 'extend' over 'append'

---
 src/renderer/display_list.rs | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 7403636..d0444a3 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -714,11 +714,11 @@ fn format_message(
     if !snippets.is_empty() || primary {
         body.push(format_title(level, id, title));
     } else {
-        body.append(&mut format_footer(level, id, title));
+        body.extend(format_footer(level, id, title));
     }
 
     for (idx, snippet) in snippets.into_iter().enumerate() {
-        body.append(&mut format_snippet(
+        body.extend(format_snippet(
             snippet,
             idx == 0,
             !footer.is_empty(),
@@ -727,7 +727,7 @@ fn format_message(
     }
 
     for annotation in footer {
-        body.append(&mut format_message(annotation, margin, false));
+        body.extend(format_message(annotation, margin, false));
     }
 
     body
@@ -789,14 +789,14 @@ fn format_snippet(
     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 mut body = format_body(snippet, need_empty_header, has_footer, margin);
+    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![];
 
     if let Some(header) = header {
         result.push(header);
     }
-    result.append(&mut body);
+    result.extend(body);
     result
 }
 

From aed54bb789f9830996d08181634542aa0b53b1b8 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sun, 10 Mar 2024 12:45:03 -0600
Subject: [PATCH 133/302] fix: Make spans work with single width emojis

---
 Cargo.lock                                    | 14 +---
 Cargo.toml                                    |  1 -
 src/renderer/display_list.rs                  | 76 ++++++++-----------
 .../no-color/ensure-emoji-highlight-width.svg | 33 ++++++++
 .../ensure-emoji-highlight-width.toml         | 15 ++++
 tests/formatter.rs                            | 10 +--
 6 files changed, 85 insertions(+), 64 deletions(-)
 create mode 100644 tests/fixtures/no-color/ensure-emoji-highlight-width.svg
 create mode 100644 tests/fixtures/no-color/ensure-emoji-highlight-width.toml

diff --git a/Cargo.lock b/Cargo.lock
index 5672f61..3bfb779 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -26,7 +26,6 @@ dependencies = [
  "criterion",
  "difference",
  "glob",
- "itertools 0.12.1",
  "serde",
  "snapbox",
  "toml",
@@ -243,7 +242,7 @@ dependencies = [
  "clap",
  "criterion-plot",
  "is-terminal",
- "itertools 0.10.5",
+ "itertools",
  "num-traits",
  "once_cell",
  "oorandom",
@@ -264,7 +263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
 dependencies = [
  "cast",
- "itertools 0.10.5",
+ "itertools",
 ]
 
 [[package]]
@@ -449,15 +448,6 @@ dependencies = [
  "either",
 ]
 
-[[package]]
-name = "itertools"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
-dependencies = [
- "either",
-]
-
 [[package]]
 name = "itoa"
 version = "1.0.9"
diff --git a/Cargo.toml b/Cargo.toml
index 5fbfc0b..f97dddb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,7 +25,6 @@ maintenance = { status = "actively-developed" }
 
 [dependencies]
 anstyle = "1.0.4"
-itertools = "0.12.1"
 unicode-width = "0.1.11"
 
 [dev-dependencies]
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index d0444a3..5cc1e2c 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -32,8 +32,6 @@
 //!
 //! The above snippet has been built out of the following structure:
 use crate::snippet;
-use itertools::FoldWhile::{Continue, Done};
-use itertools::Itertools;
 use std::fmt::{Display, Write};
 use std::ops::Range;
 use std::{cmp, fmt};
@@ -830,20 +828,7 @@ fn format_header<'a>(
             } = item
             {
                 if main_range >= range.0 && main_range <= range.1 {
-                    let char_column = text
-                        .chars()
-                        .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                        .chain(std::iter::once(1)) // treat the end of line as single-width
-                        .enumerate()
-                        .fold_while((0, 0), |(count, acc), (i, width)| {
-                            if acc <= main_range - range.0 {
-                                Continue((i, acc + width))
-                            } else {
-                                Done((count, acc))
-                            }
-                        })
-                        .into_inner()
-                        .0;
+                    let char_column = text[0..(main_range - range.0)].chars().count();
                     col = char_column + 1;
                     line_offset = lineno.unwrap_or(1);
                     break;
@@ -984,18 +969,11 @@ fn format_body(
     let mut body = vec![];
     let mut current_line = snippet.line_start;
     let mut current_index = 0;
-    let mut line_info = vec![];
 
-    struct LineInfo {
-        line_start_index: usize,
-        line_end_index: usize,
-    }
-
-    for (line, end_line) in CursorLines::new(snippet.source) {
-        let line_length: usize = line
-            .chars()
-            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-            .sum();
+    let mut annotation_line_count = 0;
+    let mut annotations = snippet.annotations;
+    for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() {
+        let line_length: usize = line.len();
         let line_range = (current_index, current_index + line_length);
         body.push(DisplayLine::Source {
             lineno: Some(current_line),
@@ -1005,24 +983,11 @@ fn format_body(
                 range: line_range,
             },
         });
-        line_info.push(LineInfo {
-            line_start_index: line_range.0,
-            line_end_index: line_range.1,
-        });
+        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 mut annotation_line_count = 0;
-    let mut annotations = snippet.annotations;
-    for (
-        idx,
-        LineInfo {
-            line_start_index,
-            line_end_index,
-        },
-    ) in line_info.into_iter().enumerate()
-    {
         let margin_left = margin
             .map(|m| m.left(line_end_index - line_start_index))
             .unwrap_or_default();
@@ -1040,8 +1005,20 @@ fn format_body(
                     if start >= line_start_index && end <= line_end_index
                         || start == line_end_index && end - start <= 1 =>
                 {
-                    let annotation_start_col = start - line_start_index - margin_left;
-                    let annotation_end_col = end - line_start_index - margin_left;
+                    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,
@@ -1080,7 +1057,10 @@ fn format_body(
                             });
                         }
                     } else {
-                        let annotation_start_col = start - line_start_index;
+                        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,
@@ -1132,7 +1112,11 @@ fn format_body(
                         });
                     }
 
-                    let end_mark = (end - line_start_index).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,
diff --git a/tests/fixtures/no-color/ensure-emoji-highlight-width.svg b/tests/fixtures/no-color/ensure-emoji-highlight-width.svg
new file mode 100644
index 0000000..0840805
--- /dev/null
+++ b/tests/fixtures/no-color/ensure-emoji-highlight-width.svg
@@ -0,0 +1,33 @@
+<svg width="1356px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; &lt;file&gt;:7:1</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>7 | "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>  |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/ensure-emoji-highlight-width.toml b/tests/fixtures/no-color/ensure-emoji-highlight-width.toml
new file mode 100644
index 0000000..7af05ff
--- /dev/null
+++ b/tests/fixtures/no-color/ensure-emoji-highlight-width.toml
@@ -0,0 +1,15 @@
+[message]
+title = "invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)"
+level = "Error"
+
+
+[[message.snippets]]
+source = """
+"haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }
+"""
+line_start = 7
+origin = "<file>"
+[[message.snippets.annotations]]
+label = ""
+level = "Error"
+range = [0, 35]
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 76dcb27..035492a 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -25,7 +25,7 @@ fn test_point_to_double_width_characters() {
     let snippets = Level::Error.title("").snippet(
         Snippet::source("こんにちは、世界")
             .origin("<current file>")
-            .annotation(Level::Error.span(12..16).label("world")),
+            .annotation(Level::Error.span(18..24).label("world")),
     );
 
     let expected = r#"error
@@ -44,7 +44,7 @@ fn test_point_to_double_width_characters_across_lines() {
     let snippets = Level::Error.title("").snippet(
         Snippet::source("おはよう\nございます")
             .origin("<current file>")
-            .annotation(Level::Error.span(4..15).label("Good morning")),
+            .annotation(Level::Error.span(6..22).label("Good morning")),
     );
 
     let expected = r#"error
@@ -65,8 +65,8 @@ fn test_point_to_double_width_characters_multiple() {
     let snippets = Level::Error.title("").snippet(
         Snippet::source("お寿司\n食べたい🍣")
             .origin("<current file>")
-            .annotation(Level::Error.span(0..6).label("Sushi1"))
-            .annotation(Level::Note.span(11..15).label("Sushi2")),
+            .annotation(Level::Error.span(0..9).label("Sushi1"))
+            .annotation(Level::Note.span(16..22).label("Sushi2")),
     );
 
     let expected = r#"error
@@ -87,7 +87,7 @@ fn test_point_to_double_width_characters_mixed() {
     let snippets = Level::Error.title("").snippet(
         Snippet::source("こんにちは、新しいWorld!")
             .origin("<current file>")
-            .annotation(Level::Error.span(12..23).label("New world")),
+            .annotation(Level::Error.span(18..32).label("New world")),
     );
 
     let expected = r#"error

From 449feec0a82dd1da8cca2d8da7882aa3808a5f15 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 12 Mar 2024 20:24:09 -0500
Subject: [PATCH 134/302] test(render): Migrate to snapshot tests

---
 Cargo.lock                   |   4 +-
 Cargo.toml                   |   2 +-
 src/renderer/display_list.rs | 754 +++++++++++++++++++++--------------
 3 files changed, 463 insertions(+), 297 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 5672f61..0fb37c3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -748,9 +748,9 @@ checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
 
 [[package]]
 name = "snapbox"
-version = "0.5.8"
+version = "0.5.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6de4a00e16c1e859fbc29a6484c5aad30f18fb9f4c2c29033c28aef7e965b82e"
+checksum = "8ac441e1ecf678f68423d47f376d53fabce1afba92c8f68e31508eb27df8562a"
 dependencies = [
  "anstream",
  "anstyle",
diff --git a/Cargo.toml b/Cargo.toml
index 5fbfc0b..908478b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,7 +34,7 @@ criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
 serde = { version = "1.0.197", features = ["derive"] }
-snapbox = { version = "0.5.8", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] }
+snapbox = { version = "0.5.9", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] }
 toml = "0.5.11"
 
 [[bench]]
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index d0444a3..95517df 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1212,6 +1212,10 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
 mod tests {
     use super::*;
 
+    use snapbox::assert_eq;
+    use snapbox::str;
+    use snapbox::ToDebug as _;
+
     const STYLESHEET: Stylesheet = Stylesheet::plain();
 
     fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> DisplayList<'_> {
@@ -1226,19 +1230,35 @@ mod tests {
     #[test]
     fn test_format_title() {
         let input = snippet::Level::Error.title("This is a title").id("E0001");
-        let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::Error,
-                id: Some("E0001"),
-                label: vec![DisplayTextFragment {
-                    content: "This is a title",
-                    style: DisplayTextStyle::Emphasis,
-                }],
-            },
-            source_aligned: false,
-            continuation: false,
-        })]);
-        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
+        let output = str![[r#"
+            DisplayList {
+                body: [
+                    Raw(
+                        Annotation {
+                            annotation: Annotation {
+                                annotation_type: Error,
+                                id: Some(
+                                    "E0001",
+                                ),
+                                label: [
+                                    DisplayTextFragment {
+                                        content: "This is a title",
+                                        style: Emphasis,
+                                    },
+                                ],
+                            },
+                            source_aligned: false,
+                            continuation: false,
+                        },
+                    ),
+                ],
+                anonymized_line_numbers: false,
+            }
+        "#]];
+        assert_eq(
+            output,
+            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
+        );
     }
 
     #[test]
@@ -1249,55 +1269,75 @@ mod tests {
         let input = snippet::Level::Error
             .title("")
             .snippet(snippet::Snippet::source(&source).line_start(5402));
-        let output = from_display_lines(vec![
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Error,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "",
-                        style: DisplayTextStyle::Emphasis,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            }),
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-            DisplayLine::Source {
-                lineno: Some(5402),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: line_1,
-                    range: (0, line_1.len()),
-                },
-            },
-            DisplayLine::Source {
-                lineno: Some(5403),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    range: (line_1.len() + 1, source.len()),
-                    text: line_2,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-        ]);
-        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
+        let output = str![[r#"
+            DisplayList {
+                body: [
+                    Raw(
+                        Annotation {
+                            annotation: Annotation {
+                                annotation_type: Error,
+                                id: None,
+                                label: [
+                                    DisplayTextFragment {
+                                        content: "",
+                                        style: Emphasis,
+                                    },
+                                ],
+                            },
+                            source_aligned: false,
+                            continuation: false,
+                        },
+                    ),
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
+                    },
+                    Source {
+                        lineno: Some(
+                            5402,
+                        ),
+                        inline_marks: [],
+                        line: Content {
+                            text: "This is line 1",
+                            range: (
+                                0,
+                                14,
+                            ),
+                        },
+                    },
+                    Source {
+                        lineno: Some(
+                            5403,
+                        ),
+                        inline_marks: [],
+                        line: Content {
+                            text: "This is line 2",
+                            range: (
+                                15,
+                                29,
+                            ),
+                        },
+                    },
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
+                    },
+                ],
+                anonymized_line_numbers: false,
+            }
+        "#]];
+        assert_eq(
+            output,
+            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
+        );
     }
 
     #[test]
     fn test_format_slices_continuation() {
         let src_0 = "This is slice 1";
-        let src_0_len = src_0.len();
         let src_1 = "This is slice 2";
-        let src_1_len = src_1.len();
         let input = snippet::Level::Error
             .title("")
             .snippet(
@@ -1310,67 +1350,93 @@ mod tests {
                     .line_start(2)
                     .origin("file2.rs"),
             );
-        let output = from_display_lines(vec![
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Error,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "",
-                        style: DisplayTextStyle::Emphasis,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            }),
-            DisplayLine::Raw(DisplayRawLine::Origin {
-                path: "file1.rs",
-                pos: None,
-                header_type: DisplayHeaderType::Initial,
-            }),
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-            DisplayLine::Source {
-                lineno: Some(5402),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: src_0,
-                    range: (0, src_0_len),
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-            DisplayLine::Raw(DisplayRawLine::Origin {
-                path: "file2.rs",
-                pos: None,
-                header_type: DisplayHeaderType::Continuation,
-            }),
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-            DisplayLine::Source {
-                lineno: Some(2),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: src_1,
-                    range: (0, src_1_len),
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-        ]);
-        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
+        let output = str![[r#"
+            DisplayList {
+                body: [
+                    Raw(
+                        Annotation {
+                            annotation: Annotation {
+                                annotation_type: Error,
+                                id: None,
+                                label: [
+                                    DisplayTextFragment {
+                                        content: "",
+                                        style: Emphasis,
+                                    },
+                                ],
+                            },
+                            source_aligned: false,
+                            continuation: false,
+                        },
+                    ),
+                    Raw(
+                        Origin {
+                            path: "file1.rs",
+                            pos: None,
+                            header_type: Initial,
+                        },
+                    ),
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
+                    },
+                    Source {
+                        lineno: Some(
+                            5402,
+                        ),
+                        inline_marks: [],
+                        line: Content {
+                            text: "This is slice 1",
+                            range: (
+                                0,
+                                15,
+                            ),
+                        },
+                    },
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
+                    },
+                    Raw(
+                        Origin {
+                            path: "file2.rs",
+                            pos: None,
+                            header_type: Continuation,
+                        },
+                    ),
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
+                    },
+                    Source {
+                        lineno: Some(
+                            2,
+                        ),
+                        inline_marks: [],
+                        line: Content {
+                            text: "This is slice 2",
+                            range: (
+                                0,
+                                15,
+                            ),
+                        },
+                    },
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
+                    },
+                ],
+                anonymized_line_numbers: false,
+            }
+        "#]];
+        assert_eq(
+            output,
+            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
+        );
     }
 
     #[test]
@@ -1389,67 +1455,91 @@ mod tests {
                         .label("Test annotation"),
                 ),
         );
-        let output = from_display_lines(vec![
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Error,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "",
-                        style: DisplayTextStyle::Emphasis,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            }),
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-            DisplayLine::Source {
-                lineno: Some(5402),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    range: (0, line_1.len()),
-                    text: line_1,
-                },
-            },
-            DisplayLine::Source {
-                lineno: Some(5403),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    range: (line_1.len() + 1, source.len()),
-                    text: line_2,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::Info,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "Test annotation",
-                            style: DisplayTextStyle::Regular,
-                        }],
-                    },
-                    range: (
-                        range.start - (line_1.len() + 1),
-                        range.end - (line_1.len() + 1),
+        let output = str![[r#"
+            DisplayList {
+                body: [
+                    Raw(
+                        Annotation {
+                            annotation: Annotation {
+                                annotation_type: Error,
+                                id: None,
+                                label: [
+                                    DisplayTextFragment {
+                                        content: "",
+                                        style: Emphasis,
+                                    },
+                                ],
+                            },
+                            source_aligned: false,
+                            continuation: false,
+                        },
                     ),
-                    annotation_type: DisplayAnnotationType::Info,
-                    annotation_part: DisplayAnnotationPart::Standalone,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-        ]);
-        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
+                    },
+                    Source {
+                        lineno: Some(
+                            5402,
+                        ),
+                        inline_marks: [],
+                        line: Content {
+                            text: "This is line 1",
+                            range: (
+                                0,
+                                14,
+                            ),
+                        },
+                    },
+                    Source {
+                        lineno: Some(
+                            5403,
+                        ),
+                        inline_marks: [],
+                        line: Content {
+                            text: "This is line 2",
+                            range: (
+                                15,
+                                29,
+                            ),
+                        },
+                    },
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Annotation {
+                            annotation: Annotation {
+                                annotation_type: Info,
+                                id: None,
+                                label: [
+                                    DisplayTextFragment {
+                                        content: "Test annotation",
+                                        style: Regular,
+                                    },
+                                ],
+                            },
+                            range: (
+                                7,
+                                9,
+                            ),
+                            annotation_type: Info,
+                            annotation_part: Standalone,
+                        },
+                    },
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
+                    },
+                ],
+                anonymized_line_numbers: false,
+            }
+        "#]];
+        assert_eq(
+            output,
+            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
+        );
     }
 
     #[test]
@@ -1457,33 +1547,49 @@ mod tests {
         let input = snippet::Level::Error
             .title("")
             .footer(snippet::Level::Error.title("This __is__ a title"));
-        let output = from_display_lines(vec![
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Error,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "",
-                        style: DisplayTextStyle::Emphasis,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            }),
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Error,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "This __is__ a title",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                source_aligned: true,
-                continuation: false,
-            }),
-        ]);
-        assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output);
+        let output = str![[r#"
+            DisplayList {
+                body: [
+                    Raw(
+                        Annotation {
+                            annotation: Annotation {
+                                annotation_type: Error,
+                                id: None,
+                                label: [
+                                    DisplayTextFragment {
+                                        content: "",
+                                        style: Emphasis,
+                                    },
+                                ],
+                            },
+                            source_aligned: false,
+                            continuation: false,
+                        },
+                    ),
+                    Raw(
+                        Annotation {
+                            annotation: Annotation {
+                                annotation_type: Error,
+                                id: None,
+                                label: [
+                                    DisplayTextFragment {
+                                        content: "This __is__ a title",
+                                        style: Regular,
+                                    },
+                                ],
+                            },
+                            source_aligned: true,
+                            continuation: false,
+                        },
+                    ),
+                ],
+                anonymized_line_numbers: false,
+            }
+        "#]];
+        assert_eq(
+            output,
+            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
+        );
     }
 
     #[test]
@@ -1501,79 +1607,109 @@ mod tests {
 
     #[test]
     fn test_i_29() {
-        let snippets = snippet::Level::Error.title("oops").snippet(
+        let input = snippet::Level::Error.title("oops").snippet(
             snippet::Snippet::source("First line\r\nSecond oops line")
                 .line_start(1)
                 .origin("<current file>")
                 .fold(true)
                 .annotation(snippet::Level::Error.span(19..23).label("oops")),
         );
-
-        let expected = from_display_lines(vec![
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Error,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "oops",
-                        style: DisplayTextStyle::Emphasis,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            }),
-            DisplayLine::Raw(DisplayRawLine::Origin {
-                path: "<current file>",
-                pos: Some((2, 8)),
-                header_type: DisplayHeaderType::Initial,
-            }),
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-            DisplayLine::Source {
-                lineno: Some(1),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: "First line",
-                    range: (0, 10),
-                },
-            },
-            DisplayLine::Source {
-                lineno: Some(2),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: "Second oops line",
-                    range: (12, 28),
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::None,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "oops",
-                            style: DisplayTextStyle::Regular,
-                        }],
+        let output = str![[r#"
+            DisplayList {
+                body: [
+                    Raw(
+                        Annotation {
+                            annotation: Annotation {
+                                annotation_type: Error,
+                                id: None,
+                                label: [
+                                    DisplayTextFragment {
+                                        content: "oops",
+                                        style: Emphasis,
+                                    },
+                                ],
+                            },
+                            source_aligned: false,
+                            continuation: false,
+                        },
+                    ),
+                    Raw(
+                        Origin {
+                            path: "<current file>",
+                            pos: Some(
+                                (
+                                    2,
+                                    8,
+                                ),
+                            ),
+                            header_type: Initial,
+                        },
+                    ),
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
                     },
-                    range: (7, 11),
-                    annotation_type: DisplayAnnotationType::Error,
-                    annotation_part: DisplayAnnotationPart::Standalone,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-        ]);
-        assert_eq!(
-            DisplayList::new(snippets, &STYLESHEET, false, None),
-            expected
+                    Source {
+                        lineno: Some(
+                            1,
+                        ),
+                        inline_marks: [],
+                        line: Content {
+                            text: "First line",
+                            range: (
+                                0,
+                                10,
+                            ),
+                        },
+                    },
+                    Source {
+                        lineno: Some(
+                            2,
+                        ),
+                        inline_marks: [],
+                        line: Content {
+                            text: "Second oops line",
+                            range: (
+                                12,
+                                28,
+                            ),
+                        },
+                    },
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Annotation {
+                            annotation: Annotation {
+                                annotation_type: None,
+                                id: None,
+                                label: [
+                                    DisplayTextFragment {
+                                        content: "oops",
+                                        style: Regular,
+                                    },
+                                ],
+                            },
+                            range: (
+                                7,
+                                11,
+                            ),
+                            annotation_type: Error,
+                            annotation_part: Standalone,
+                        },
+                    },
+                    Source {
+                        lineno: None,
+                        inline_marks: [],
+                        line: Empty,
+                    },
+                ],
+                anonymized_line_numbers: false,
+            }
+        "#]];
+        assert_eq(
+            output,
+            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
         );
     }
 
@@ -1585,7 +1721,7 @@ mod tests {
             line: DisplaySourceLine::Empty,
         }]);
 
-        assert_eq!(dl.to_string(), " |");
+        assert_eq(str![" |"], dl.to_string());
     }
 
     #[test]
@@ -1609,9 +1745,11 @@ mod tests {
             },
         ]);
 
-        assert_eq!(
+        assert_eq(
+            str![[r#"
+            56 | This is an example
+            57 | of content lines"#]],
             dl.to_string(),
-            "56 | This is an example\n57 | of content lines"
         );
     }
 
@@ -1635,7 +1773,7 @@ mod tests {
             },
         }]);
 
-        assert_eq!(dl.to_string(), " | ^^^^^ Example string");
+        assert_eq(str![" | ^^^^^ Example string"], dl.to_string());
     }
 
     #[test]
@@ -1677,9 +1815,12 @@ mod tests {
             },
         ]);
 
-        assert_eq!(
+        assert_eq(
+            str![[r#"
+ | ----- help: Example string
+ |             Second line"#]]
+            .indent(false),
             dl.to_string(),
-            " | ----- help: Example string\n |             Second line"
         );
     }
 
@@ -1790,7 +1931,17 @@ mod tests {
             },
         ]);
 
-        assert_eq!(dl.to_string(), " | ----- info: Example string\n |             Second line\n |                Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n |  This is an annotation of type none");
+        assert_eq(
+            str![[r#"
+ | ----- info: Example string
+ |             Second line
+ |                Second line of the warning
+ | ----- info: This is an info
+ | ----- help: This is help
+ |  This is an annotation of type none"#]]
+            .indent(false),
+            dl.to_string(),
+        );
     }
 
     #[test]
@@ -1817,9 +1968,12 @@ mod tests {
             },
         ]);
 
-        assert_eq!(
+        assert_eq(
+            str![[r#"
+                5 | This is line 5
+            ...
+            10021 | ... and now we're at line 10021"#]],
             dl.to_string(),
-            "    5 | This is line 5\n...\n10021 | ... and now we're at line 10021"
         );
     }
 
@@ -1831,7 +1985,7 @@ mod tests {
             header_type: DisplayHeaderType::Initial,
         })]);
 
-        assert_eq!(dl.to_string(), "--> src/test.rs");
+        assert_eq(str!["--> src/test.rs"], dl.to_string());
     }
 
     #[test]
@@ -1842,7 +1996,7 @@ mod tests {
             header_type: DisplayHeaderType::Initial,
         })]);
 
-        assert_eq!(dl.to_string(), "--> src/test.rs:23:15");
+        assert_eq(str!["--> src/test.rs:23:15"], dl.to_string());
     }
 
     #[test]
@@ -1853,7 +2007,7 @@ mod tests {
             header_type: DisplayHeaderType::Continuation,
         })]);
 
-        assert_eq!(dl.to_string(), "::: src/test.rs:23:15");
+        assert_eq(str!["::: src/test.rs:23:15"], dl.to_string());
     }
 
     #[test]
@@ -1871,7 +2025,7 @@ mod tests {
             continuation: false,
         })]);
 
-        assert_eq!(dl.to_string(), "error[E0001]: This is an error");
+        assert_eq(str!["error[E0001]: This is an error"], dl.to_string());
     }
 
     #[test]
@@ -1903,9 +2057,11 @@ mod tests {
             }),
         ]);
 
-        assert_eq!(
+        assert_eq(
+            str![[r#"
+            warning[E0001]: This is an error
+                            Second line of the error"#]],
             dl.to_string(),
-            "warning[E0001]: This is an error\n                Second line of the error"
         );
     }
 
@@ -1924,7 +2080,7 @@ mod tests {
             continuation: false,
         })]);
 
-        assert_eq!(dl.to_string(), " = error[E0001]: This is an error");
+        assert_eq(str![" = error[E0001]: This is an error"], dl.to_string());
     }
 
     #[test]
@@ -1956,9 +2112,12 @@ mod tests {
             }),
         ]);
 
-        assert_eq!(
+        assert_eq(
+            str![[r#"
+ = warning[E0001]: This is an error
+                   Second line of the error"#]]
+            .indent(false),
             dl.to_string(),
-            " = warning[E0001]: This is an error\n                   Second line of the error"
         );
     }
 
@@ -2003,9 +2162,12 @@ mod tests {
             }),
         ]);
 
-        assert_eq!(
+        assert_eq(
+            str![[r#"
+            note: This is a note
+            This is just a string
+              Second line of none type annotation"#]],
             dl.to_string(),
-            "note: This is a note\nThis is just a string\n  Second line of none type annotation",
         );
     }
 
@@ -2020,7 +2182,7 @@ mod tests {
             line: DisplaySourceLine::Empty,
         }]);
 
-        assert_eq!(dl.to_string(), " | |",);
+        assert_eq(str![[" | |"]], dl.to_string());
     }
 
     #[test]
@@ -2058,9 +2220,13 @@ mod tests {
         ]);
 
         dl.anonymized_line_numbers = true;
-        assert_eq!(
+        assert_eq(
+            str![[r#"
+            LL | This is an example
+            LL | of content lines
+               |
+               | abc"#]],
             dl.to_string(),
-            "LL | This is an example\nLL | of content lines\n   |\n   | abc"
         );
     }
 
@@ -2074,6 +2240,6 @@ mod tests {
 
         // Using anonymized_line_numbers should not affect the initial position
         dl.anonymized_line_numbers = true;
-        assert_eq!(dl.to_string(), "--> src/test.rs:23:15");
+        assert_eq(str!["--> src/test.rs:23:15"], dl.to_string());
     }
 }

From 1aa344705b6648c741f6e26918c8f23569e72cdc Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 14 Mar 2024 14:59:16 -0600
Subject: [PATCH 135/302] refactor: Move display_list tests to formatter

---
 src/renderer/display_list.rs | 1036 ----------------------------------
 tests/formatter.rs           |  174 ++++++
 2 files changed, 174 insertions(+), 1036 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 89a779f..05c1910 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1191,1039 +1191,3 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
         .iter()
         .all(|fragment| fragment.content.is_empty())
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    use snapbox::assert_eq;
-    use snapbox::str;
-    use snapbox::ToDebug as _;
-
-    const STYLESHEET: Stylesheet = Stylesheet::plain();
-
-    fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> DisplayList<'_> {
-        DisplayList {
-            body: lines,
-            stylesheet: &STYLESHEET,
-            anonymized_line_numbers: false,
-            margin: None,
-        }
-    }
-
-    #[test]
-    fn test_format_title() {
-        let input = snippet::Level::Error.title("This is a title").id("E0001");
-        let output = str![[r#"
-            DisplayList {
-                body: [
-                    Raw(
-                        Annotation {
-                            annotation: Annotation {
-                                annotation_type: Error,
-                                id: Some(
-                                    "E0001",
-                                ),
-                                label: [
-                                    DisplayTextFragment {
-                                        content: "This is a title",
-                                        style: Emphasis,
-                                    },
-                                ],
-                            },
-                            source_aligned: false,
-                            continuation: false,
-                        },
-                    ),
-                ],
-                anonymized_line_numbers: false,
-            }
-        "#]];
-        assert_eq(
-            output,
-            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
-        );
-    }
-
-    #[test]
-    fn test_format_slice() {
-        let line_1 = "This is line 1";
-        let line_2 = "This is line 2";
-        let source = [line_1, line_2].join("\n");
-        let input = snippet::Level::Error
-            .title("")
-            .snippet(snippet::Snippet::source(&source).line_start(5402));
-        let output = str![[r#"
-            DisplayList {
-                body: [
-                    Raw(
-                        Annotation {
-                            annotation: Annotation {
-                                annotation_type: Error,
-                                id: None,
-                                label: [
-                                    DisplayTextFragment {
-                                        content: "",
-                                        style: Emphasis,
-                                    },
-                                ],
-                            },
-                            source_aligned: false,
-                            continuation: false,
-                        },
-                    ),
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                    Source {
-                        lineno: Some(
-                            5402,
-                        ),
-                        inline_marks: [],
-                        line: Content {
-                            text: "This is line 1",
-                            range: (
-                                0,
-                                14,
-                            ),
-                        },
-                    },
-                    Source {
-                        lineno: Some(
-                            5403,
-                        ),
-                        inline_marks: [],
-                        line: Content {
-                            text: "This is line 2",
-                            range: (
-                                15,
-                                29,
-                            ),
-                        },
-                    },
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                ],
-                anonymized_line_numbers: false,
-            }
-        "#]];
-        assert_eq(
-            output,
-            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
-        );
-    }
-
-    #[test]
-    fn test_format_slices_continuation() {
-        let src_0 = "This is slice 1";
-        let src_1 = "This is slice 2";
-        let input = snippet::Level::Error
-            .title("")
-            .snippet(
-                snippet::Snippet::source(src_0)
-                    .line_start(5402)
-                    .origin("file1.rs"),
-            )
-            .snippet(
-                snippet::Snippet::source(src_1)
-                    .line_start(2)
-                    .origin("file2.rs"),
-            );
-        let output = str![[r#"
-            DisplayList {
-                body: [
-                    Raw(
-                        Annotation {
-                            annotation: Annotation {
-                                annotation_type: Error,
-                                id: None,
-                                label: [
-                                    DisplayTextFragment {
-                                        content: "",
-                                        style: Emphasis,
-                                    },
-                                ],
-                            },
-                            source_aligned: false,
-                            continuation: false,
-                        },
-                    ),
-                    Raw(
-                        Origin {
-                            path: "file1.rs",
-                            pos: None,
-                            header_type: Initial,
-                        },
-                    ),
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                    Source {
-                        lineno: Some(
-                            5402,
-                        ),
-                        inline_marks: [],
-                        line: Content {
-                            text: "This is slice 1",
-                            range: (
-                                0,
-                                15,
-                            ),
-                        },
-                    },
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                    Raw(
-                        Origin {
-                            path: "file2.rs",
-                            pos: None,
-                            header_type: Continuation,
-                        },
-                    ),
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                    Source {
-                        lineno: Some(
-                            2,
-                        ),
-                        inline_marks: [],
-                        line: Content {
-                            text: "This is slice 2",
-                            range: (
-                                0,
-                                15,
-                            ),
-                        },
-                    },
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                ],
-                anonymized_line_numbers: false,
-            }
-        "#]];
-        assert_eq(
-            output,
-            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
-        );
-    }
-
-    #[test]
-    fn test_format_slice_annotation_standalone() {
-        let line_1 = "This is line 1";
-        let line_2 = "This is line 2";
-        let source = [line_1, line_2].join("\n");
-        // In line 2
-        let range = 22..24;
-        let input = snippet::Level::Error.title("").snippet(
-            snippet::Snippet::source(&source)
-                .line_start(5402)
-                .annotation(
-                    snippet::Level::Info
-                        .span(range.clone())
-                        .label("Test annotation"),
-                ),
-        );
-        let output = str![[r#"
-            DisplayList {
-                body: [
-                    Raw(
-                        Annotation {
-                            annotation: Annotation {
-                                annotation_type: Error,
-                                id: None,
-                                label: [
-                                    DisplayTextFragment {
-                                        content: "",
-                                        style: Emphasis,
-                                    },
-                                ],
-                            },
-                            source_aligned: false,
-                            continuation: false,
-                        },
-                    ),
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                    Source {
-                        lineno: Some(
-                            5402,
-                        ),
-                        inline_marks: [],
-                        line: Content {
-                            text: "This is line 1",
-                            range: (
-                                0,
-                                14,
-                            ),
-                        },
-                    },
-                    Source {
-                        lineno: Some(
-                            5403,
-                        ),
-                        inline_marks: [],
-                        line: Content {
-                            text: "This is line 2",
-                            range: (
-                                15,
-                                29,
-                            ),
-                        },
-                    },
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Annotation {
-                            annotation: Annotation {
-                                annotation_type: Info,
-                                id: None,
-                                label: [
-                                    DisplayTextFragment {
-                                        content: "Test annotation",
-                                        style: Regular,
-                                    },
-                                ],
-                            },
-                            range: (
-                                7,
-                                9,
-                            ),
-                            annotation_type: Info,
-                            annotation_part: Standalone,
-                        },
-                    },
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                ],
-                anonymized_line_numbers: false,
-            }
-        "#]];
-        assert_eq(
-            output,
-            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
-        );
-    }
-
-    #[test]
-    fn test_format_label() {
-        let input = snippet::Level::Error
-            .title("")
-            .footer(snippet::Level::Error.title("This __is__ a title"));
-        let output = str![[r#"
-            DisplayList {
-                body: [
-                    Raw(
-                        Annotation {
-                            annotation: Annotation {
-                                annotation_type: Error,
-                                id: None,
-                                label: [
-                                    DisplayTextFragment {
-                                        content: "",
-                                        style: Emphasis,
-                                    },
-                                ],
-                            },
-                            source_aligned: false,
-                            continuation: false,
-                        },
-                    ),
-                    Raw(
-                        Annotation {
-                            annotation: Annotation {
-                                annotation_type: Error,
-                                id: None,
-                                label: [
-                                    DisplayTextFragment {
-                                        content: "This __is__ a title",
-                                        style: Regular,
-                                    },
-                                ],
-                            },
-                            source_aligned: true,
-                            continuation: false,
-                        },
-                    ),
-                ],
-                anonymized_line_numbers: false,
-            }
-        "#]];
-        assert_eq(
-            output,
-            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
-        );
-    }
-
-    #[test]
-    #[should_panic]
-    fn test_i26() {
-        let source = "short";
-        let label = "label";
-        let input = snippet::Level::Error.title("").snippet(
-            snippet::Snippet::source(source)
-                .line_start(0)
-                .annotation(snippet::Level::Error.span(0..source.len() + 2).label(label)),
-        );
-        let _ = DisplayList::new(input, &STYLESHEET, false, None);
-    }
-
-    #[test]
-    fn test_i_29() {
-        let input = snippet::Level::Error.title("oops").snippet(
-            snippet::Snippet::source("First line\r\nSecond oops line")
-                .line_start(1)
-                .origin("<current file>")
-                .fold(true)
-                .annotation(snippet::Level::Error.span(19..23).label("oops")),
-        );
-        let output = str![[r#"
-            DisplayList {
-                body: [
-                    Raw(
-                        Annotation {
-                            annotation: Annotation {
-                                annotation_type: Error,
-                                id: None,
-                                label: [
-                                    DisplayTextFragment {
-                                        content: "oops",
-                                        style: Emphasis,
-                                    },
-                                ],
-                            },
-                            source_aligned: false,
-                            continuation: false,
-                        },
-                    ),
-                    Raw(
-                        Origin {
-                            path: "<current file>",
-                            pos: Some(
-                                (
-                                    2,
-                                    8,
-                                ),
-                            ),
-                            header_type: Initial,
-                        },
-                    ),
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                    Source {
-                        lineno: Some(
-                            1,
-                        ),
-                        inline_marks: [],
-                        line: Content {
-                            text: "First line",
-                            range: (
-                                0,
-                                10,
-                            ),
-                        },
-                    },
-                    Source {
-                        lineno: Some(
-                            2,
-                        ),
-                        inline_marks: [],
-                        line: Content {
-                            text: "Second oops line",
-                            range: (
-                                12,
-                                28,
-                            ),
-                        },
-                    },
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Annotation {
-                            annotation: Annotation {
-                                annotation_type: None,
-                                id: None,
-                                label: [
-                                    DisplayTextFragment {
-                                        content: "oops",
-                                        style: Regular,
-                                    },
-                                ],
-                            },
-                            range: (
-                                7,
-                                11,
-                            ),
-                            annotation_type: Error,
-                            annotation_part: Standalone,
-                        },
-                    },
-                    Source {
-                        lineno: None,
-                        inline_marks: [],
-                        line: Empty,
-                    },
-                ],
-                anonymized_line_numbers: false,
-            }
-        "#]];
-        assert_eq(
-            output,
-            DisplayList::new(input, &STYLESHEET, false, None).to_debug(),
-        );
-    }
-
-    #[test]
-    fn test_source_empty() {
-        let dl = from_display_lines(vec![DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Empty,
-        }]);
-
-        assert_eq(str![" |"], dl.to_string());
-    }
-
-    #[test]
-    fn test_source_content() {
-        let dl = from_display_lines(vec![
-            DisplayLine::Source {
-                lineno: Some(56),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: "This is an example",
-                    range: (0, 19),
-                },
-            },
-            DisplayLine::Source {
-                lineno: Some(57),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: "of content lines",
-                    range: (0, 19),
-                },
-            },
-        ]);
-
-        assert_eq(
-            str![[r#"
-            56 | This is an example
-            57 | of content lines"#]],
-            dl.to_string(),
-        );
-    }
-
-    #[test]
-    fn test_source_annotation_standalone_singleline() {
-        let dl = from_display_lines(vec![DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Annotation {
-                range: (0, 5),
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::None,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "Example string",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                annotation_type: DisplayAnnotationType::Error,
-                annotation_part: DisplayAnnotationPart::Standalone,
-            },
-        }]);
-
-        assert_eq(str![" | ^^^^^ Example string"], dl.to_string());
-    }
-
-    #[test]
-    fn test_source_annotation_standalone_multiline() {
-        let dl = from_display_lines(vec![
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    range: (0, 5),
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::Help,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "Example string",
-                            style: DisplayTextStyle::Regular,
-                        }],
-                    },
-                    annotation_type: DisplayAnnotationType::Warning,
-                    annotation_part: DisplayAnnotationPart::Standalone,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    range: (0, 5),
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::Help,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "Second line",
-                            style: DisplayTextStyle::Regular,
-                        }],
-                    },
-                    annotation_type: DisplayAnnotationType::Warning,
-                    annotation_part: DisplayAnnotationPart::LabelContinuation,
-                },
-            },
-        ]);
-
-        assert_eq(
-            str![[r#"
- | ----- help: Example string
- |             Second line"#]]
-            .indent(false),
-            dl.to_string(),
-        );
-    }
-
-    #[test]
-    fn test_source_annotation_standalone_multi_annotation() {
-        let dl = from_display_lines(vec![
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    range: (0, 5),
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::Info,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "Example string",
-                            style: DisplayTextStyle::Regular,
-                        }],
-                    },
-                    annotation_type: DisplayAnnotationType::Note,
-                    annotation_part: DisplayAnnotationPart::Standalone,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    range: (0, 5),
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::Info,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "Second line",
-                            style: DisplayTextStyle::Regular,
-                        }],
-                    },
-                    annotation_type: DisplayAnnotationType::Note,
-                    annotation_part: DisplayAnnotationPart::LabelContinuation,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    range: (0, 5),
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::Warning,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "Second line of the warning",
-                            style: DisplayTextStyle::Regular,
-                        }],
-                    },
-                    annotation_type: DisplayAnnotationType::Note,
-                    annotation_part: DisplayAnnotationPart::LabelContinuation,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    range: (0, 5),
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::Info,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "This is an info",
-                            style: DisplayTextStyle::Regular,
-                        }],
-                    },
-                    annotation_type: DisplayAnnotationType::Info,
-                    annotation_part: DisplayAnnotationPart::Standalone,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    range: (0, 5),
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::Help,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "This is help",
-                            style: DisplayTextStyle::Regular,
-                        }],
-                    },
-                    annotation_type: DisplayAnnotationType::Help,
-                    annotation_part: DisplayAnnotationPart::Standalone,
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Annotation {
-                    range: (0, 0),
-                    annotation: Annotation {
-                        annotation_type: DisplayAnnotationType::None,
-                        id: None,
-                        label: vec![DisplayTextFragment {
-                            content: "This is an annotation of type none",
-                            style: DisplayTextStyle::Regular,
-                        }],
-                    },
-                    annotation_type: DisplayAnnotationType::None,
-                    annotation_part: DisplayAnnotationPart::Standalone,
-                },
-            },
-        ]);
-
-        assert_eq(
-            str![[r#"
- | ----- info: Example string
- |             Second line
- |                Second line of the warning
- | ----- info: This is an info
- | ----- help: This is help
- |  This is an annotation of type none"#]]
-            .indent(false),
-            dl.to_string(),
-        );
-    }
-
-    #[test]
-    fn test_fold_line() {
-        let dl = from_display_lines(vec![
-            DisplayLine::Source {
-                lineno: Some(5),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: "This is line 5",
-                    range: (0, 19),
-                },
-            },
-            DisplayLine::Fold {
-                inline_marks: vec![],
-            },
-            DisplayLine::Source {
-                lineno: Some(10021),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: "... and now we're at line 10021",
-                    range: (0, 19),
-                },
-            },
-        ]);
-
-        assert_eq(
-            str![[r#"
-                5 | This is line 5
-            ...
-            10021 | ... and now we're at line 10021"#]],
-            dl.to_string(),
-        );
-    }
-
-    #[test]
-    fn test_raw_origin_initial_nopos() {
-        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
-            path: "src/test.rs",
-            pos: None,
-            header_type: DisplayHeaderType::Initial,
-        })]);
-
-        assert_eq(str!["--> src/test.rs"], dl.to_string());
-    }
-
-    #[test]
-    fn test_raw_origin_initial_pos() {
-        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
-            path: "src/test.rs",
-            pos: Some((23, 15)),
-            header_type: DisplayHeaderType::Initial,
-        })]);
-
-        assert_eq(str!["--> src/test.rs:23:15"], dl.to_string());
-    }
-
-    #[test]
-    fn test_raw_origin_continuation() {
-        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
-            path: "src/test.rs",
-            pos: Some((23, 15)),
-            header_type: DisplayHeaderType::Continuation,
-        })]);
-
-        assert_eq(str!["::: src/test.rs:23:15"], dl.to_string());
-    }
-
-    #[test]
-    fn test_raw_annotation_unaligned() {
-        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::Error,
-                id: Some("E0001"),
-                label: vec![DisplayTextFragment {
-                    content: "This is an error",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: false,
-            continuation: false,
-        })]);
-
-        assert_eq(str!["error[E0001]: This is an error"], dl.to_string());
-    }
-
-    #[test]
-    fn test_raw_annotation_unaligned_multiline() {
-        let dl = from_display_lines(vec![
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Warning,
-                    id: Some("E0001"),
-                    label: vec![DisplayTextFragment {
-                        content: "This is an error",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            }),
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Warning,
-                    id: Some("E0001"),
-                    label: vec![DisplayTextFragment {
-                        content: "Second line of the error",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                source_aligned: false,
-                continuation: true,
-            }),
-        ]);
-
-        assert_eq(
-            str![[r#"
-            warning[E0001]: This is an error
-                            Second line of the error"#]],
-            dl.to_string(),
-        );
-    }
-
-    #[test]
-    fn test_raw_annotation_aligned() {
-        let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::Error,
-                id: Some("E0001"),
-                label: vec![DisplayTextFragment {
-                    content: "This is an error",
-                    style: DisplayTextStyle::Regular,
-                }],
-            },
-            source_aligned: true,
-            continuation: false,
-        })]);
-
-        assert_eq(str![" = error[E0001]: This is an error"], dl.to_string());
-    }
-
-    #[test]
-    fn test_raw_annotation_aligned_multiline() {
-        let dl = from_display_lines(vec![
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Warning,
-                    id: Some("E0001"),
-                    label: vec![DisplayTextFragment {
-                        content: "This is an error",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                source_aligned: true,
-                continuation: false,
-            }),
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Warning,
-                    id: Some("E0001"),
-                    label: vec![DisplayTextFragment {
-                        content: "Second line of the error",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                source_aligned: true,
-                continuation: true,
-            }),
-        ]);
-
-        assert_eq(
-            str![[r#"
- = warning[E0001]: This is an error
-                   Second line of the error"#]]
-            .indent(false),
-            dl.to_string(),
-        );
-    }
-
-    #[test]
-    fn test_different_annotation_types() {
-        let dl = from_display_lines(vec![
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::Note,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "This is a note",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            }),
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::None,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "This is just a string",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                source_aligned: false,
-                continuation: false,
-            }),
-            DisplayLine::Raw(DisplayRawLine::Annotation {
-                annotation: Annotation {
-                    annotation_type: DisplayAnnotationType::None,
-                    id: None,
-                    label: vec![DisplayTextFragment {
-                        content: "Second line of none type annotation",
-                        style: DisplayTextStyle::Regular,
-                    }],
-                },
-                source_aligned: false,
-                continuation: true,
-            }),
-        ]);
-
-        assert_eq(
-            str![[r#"
-            note: This is a note
-            This is just a string
-              Second line of none type annotation"#]],
-            dl.to_string(),
-        );
-    }
-
-    #[test]
-    fn test_inline_marks_empty_line() {
-        let dl = from_display_lines(vec![DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![DisplayMark {
-                mark_type: DisplayMarkType::AnnotationThrough,
-                annotation_type: DisplayAnnotationType::Error,
-            }],
-            line: DisplaySourceLine::Empty,
-        }]);
-
-        assert_eq(str![[" | |"]], dl.to_string());
-    }
-
-    #[test]
-    fn test_anon_lines() {
-        let mut dl = from_display_lines(vec![
-            DisplayLine::Source {
-                lineno: Some(56),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: "This is an example",
-                    range: (0, 19),
-                },
-            },
-            DisplayLine::Source {
-                lineno: Some(57),
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: "of content lines",
-                    range: (0, 19),
-                },
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-            },
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Content {
-                    text: "abc",
-                    range: (0, 19),
-                },
-            },
-        ]);
-
-        dl.anonymized_line_numbers = true;
-        assert_eq(
-            str![[r#"
-            LL | This is an example
-            LL | of content lines
-               |
-               | abc"#]],
-            dl.to_string(),
-        );
-    }
-
-    #[test]
-    fn test_raw_origin_initial_pos_anon_lines() {
-        let mut dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin {
-            path: "src/test.rs",
-            pos: Some((23, 15)),
-            header_type: DisplayHeaderType::Initial,
-        })]);
-
-        // Using anonymized_line_numbers should not affect the initial position
-        dl.anonymized_line_numbers = true;
-        assert_eq(str!["--> src/test.rs:23:15"], dl.to_string());
-    }
-}
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 035492a..b1a53af 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -100,3 +100,177 @@ fn test_point_to_double_width_characters_mixed() {
     let renderer = Renderer::plain();
     assert_eq!(renderer.render(snippets).to_string(), expected);
 }
+
+#[test]
+fn test_format_title() {
+    let input = Level::Error.title("This is a title").id("E0001");
+
+    let expected = r#"error[E0001]: This is a title"#;
+    let renderer = Renderer::plain();
+    assert_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn test_format_snippet_only() {
+    let source = "This is line 1\nThis is line 2";
+    let input = Level::Error
+        .title("")
+        .snippet(Snippet::source(source).line_start(5402));
+
+    let expected = r#"error
+     |
+5402 | This is line 1
+5403 | This is line 2
+     |"#;
+    let renderer = Renderer::plain();
+    assert_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn test_format_snippets_continuation() {
+    let src_0 = "This is slice 1";
+    let src_1 = "This is slice 2";
+    let input = Level::Error
+        .title("")
+        .snippet(Snippet::source(src_0).line_start(5402).origin("file1.rs"))
+        .snippet(Snippet::source(src_1).line_start(2).origin("file2.rs"));
+    let expected = r#"error
+    --> file1.rs
+     |
+5402 | This is slice 1
+     |
+    ::: file2.rs
+     |
+   2 | This is slice 2
+     |"#;
+    let renderer = Renderer::plain();
+    assert_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn test_format_snippet_annotation_standalone() {
+    let line_1 = "This is line 1";
+    let line_2 = "This is line 2";
+    let source = [line_1, line_2].join("\n");
+    // In line 2
+    let range = 22..24;
+    let input = Level::Error.title("").snippet(
+        Snippet::source(&source)
+            .line_start(5402)
+            .annotation(Level::Info.span(range.clone()).label("Test annotation")),
+    );
+    let expected = r#"error
+     |
+5402 | This is line 1
+5403 | This is line 2
+     |        -- info: Test annotation
+     |"#;
+    let renderer = Renderer::plain();
+    assert_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn test_format_footer_title() {
+    let input = Level::Error
+        .title("")
+        .footer(Level::Error.title("This __is__ a title"));
+    let expected = r#"error
+ = error: This __is__ a title"#;
+    let renderer = Renderer::plain();
+    assert_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+#[should_panic]
+fn test_i26() {
+    let source = "short";
+    let label = "label";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .line_start(0)
+            .annotation(Level::Error.span(0..source.len() + 2).label(label)),
+    );
+    let renderer = Renderer::plain();
+    let _ = renderer.render(input).to_string();
+}
+
+#[test]
+fn test_source_content() {
+    let source = "This is an example\nof content lines";
+    let input = Level::Error
+        .title("")
+        .snippet(Snippet::source(source).line_start(56));
+    let expected = r#"error
+   |
+56 | This is an example
+57 | of content lines
+   |"#;
+    let renderer = Renderer::plain();
+    assert_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn test_source_annotation_standalone_singleline() {
+    let source = "tests";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .annotation(Level::Help.span(0..5).label("Example string")),
+    );
+    let expected = r#"error
+  |
+1 | tests
+  | ----- help: Example string
+  |"#;
+    let renderer = Renderer::plain();
+    assert_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn test_source_annotation_standalone_multiline() {
+    let source = "tests";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .annotation(Level::Help.span(0..5).label("Example string"))
+            .annotation(Level::Help.span(0..5).label("Second line")),
+    );
+    let expected = r#"error
+  |
+1 | tests
+  | ----- help: Example string
+  | ----- help: Second line
+  |"#;
+    let renderer = Renderer::plain();
+    assert_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn test_only_source() {
+    let input = Level::Error
+        .title("")
+        .snippet(Snippet::source("").origin("file.rs"));
+    let expected = r#"error
+--> file.rs
+ |
+ |"#;
+    let renderer = Renderer::plain();
+    assert_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn test_anon_lines() {
+    let source = "This is an example\nof content lines\n\nabc";
+    let input = Level::Error
+        .title("")
+        .snippet(Snippet::source(source).line_start(56));
+    let expected = r#"error
+   |
+LL | This is an example
+LL | of content lines
+LL | 
+LL | abc
+   |"#;
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_eq!(renderer.render(input).to_string(), expected);
+}

From da9ea864f95ab3d58f8dbe2d131a6d16c41a8821 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 14 Mar 2024 15:13:14 -0600
Subject: [PATCH 136/302] refactor: Use snapbox::str to auto-update tests

---
 tests/formatter.rs | 118 ++++++++++++++++++++++++++++-----------------
 1 file changed, 74 insertions(+), 44 deletions(-)

diff --git a/tests/formatter.rs b/tests/formatter.rs
index b1a53af..3b02489 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,5 +1,7 @@
 use annotate_snippets::{Level, Renderer, Snippet};
 
+use snapbox::{assert_eq, str};
+
 #[test]
 fn test_i_29() {
     let snippets = Level::Error.title("oops").snippet(
@@ -8,16 +10,18 @@ fn test_i_29() {
             .annotation(Level::Error.span(19..23).label("oops"))
             .fold(true),
     );
-    let expected = r#"error: oops
+    let expected = str![[r#"
+error: oops
  --> <current file>:2:8
   |
 1 | First line
 2 | Second oops line
   |        ^^^^ oops
-  |"#;
+  |"#]]
+    .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(snippets).to_string(), expected);
+    assert_eq(expected, renderer.render(snippets).to_string());
 }
 
 #[test]
@@ -28,15 +32,17 @@ fn test_point_to_double_width_characters() {
             .annotation(Level::Error.span(18..24).label("world")),
     );
 
-    let expected = r#"error
+    let expected = str![[r#"
+error
  --> <current file>:1:7
   |
 1 | こんにちは、世界
   |             ^^^^ world
-  |"#;
+  |"#]]
+    .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(snippets).to_string(), expected);
+    assert_eq(expected, renderer.render(snippets).to_string());
 }
 
 #[test]
@@ -47,17 +53,19 @@ fn test_point_to_double_width_characters_across_lines() {
             .annotation(Level::Error.span(6..22).label("Good morning")),
     );
 
-    let expected = r#"error
+    let expected = str![[r#"
+error
  --> <current file>:1:3
   |
 1 |   おはよう
   |  _____^
 2 | | ございます
   | |______^ Good morning
-  |"#;
+  |"#]]
+    .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(snippets).to_string(), expected);
+    assert_eq(expected, renderer.render(snippets).to_string());
 }
 
 #[test]
@@ -69,17 +77,19 @@ fn test_point_to_double_width_characters_multiple() {
             .annotation(Level::Note.span(16..22).label("Sushi2")),
     );
 
-    let expected = r#"error
+    let expected = str![[r#"
+error
  --> <current file>:1:1
   |
 1 | お寿司
   | ^^^^^^ Sushi1
 2 | 食べたい🍣
   |     ---- note: Sushi2
-  |"#;
+  |"#]]
+    .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(snippets).to_string(), expected);
+    assert_eq(expected, renderer.render(snippets).to_string());
 }
 
 #[test]
@@ -90,24 +100,26 @@ fn test_point_to_double_width_characters_mixed() {
             .annotation(Level::Error.span(18..32).label("New world")),
     );
 
-    let expected = r#"error
+    let expected = str![[r#"
+error
  --> <current file>:1:7
   |
 1 | こんにちは、新しいWorld!
   |             ^^^^^^^^^^^ New world
-  |"#;
+  |"#]]
+    .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(snippets).to_string(), expected);
+    assert_eq(expected, renderer.render(snippets).to_string());
 }
 
 #[test]
 fn test_format_title() {
     let input = Level::Error.title("This is a title").id("E0001");
 
-    let expected = r#"error[E0001]: This is a title"#;
+    let expected = str![r#"error[E0001]: This is a title"#];
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }
 
 #[test]
@@ -117,13 +129,15 @@ fn test_format_snippet_only() {
         .title("")
         .snippet(Snippet::source(source).line_start(5402));
 
-    let expected = r#"error
+    let expected = str![[r#"
+error
      |
 5402 | This is line 1
 5403 | This is line 2
-     |"#;
+     |"#]]
+    .indent(false);
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }
 
 #[test]
@@ -134,7 +148,8 @@ fn test_format_snippets_continuation() {
         .title("")
         .snippet(Snippet::source(src_0).line_start(5402).origin("file1.rs"))
         .snippet(Snippet::source(src_1).line_start(2).origin("file2.rs"));
-    let expected = r#"error
+    let expected = str![[r#"
+error
     --> file1.rs
      |
 5402 | This is slice 1
@@ -142,9 +157,10 @@ fn test_format_snippets_continuation() {
     ::: file2.rs
      |
    2 | This is slice 2
-     |"#;
+     |"#]]
+    .indent(false);
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }
 
 #[test]
@@ -159,14 +175,16 @@ fn test_format_snippet_annotation_standalone() {
             .line_start(5402)
             .annotation(Level::Info.span(range.clone()).label("Test annotation")),
     );
-    let expected = r#"error
+    let expected = str![[r#"
+error
      |
 5402 | This is line 1
 5403 | This is line 2
      |        -- info: Test annotation
-     |"#;
+     |"#]]
+    .indent(false);
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }
 
 #[test]
@@ -174,10 +192,12 @@ fn test_format_footer_title() {
     let input = Level::Error
         .title("")
         .footer(Level::Error.title("This __is__ a title"));
-    let expected = r#"error
- = error: This __is__ a title"#;
+    let expected = str![[r#"
+error
+ = error: This __is__ a title"#]]
+    .indent(false);
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }
 
 #[test]
@@ -200,13 +220,15 @@ fn test_source_content() {
     let input = Level::Error
         .title("")
         .snippet(Snippet::source(source).line_start(56));
-    let expected = r#"error
+    let expected = str![[r#"
+error
    |
 56 | This is an example
 57 | of content lines
-   |"#;
+   |"#]]
+    .indent(false);
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }
 
 #[test]
@@ -217,13 +239,15 @@ fn test_source_annotation_standalone_singleline() {
             .line_start(1)
             .annotation(Level::Help.span(0..5).label("Example string")),
     );
-    let expected = r#"error
+    let expected = str![[r#"
+error
   |
 1 | tests
   | ----- help: Example string
-  |"#;
+  |"#]]
+    .indent(false);
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }
 
 #[test]
@@ -235,14 +259,16 @@ fn test_source_annotation_standalone_multiline() {
             .annotation(Level::Help.span(0..5).label("Example string"))
             .annotation(Level::Help.span(0..5).label("Second line")),
     );
-    let expected = r#"error
+    let expected = str![[r#"
+error
   |
 1 | tests
   | ----- help: Example string
   | ----- help: Second line
-  |"#;
+  |"#]]
+    .indent(false);
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }
 
 #[test]
@@ -250,12 +276,14 @@ fn test_only_source() {
     let input = Level::Error
         .title("")
         .snippet(Snippet::source("").origin("file.rs"));
-    let expected = r#"error
+    let expected = str![[r#"
+error
 --> file.rs
  |
- |"#;
+ |"#]]
+    .indent(false);
     let renderer = Renderer::plain();
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }
 
 #[test]
@@ -264,13 +292,15 @@ fn test_anon_lines() {
     let input = Level::Error
         .title("")
         .snippet(Snippet::source(source).line_start(56));
-    let expected = r#"error
+    let expected = str![[r#"
+error
    |
 LL | This is an example
 LL | of content lines
 LL | 
 LL | abc
-   |"#;
+   |"#]]
+    .indent(false);
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_eq!(renderer.render(input).to_string(), expected);
+    assert_eq(expected, renderer.render(input).to_string());
 }

From 0efbba4000c013543d903e6c87f4806e18d18d10 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 13 Mar 2024 14:15:03 -0600
Subject: [PATCH 137/302] refactor: Add a DisplaySet for each Snippet

---
 src/renderer/display_list.rs | 242 +++++++++++++++++++++--------------
 src/renderer/margin.rs       |   2 +-
 2 files changed, 150 insertions(+), 94 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 05c1910..81a7be1 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -38,12 +38,18 @@ use std::{cmp, fmt};
 
 use crate::renderer::{stylesheet::Stylesheet, Margin, Style};
 
+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,46 +69,36 @@ 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,
@@ -115,53 +111,53 @@ impl<'a> DisplayList<'a> {
             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: Option<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 +181,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,11 +211,11 @@ 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(())
@@ -229,6 +226,7 @@ impl<'a> DisplayList<'a> {
     fn format_source_line(
         &self,
         line: &DisplaySourceLine<'_>,
+        stylesheet: &Stylesheet,
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
         match line {
@@ -318,7 +316,7 @@ impl<'a> DisplayList<'a> {
                     DisplayAnnotationType::Help => '-',
                     DisplayAnnotationType::None => ' ',
                 };
-                let color = self.get_annotation_style(annotation_type);
+                let color = get_annotation_style(annotation_type, stylesheet);
                 let indent_length = match annotation_part {
                     DisplayAnnotationPart::LabelContinuation => range.1,
                     _ => range.0,
@@ -336,6 +334,7 @@ impl<'a> DisplayList<'a> {
                         annotation,
                         annotation_part == &DisplayAnnotationPart::LabelContinuation,
                         true,
+                        stylesheet,
                         f,
                     )?;
                     write!(f, "{}", color.render_reset())?;
@@ -351,6 +350,7 @@ impl<'a> DisplayList<'a> {
         &self,
         line: &DisplayRawLine<'_>,
         lineno_width: usize,
+        stylesheet: &Stylesheet,
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
         match line {
@@ -363,7 +363,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 +402,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 +414,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 +425,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 {
@@ -433,10 +435,10 @@ impl<'a> DisplayList<'a> {
                 inline_marks,
                 line,
             } => {
-                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 {
@@ -451,12 +453,12 @@ impl<'a> DisplayList<'a> {
                 if *line != DisplaySourceLine::Empty {
                     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)?;
                     }
-                    self.format_source_line(line, f)?;
+                    self.format_source_line(line, stylesheet, 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 +466,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 +478,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 => '|',
@@ -706,17 +709,16 @@ fn format_message(
     }: snippet::Message<'_>,
     margin: Option<Margin>,
     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(),
@@ -724,11 +726,22 @@ fn format_message(
         ));
     }
 
+    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,
+        });
+    }
+
     for annotation in footer {
-        body.extend(format_message(annotation, margin, false));
+        sets.extend(format_message(annotation, margin, false));
     }
 
-    body
+    sets
 }
 
 fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> {
@@ -783,7 +796,7 @@ fn format_snippet(
     is_first: bool,
     has_footer: bool,
     margin: Option<Margin>,
-) -> Vec<DisplayLine<'_>> {
+) -> 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;
@@ -795,7 +808,10 @@ fn format_snippet(
         result.push(header);
     }
     result.extend(body);
-    result
+    DisplaySet {
+        display_lines: result,
+        margin,
+    }
 }
 
 #[inline]
@@ -1184,6 +1200,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
diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs
index 361f5f3..7636603 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,

From e88121f1199be38a929b43d6e50c92294566ac70 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 14 Mar 2024 16:24:21 -0600
Subject: [PATCH 138/302] refactor: Keep annotations with their DisplayLine

---
 src/renderer/display_list.rs | 513 ++++++++++++++++++-----------------
 1 file changed, 259 insertions(+), 254 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 81a7be1..250cfc4 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -222,129 +222,6 @@ impl<'a> DisplaySet<'a> {
         }
     }
 
-    #[inline]
-    fn format_source_line(
-        &self,
-        line: &DisplaySourceLine<'_>,
-        stylesheet: &Stylesheet,
-        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 = get_annotation_style(annotation_type, stylesheet);
-                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,
-                        stylesheet,
-                        f,
-                    )?;
-                    write!(f, "{}", color.render_reset())?;
-                }
-
-                Ok(())
-            }
-        }
-    }
-
     #[inline]
     fn format_raw_line(
         &self,
@@ -434,6 +311,7 @@ impl<'a> DisplaySet<'a> {
                 lineno,
                 inline_marks,
                 line,
+                annotations,
             } => {
                 let lineno_color = stylesheet.line_no();
                 if anonymized_line_numbers && lineno.is_some() {
@@ -450,12 +328,95 @@ impl<'a> DisplaySet<'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, stylesheet, f)?;
                     }
-                    self.format_source_line(line, stylesheet, f)?;
+                    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)?;
+                        }
+                    } else {
+                        text.fmt(f)?;
+                    }
+
+                    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, stylesheet, f)?;
+                    }
                 } else if !inline_marks.is_empty() {
                     f.write_char(' ')?;
                     self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?;
@@ -493,6 +454,51 @@ impl<'a> DisplaySet<'a> {
         }
         Ok(())
     }
+
+    fn format_source_annotation(
+        &self,
+        annotation: &DisplaySourceAnnotation<'_>,
+        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 indent_length = match annotation.annotation_part {
+            DisplayAnnotationPart::LabelContinuation => annotation.range.1,
+            _ => annotation.range.0,
+        };
+        write!(f, "{}", color.render())?;
+        format_repeat_char(indent_char, indent_length + 1, f)?;
+        format_repeat_char(mark, annotation.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.
@@ -511,6 +517,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.
@@ -528,19 +535,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)]
@@ -878,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;
@@ -943,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!()
                 }
@@ -986,7 +1002,6 @@ fn format_body(
     let mut current_line = snippet.line_start;
     let mut current_index = 0;
 
-    let mut annotation_line_count = 0;
     let mut annotations = snippet.annotations;
     for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() {
         let line_length: usize = line.len();
@@ -998,6 +1013,7 @@ fn format_body(
                 text: line,
                 range: line_range,
             },
+            annotations: vec![],
         });
         let line_start_index = line_range.0;
         let line_end_index = line_range.1;
@@ -1009,7 +1025,7 @@ fn format_body(
             .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,
@@ -1021,39 +1037,38 @@ fn format_body(
                     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>()
+                            - 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);
+                        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 }
@@ -1072,30 +1087,27 @@ 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;
+                        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
                 }
@@ -1119,6 +1131,7 @@ fn format_body(
                 {
                     if let DisplayLine::Source {
                         ref mut inline_marks,
+                        ref mut annotations,
                         ..
                     } = body[body_idx]
                     {
@@ -1126,35 +1139,24 @@ 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 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_mark = line[0..(end - line_start_index)]
+                            .chars()
+                            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
+                            .sum::<usize>()
+                            .saturating_sub(1)
+                            - margin_left;
+                        let range = (end_mark, end_mark + 1);
+                        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,
@@ -1173,6 +1175,7 @@ fn format_body(
                 lineno: None,
                 inline_marks: vec![],
                 line: DisplaySourceLine::Empty,
+                annotations: vec![],
             },
         );
     }
@@ -1182,12 +1185,14 @@ 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

From 73919a734acdfe208086580386b20fa6b9ee5d63 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 15 Mar 2024 12:05:55 -0600
Subject: [PATCH 139/302] fix!: Create Margin within each DisplaySet

BREAKING CHANGE: column_width is exposed instead of Margin
---
 src/renderer/display_list.rs                  | 266 ++++++++++++------
 src/renderer/margin.rs                        |  28 +-
 src/renderer/mod.rs                           |  30 +-
 tests/fixtures/deserialize.rs                 |  46 +--
 tests/fixtures/no-color/strip_line.toml       |   7 -
 tests/fixtures/no-color/strip_line_char.toml  |   7 -
 tests/fixtures/no-color/strip_line_non_ws.svg |  12 +-
 .../fixtures/no-color/strip_line_non_ws.toml  |  21 +-
 8 files changed, 220 insertions(+), 197 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 250cfc4..53caa38 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -32,11 +32,12 @@
 //!
 //! 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";
@@ -103,9 +104,9 @@ impl<'a> DisplayList<'a> {
         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,
@@ -147,7 +148,7 @@ impl<'a> DisplayList<'a> {
 #[derive(Debug, PartialEq)]
 pub(crate) struct DisplaySet<'a> {
     pub display_lines: Vec<DisplayLine<'a>>,
-    pub margin: Option<Margin>,
+    pub margin: Margin,
 }
 
 impl<'a> DisplaySet<'a> {
@@ -335,66 +336,50 @@ impl<'a> DisplaySet<'a> {
                         self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?;
                     }
                     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)
-                                }
-                            });
+                    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);
 
-                        // Format text with margins
-                        text[range.0.expect("One character at line")..range.1].fmt(f)?;
+                    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 cut_right {
-                            // We have stripped some code after the right-most span end, make it clear we did so.
-                            "...".fmt(f)?;
-                        }
+                    if self.margin.was_cut_right(line_len) {
+                        code[..taken.saturating_sub(3)].fmt(f)?;
+                        "...".fmt(f)?;
                     } else {
-                        text.fmt(f)?;
+                        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 {
@@ -415,7 +400,7 @@ impl<'a> DisplaySet<'a> {
                                 f,
                             )?;
                         }
-                        self.format_source_annotation(annotation, stylesheet, f)?;
+                        self.format_source_annotation(annotation, left, stylesheet, f)?;
                     }
                 } else if !inline_marks.is_empty() {
                     f.write_char(' ')?;
@@ -458,6 +443,7 @@ impl<'a> DisplaySet<'a> {
     fn format_source_annotation(
         &self,
         annotation: &DisplaySourceAnnotation<'_>,
+        left: usize,
         stylesheet: &Stylesheet,
         f: &mut fmt::Formatter<'_>,
     ) -> fmt::Result {
@@ -476,13 +462,17 @@ impl<'a> DisplaySet<'a> {
             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 => annotation.range.1,
-            _ => annotation.range.0,
+            DisplayAnnotationPart::LabelContinuation => range.1,
+            _ => range.0,
         };
         write!(f, "{}", color.render())?;
         format_repeat_char(indent_char, indent_length + 1, f)?;
-        format_repeat_char(mark, annotation.range.1 - indent_length, f)?;
+        format_repeat_char(mark, range.1 - indent_length, f)?;
         write!(f, "{}", color.render_reset())?;
 
         if !is_annotation_empty(&annotation.annotation) {
@@ -713,7 +703,8 @@ fn format_message(
         footer,
         snippets,
     }: snippet::Message<'_>,
-    margin: Option<Margin>,
+    term_width: usize,
+    anonymized_line_numbers: bool,
     primary: bool,
 ) -> Vec<DisplaySet<'_>> {
     let mut sets = vec![];
@@ -728,7 +719,8 @@ fn format_message(
             snippet,
             idx == 0,
             !footer.is_empty(),
-            margin,
+            term_width,
+            anonymized_line_numbers,
         ));
     }
 
@@ -739,12 +731,17 @@ fn format_message(
     } else {
         sets.push(DisplaySet {
             display_lines: body,
-            margin,
+            margin: Margin::new(0, 0, 0, 0, DEFAULT_TERM_WIDTH, 0),
         });
     }
 
     for annotation in footer {
-        sets.extend(format_message(annotation, margin, false));
+        sets.extend(format_message(
+            annotation,
+            term_width,
+            anonymized_line_numbers,
+            false,
+        ));
     }
 
     sets
@@ -801,23 +798,26 @@ fn format_snippet(
     snippet: snippet::Snippet<'_>,
     is_first: bool,
     has_footer: bool,
-    margin: Option<Margin>,
+    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);
-    }
-    result.extend(body);
-    DisplaySet {
-        display_lines: result,
-        margin,
+        body.display_lines.insert(0, header);
     }
+
+    body
 }
 
 #[inline]
@@ -981,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.
@@ -1002,6 +1003,12 @@ fn format_body(
     let mut current_line = snippet.line_start;
     let mut current_index = 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();
@@ -1015,14 +1022,28 @@ fn format_body(
             },
             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;
@@ -1031,6 +1052,7 @@ fn format_body(
                 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 }
@@ -1045,8 +1067,7 @@ fn format_body(
                         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;
+                            .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);
@@ -1054,8 +1075,12 @@ fn format_body(
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
                             .sum::<usize>()
-                            + safe_end
-                            - margin_left;
+                            + 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 {
@@ -1097,6 +1122,12 @@ fn format_body(
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
                             .sum::<usize>();
                         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 {
@@ -1143,9 +1174,15 @@ fn format_body(
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
                             .sum::<usize>()
-                            .saturating_sub(1)
-                            - margin_left;
-                        let range = (end_mark, end_mark + 1);
+                            .saturating_sub(1);
+
+                        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,
@@ -1195,7 +1232,31 @@ fn format_body(
             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 {
@@ -1252,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 7636603..3f1b28b 100644
--- a/src/renderer/margin.rs
+++ b/src/renderer/margin.rs
@@ -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..b518cfb 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -21,11 +21,13 @@ pub 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,
         )
     }
 }
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

From e89fd82cdcded1a29791f558a2d5bda7712bacb1 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 15 Mar 2024 12:08:09 -0600
Subject: [PATCH 140/302] fix!: Make Margin private

---
 src/renderer/mod.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index b518cfb..ee63da9 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -17,7 +17,7 @@ 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;
 

From e6b273b2864b54668be271811b07db51b192f3fd Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 15 Mar 2024 15:15:51 -0600
Subject: [PATCH 141/302] chore: Update CHANGELOG.md

---
 CHANGELOG.md | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f46022..268aaf5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+### Breaking Changes
+
+- Switched from char spans to byte spans [#90](https://github.com/rust-lang/annotate-snippets-rs/pull/90/commits/b65b8cabcd34da9fed88490a7a1cd8085777706a)
+- Renamed `AnnotationType` to `Level` [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/b49f9471d920c7f561fa61970039b0ba44e448ac)
+- Renamed `SourceAnnotation` to `Annotation` [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/bbf9c5fe27e83652433151cbfc7d6cafc02a8c47)
+- Renamed `Snippet` to `Message` [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/105da760b6e1bd4cfce4c642ac679ecf6011f511)
+- Renamed `Slice` to `Snippet` [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/1c18950300cf8b93d92d89e9797ed0bae02c0a37)
+- `Message`, `Snippet`, `Annotation` and `Level` can only be built with a builder pattern [#91](https://github.com/rust-lang/annotate-snippets-rs/pull/91) and [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94)
+- `Annotation` labels are now optional [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/c821084068a1acd2688b6c8d0b3423e143d359e2)
+- `Annotation` now takes in `Range<usize>` instead of `(usize, usize)` [#90](https://github.com/rust-lang/annotate-snippets-rs/pull/90/commits/c3bd0c3a63f983f5f2b4793a099972b1f6e97a9f)
+- `Margin` is now an internal detail, only `term_width` is exposed [#105](https://github.com/rust-lang/annotate-snippets-rs/pull/105)
+- `footer` was generalized to be a `Message` [#98](https://github.com/rust-lang/annotate-snippets-rs/pull/98)
+
+### Added
+- `term_width` was added to `Renderer` to control the rendering width [#105](https://github.com/rust-lang/annotate-snippets-rs/pull/105)
+  - defaults to 140 when not set
+
+### Fixed
+- `Margin`s are now calculated per `Snippet`, rather than for the entire `Message` [#105](https://github.com/rust-lang/annotate-snippets-rs/pull/105)
+- `Annotation`s can be created without labels
+
+### Features
+- `footer` was expanded to allow annotating sources by accepting `Message` [#98](https://github.com/rust-lang/annotate-snippets-rs/pull/98)
 
 ## [0.10.2] - 2024-02-29
 

From d0e7997c6859a4ca690f81f019e158743dffd0f7 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 15 Mar 2024 15:18:16 -0600
Subject: [PATCH 142/302] chore: Release annotate-snippets version 0.11.0

---
 CHANGELOG.md | 5 ++++-
 Cargo.lock   | 2 +-
 Cargo.toml   | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 268aaf5..ad3df89 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+## [0.11.0] - 2024-03-15
+
 ### Breaking Changes
 
 - Switched from char spans to byte spans [#90](https://github.com/rust-lang/annotate-snippets-rs/pull/90/commits/b65b8cabcd34da9fed88490a7a1cd8085777706a)
@@ -116,7 +118,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Update the syntax to Rust 2018 idioms. (#4)
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...HEAD
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...HEAD
+[0.11.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...0.11.0
 [0.10.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...0.10.2
 [0.10.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...0.10.1
 [0.10.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...0.10.0
diff --git a/Cargo.lock b/Cargo.lock
index 0629775..c18b289 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "annotate-snippets"
-version = "0.10.2"
+version = "0.11.0"
 dependencies = [
  "anstream",
  "anstyle",
diff --git a/Cargo.toml b/Cargo.toml
index 5f685e1..17b6600 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "annotate-snippets"
-version = "0.10.2"
+version = "0.11.0"
 edition = "2021"
 rust-version = "1.73"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]

From 3d5ead81cf3962997045915cd9b137086d35d1a9 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 10:46:56 -0500
Subject: [PATCH 143/302] chore(ci): Configure standard lints

---
 Cargo.toml | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/lib.rs |  2 ++
 2 files changed, 86 insertions(+)

diff --git a/Cargo.toml b/Cargo.toml
index b8ecde1..e51c5dd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,87 @@ include = [
   "examples/**/*"
 ]
 
+[workspace.lints.rust]
+missing_docs = "warn"
+rust_2018_idioms = "warn"
+unreachable_pub = "warn"
+unsafe-op-in-unsafe-fn = "warn"
+unsafe_code = "warn"
+unused-crate-dependencies = "warn"
+unused-lifetimes = "warn"
+unused-macro-rules = "warn"
+unused-qualifications = "warn"
+
+[workspace.lints.clippy]
+bool_assert_comparison = "allow"
+branches_sharing_code = "allow"
+checked_conversions = "warn"
+collapsible_else_if = "allow"
+create_dir = "warn"
+dbg_macro = "warn"
+debug_assert_with_mut_call = "warn"
+doc_markdown = "warn"
+empty_enum = "warn"
+enum_glob_use = "warn"
+exhaustive_enums = "warn"
+exhaustive_structs = "warn"
+exit = "warn"
+expl_impl_clone_on_copy = "warn"
+explicit_deref_methods = "warn"
+explicit_into_iter_loop = "warn"
+fallible_impl_from = "warn"
+filter_map_next = "warn"
+flat_map_option = "warn"
+float_cmp_const = "warn"
+fn_params_excessive_bools = "warn"
+from_iter_instead_of_collect = "warn"
+if_same_then_else = "allow"
+implicit_clone = "warn"
+imprecise_flops = "warn"
+inconsistent_struct_constructor = "warn"
+inefficient_to_string = "warn"
+infinite_loop = "warn"
+invalid_upcast_comparisons = "warn"
+items_after_statements = "warn"
+large_digit_groups = "warn"
+large_stack_arrays = "warn"
+large_types_passed_by_value = "warn"
+let_and_return = "allow"  # sometimes good to name what you are returning
+linkedlist = "warn"
+lossy_float_literal = "warn"
+macro_use_imports = "warn"
+match_wildcard_for_single_variants = "warn"
+mem_forget = "warn"
+mutex_integer = "warn"
+needless_continue = "warn"
+needless_for_each = "warn"
+negative_feature_names = "warn"
+path_buf_push_overwrite = "warn"
+print_stderr = "warn"
+print_stdout = "warn"
+ptr_as_ptr = "warn"
+rc_mutex = "warn"
+redundant_feature_names = "warn"
+ref_option_ref = "warn"
+rest_pat_in_fully_bound_structs = "warn"
+return_self_not_must_use = "warn"
+same_functions_in_if_condition = "warn"
+self_named_module_files = "warn"
+semicolon_if_nothing_returned = "warn"
+single_match_else = "warn"
+str_to_string = "warn"
+string_add = "warn"
+string_add_assign = "warn"
+string_lit_as_bytes = "warn"
+string_to_string = "warn"
+tests_outside_test_module = "warn"
+todo = "warn"
+trait_duplication_in_bounds = "warn"
+unwrap_used = "warn"
+verbose_file_reads = "warn"
+wildcard_imports = "warn"
+zero_sized_map_values = "warn"
+
 [package]
 name = "PROJECT"
 version = "0.0.1"
@@ -46,3 +127,6 @@ default = []
 [dependencies]
 
 [dev-dependencies]
+
+[lints]
+workspace = true
diff --git a/src/lib.rs b/src/lib.rs
index 45bf577..8ce46b5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,2 +1,4 @@
+//! > DESCRIPTION
+
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
 #![allow(non_snake_case)] // TODO: Delete me

From 54975a06d41eb142077aaf2732bfe563a26efa31 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 11:51:15 -0500
Subject: [PATCH 144/302] test: Clarify test case name

---
 tests/fixtures/no-color/{one_past.svg => ann_removed_nl.svg}   | 0
 tests/fixtures/no-color/{one_past.toml => ann_removed_nl.toml} | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename tests/fixtures/no-color/{one_past.svg => ann_removed_nl.svg} (100%)
 rename tests/fixtures/no-color/{one_past.toml => ann_removed_nl.toml} (100%)

diff --git a/tests/fixtures/no-color/one_past.svg b/tests/fixtures/no-color/ann_removed_nl.svg
similarity index 100%
rename from tests/fixtures/no-color/one_past.svg
rename to tests/fixtures/no-color/ann_removed_nl.svg
diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/ann_removed_nl.toml
similarity index 100%
rename from tests/fixtures/no-color/one_past.toml
rename to tests/fixtures/no-color/ann_removed_nl.toml

From 01ca512f66fc38f9fd3ce6f4ad103e97fb91ffd2 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 11:53:17 -0500
Subject: [PATCH 145/302] test: Show existing EOF annotation behavior

---
 tests/fixtures/no-color/ann_eof.svg  | 33 ++++++++++++++++++++++++++++
 tests/fixtures/no-color/ann_eof.toml | 12 ++++++++++
 2 files changed, 45 insertions(+)
 create mode 100644 tests/fixtures/no-color/ann_eof.svg
 create mode 100644 tests/fixtures/no-color/ann_eof.toml

diff --git a/tests/fixtures/no-color/ann_eof.svg b/tests/fixtures/no-color/ann_eof.svg
new file mode 100644
index 0000000..ba3b204
--- /dev/null
+++ b/tests/fixtures/no-color/ann_eof.svg
@@ -0,0 +1,33 @@
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:5</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>1 | asdf</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>  |     </tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>  |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/ann_eof.toml b/tests/fixtures/no-color/ann_eof.toml
new file mode 100644
index 0000000..313d220
--- /dev/null
+++ b/tests/fixtures/no-color/ann_eof.toml
@@ -0,0 +1,12 @@
+[message]
+level = "Error"
+title = "expected `.`, `=`"
+
+[[message.snippets]]
+source = "asdf"
+line_start = 1
+origin = "Cargo.toml"
+[[message.snippets.annotations]]
+label = ""
+level = "Error"
+range = [4, 4]

From 0e820651c901ed2772a842d45d033f1c6ff1c5e9 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 11:54:45 -0500
Subject: [PATCH 146/302] test: Show existing empty span behavior

---
 tests/fixtures/no-color/ann_insertion.svg  | 33 ++++++++++++++++++++++
 tests/fixtures/no-color/ann_insertion.toml | 12 ++++++++
 2 files changed, 45 insertions(+)
 create mode 100644 tests/fixtures/no-color/ann_insertion.svg
 create mode 100644 tests/fixtures/no-color/ann_insertion.toml

diff --git a/tests/fixtures/no-color/ann_insertion.svg b/tests/fixtures/no-color/ann_insertion.svg
new file mode 100644
index 0000000..ba3b204
--- /dev/null
+++ b/tests/fixtures/no-color/ann_insertion.svg
@@ -0,0 +1,33 @@
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:5</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>1 | asdf</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>  |     </tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>  |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/ann_insertion.toml b/tests/fixtures/no-color/ann_insertion.toml
new file mode 100644
index 0000000..ffd2140
--- /dev/null
+++ b/tests/fixtures/no-color/ann_insertion.toml
@@ -0,0 +1,12 @@
+[message]
+level = "Error"
+title = "expected `.`, `=`"
+
+[[message.snippets]]
+source = "asf"
+line_start = 1
+origin = "Cargo.toml"
+[[message.snippets.annotations]]
+label = "'d' belongs here"
+level = "Error"
+range = [2, 2]

From 7310fd4c45ed2d92f0ecabb155ba8e5a3bb86bb2 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 11:58:03 -0500
Subject: [PATCH 147/302] fix: Allow empty spans

---
 src/renderer/display_list.rs              | 14 ++++++++------
 tests/fixtures/no-color/ann_eof.svg       |  2 +-
 tests/fixtures/no-color/ann_insertion.svg |  6 +++---
 3 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 53caa38..4d2836a 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1057,6 +1057,7 @@ fn format_body(
                 Range { start, .. } if start > line_end_index => true,
                 Range { start, end }
                     if start >= line_start_index && end <= line_end_index
+                        // Allow annotating eof or stripped eol
                         || start == line_end_index && end - start <= 1 =>
                 {
                     if let DisplayLine::Source {
@@ -1068,14 +1069,15 @@ fn format_body(
                             .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]
+                        let mut annotation_end_col = line
+                            [0..(end - line_start_index).min(line_length)]
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                            .sum::<usize>()
-                            + safe_end;
+                            .sum::<usize>();
+                        if annotation_start_col == annotation_end_col {
+                            // At least highlight something
+                            annotation_end_col += 1;
+                        }
 
                         span_left_margin = min(span_left_margin, annotation_start_col);
                         span_right_margin = max(span_right_margin, annotation_end_col);
diff --git a/tests/fixtures/no-color/ann_eof.svg b/tests/fixtures/no-color/ann_eof.svg
index ba3b204..c8900d0 100644
--- a/tests/fixtures/no-color/ann_eof.svg
+++ b/tests/fixtures/no-color/ann_eof.svg
@@ -24,7 +24,7 @@
 </tspan>
     <tspan x="10px" y="82px"><tspan>1 | asdf</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  |     </tspan>
+    <tspan x="10px" y="100px"><tspan>  |     ^</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan>  |</tspan>
 </tspan>
diff --git a/tests/fixtures/no-color/ann_insertion.svg b/tests/fixtures/no-color/ann_insertion.svg
index ba3b204..b15b81b 100644
--- a/tests/fixtures/no-color/ann_insertion.svg
+++ b/tests/fixtures/no-color/ann_insertion.svg
@@ -18,13 +18,13 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:5</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:3</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>  |</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>1 | asdf</tspan>
+    <tspan x="10px" y="82px"><tspan>1 | asf</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  |     </tspan>
+    <tspan x="10px" y="100px"><tspan>  |   ^ 'd' belongs here</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan>  |</tspan>
 </tspan>

From ace6e07683db64f05a237d8c833e2320a951b5d4 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 12:20:47 -0500
Subject: [PATCH 148/302] chore(ci): Don't update stable and MSRV together

We might want to hold one or the other back
---
 .github/renovate.json5 | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 06c1d63..373fc0e 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -59,10 +59,9 @@
       schedule: [
         '* * * * *',
       ],
-      groupName: 'rust-version',
     },
     {
-      commitMessageTopic: 'STABLE',
+      commitMessageTopic: 'Rust Stable',
       matchManagers: [
         'custom.regex',
       ],
@@ -73,7 +72,6 @@
       schedule: [
         '* * * * *',
       ],
-      groupName: 'rust-version',
     },
     // Goals:
     // - Keep version reqs low, ignoring compatible normal/build dependencies

From de0e330ba2295df8697f73b66feca2107b9da430 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 12:22:05 -0500
Subject: [PATCH 149/302] test(render): Clarify a test name

---
 .../fixtures/no-color/{issue_52.svg => fold_bad_origin_line.svg}  | 0
 .../no-color/{issue_52.toml => fold_bad_origin_line.toml}         | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename tests/fixtures/no-color/{issue_52.svg => fold_bad_origin_line.svg} (100%)
 rename tests/fixtures/no-color/{issue_52.toml => fold_bad_origin_line.toml} (100%)

diff --git a/tests/fixtures/no-color/issue_52.svg b/tests/fixtures/no-color/fold_bad_origin_line.svg
similarity index 100%
rename from tests/fixtures/no-color/issue_52.svg
rename to tests/fixtures/no-color/fold_bad_origin_line.svg
diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/fold_bad_origin_line.toml
similarity index 100%
rename from tests/fixtures/no-color/issue_52.toml
rename to tests/fixtures/no-color/fold_bad_origin_line.toml

From 22e6c1c5f1363304324e03319c2270110b5ac6fb Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 12:27:20 -0500
Subject: [PATCH 150/302] test(render): Clarify multiline cases

As `fold` is special machinery, I made that test under that.

The others, I couldn't tell what different cases they were covering, so
I kept the names.
I did name them with the `ann` prefix to highlight these are testing
annotations.
---
 .../no-color/{multiline_annotation2.svg => ann_multiline.svg}     | 0
 .../no-color/{multiline_annotation2.toml => ann_multiline.toml}   | 0
 .../no-color/{multiline_annotation3.svg => ann_multiline2.svg}    | 0
 .../no-color/{multiline_annotation3.toml => ann_multiline2.toml}  | 0
 .../no-color/{multiline_annotation.svg => fold_ann_multiline.svg} | 0
 .../{multiline_annotation.toml => fold_ann_multiline.toml}        | 0
 6 files changed, 0 insertions(+), 0 deletions(-)
 rename tests/fixtures/no-color/{multiline_annotation2.svg => ann_multiline.svg} (100%)
 rename tests/fixtures/no-color/{multiline_annotation2.toml => ann_multiline.toml} (100%)
 rename tests/fixtures/no-color/{multiline_annotation3.svg => ann_multiline2.svg} (100%)
 rename tests/fixtures/no-color/{multiline_annotation3.toml => ann_multiline2.toml} (100%)
 rename tests/fixtures/no-color/{multiline_annotation.svg => fold_ann_multiline.svg} (100%)
 rename tests/fixtures/no-color/{multiline_annotation.toml => fold_ann_multiline.toml} (100%)

diff --git a/tests/fixtures/no-color/multiline_annotation2.svg b/tests/fixtures/no-color/ann_multiline.svg
similarity index 100%
rename from tests/fixtures/no-color/multiline_annotation2.svg
rename to tests/fixtures/no-color/ann_multiline.svg
diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/ann_multiline.toml
similarity index 100%
rename from tests/fixtures/no-color/multiline_annotation2.toml
rename to tests/fixtures/no-color/ann_multiline.toml
diff --git a/tests/fixtures/no-color/multiline_annotation3.svg b/tests/fixtures/no-color/ann_multiline2.svg
similarity index 100%
rename from tests/fixtures/no-color/multiline_annotation3.svg
rename to tests/fixtures/no-color/ann_multiline2.svg
diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/ann_multiline2.toml
similarity index 100%
rename from tests/fixtures/no-color/multiline_annotation3.toml
rename to tests/fixtures/no-color/ann_multiline2.toml
diff --git a/tests/fixtures/no-color/multiline_annotation.svg b/tests/fixtures/no-color/fold_ann_multiline.svg
similarity index 100%
rename from tests/fixtures/no-color/multiline_annotation.svg
rename to tests/fixtures/no-color/fold_ann_multiline.svg
diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/fold_ann_multiline.toml
similarity index 100%
rename from tests/fixtures/no-color/multiline_annotation.toml
rename to tests/fixtures/no-color/fold_ann_multiline.toml

From a741b6bd90662651084067afa936499196381d9a Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 12:46:37 -0500
Subject: [PATCH 151/302] test(render): Show existing leading / trailing
 behavior

---
 tests/fixtures/no-color/fold_leading.svg   | 45 ++++++++++++++++++++++
 tests/fixtures/no-color/fold_leading.toml  | 26 +++++++++++++
 tests/fixtures/no-color/fold_trailing.svg  | 33 ++++++++++++++++
 tests/fixtures/no-color/fold_trailing.toml | 25 ++++++++++++
 4 files changed, 129 insertions(+)
 create mode 100644 tests/fixtures/no-color/fold_leading.svg
 create mode 100644 tests/fixtures/no-color/fold_leading.toml
 create mode 100644 tests/fixtures/no-color/fold_trailing.svg
 create mode 100644 tests/fixtures/no-color/fold_trailing.toml

diff --git a/tests/fixtures/no-color/fold_leading.svg b/tests/fixtures/no-color/fold_leading.svg
new file mode 100644
index 0000000..c85f9d7
--- /dev/null
+++ b/tests/fixtures/no-color/fold_leading.svg
@@ -0,0 +1,45 @@
+<svg width="740px" height="236px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error[E0308]: invalid type: integer `20`, expected a bool</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>  --&gt; Cargo.toml:11:13</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>   |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan> 1 | [workspace]</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan> 2 | </tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan> 3 | [package]</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan> 4 | name = "hello"</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>...</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan>10 | [lints]</tspan>
+</tspan>
+    <tspan x="10px" y="190px"><tspan>11 | workspace = 20</tspan>
+</tspan>
+    <tspan x="10px" y="208px"><tspan>   |             ^^</tspan>
+</tspan>
+    <tspan x="10px" y="226px"><tspan>   |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/fold_leading.toml b/tests/fixtures/no-color/fold_leading.toml
new file mode 100644
index 0000000..e3fc696
--- /dev/null
+++ b/tests/fixtures/no-color/fold_leading.toml
@@ -0,0 +1,26 @@
+[message]
+level = "Error"
+id = "E0308"
+title = "invalid type: integer `20`, expected a bool"
+
+[[message.snippets]]
+source = """
+[workspace]
+
+[package]
+name = "hello"
+version = "1.0.0"
+license = "MIT"
+rust-version = "1.70"
+edition = "2021"
+
+[lints]
+workspace = 20
+"""
+line_start = 1
+origin = "Cargo.toml"
+fold = true
+[[message.snippets.annotations]]
+label = ""
+level = "Error"
+range = [132, 134]
diff --git a/tests/fixtures/no-color/fold_trailing.svg b/tests/fixtures/no-color/fold_trailing.svg
new file mode 100644
index 0000000..15c9850
--- /dev/null
+++ b/tests/fixtures/no-color/fold_trailing.svg
@@ -0,0 +1,33 @@
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error[E0308]: invalid type: integer `20`, expected a lints table</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:9</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>1 | lints = 20</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>  |         ^^</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>  |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/fold_trailing.toml b/tests/fixtures/no-color/fold_trailing.toml
new file mode 100644
index 0000000..8ee4c05
--- /dev/null
+++ b/tests/fixtures/no-color/fold_trailing.toml
@@ -0,0 +1,25 @@
+[message]
+level = "Error"
+id = "E0308"
+title = "invalid type: integer `20`, expected a lints table"
+
+[[message.snippets]]
+source = """
+lints = 20
+
+[workspace]
+
+[package]
+name = "hello"
+version = "1.0.0"
+license = "MIT"
+rust-version = "1.70"
+edition = "2021"
+"""
+line_start = 1
+origin = "Cargo.toml"
+fold = true
+[[message.snippets.annotations]]
+label = ""
+level = "Error"
+range = [8, 10]

From 894f734291bb499267d53ec34d8402fea7f18c84 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 13:05:21 -0500
Subject: [PATCH 152/302] fix(render): On fold, strip prefix/suffix rather than
 fold

As we don't show `...` on non-first lines without fold, it seems to make
sense to do the same thing for fold.
To show this, we likely would want to add more nuance than just `fold`.

This has the benefit of offering a "magic mode" where the annotated
lines get selected automatically.

This was implemented by pre-processing things which should keep overhead
low as other calculations down the line aren't done.
---
 src/renderer/display_list.rs                  | 55 +++++++++++++++++--
 .../no-color/fold_bad_origin_line.svg         | 10 ++--
 tests/fixtures/no-color/fold_leading.svg      | 20 ++-----
 tests/formatter.rs                            |  1 -
 4 files changed, 57 insertions(+), 29 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 4d2836a..fa8274c 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -696,17 +696,19 @@ impl<'a> Iterator for CursorLines<'a> {
 }
 
 fn format_message(
-    snippet::Message {
+    message: snippet::Message<'_>,
+    term_width: usize,
+    anonymized_line_numbers: bool,
+    primary: bool,
+) -> Vec<DisplaySet<'_>> {
+    let snippet::Message {
         level,
         id,
         title,
         footer,
         snippets,
-    }: snippet::Message<'_>,
-    term_width: usize,
-    anonymized_line_numbers: bool,
-    primary: bool,
-) -> Vec<DisplaySet<'_>> {
+    } = message;
+
     let mut sets = vec![];
     let body = if !snippets.is_empty() || primary {
         vec![format_title(level, id, title)]
@@ -715,6 +717,7 @@ fn format_message(
     };
 
     for (idx, snippet) in snippets.into_iter().enumerate() {
+        let snippet = fold_prefix_suffix(snippet);
         sets.push(format_snippet(
             snippet,
             idx == 0,
@@ -876,6 +879,46 @@ fn format_header<'a>(
     None
 }
 
+fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_> {
+    if !snippet.fold {
+        return snippet;
+    }
+
+    let ann_start = snippet
+        .annotations
+        .iter()
+        .map(|ann| ann.range.start)
+        .min()
+        .unwrap_or(0);
+    if let Some(before_new_start) = snippet.source[0..ann_start].rfind('\n') {
+        let new_start = before_new_start + 1;
+
+        let line_offset = snippet.source[..new_start].lines().count();
+        snippet.line_start += line_offset;
+
+        snippet.source = &snippet.source[new_start..];
+
+        for ann in &mut snippet.annotations {
+            let range_start = ann.range.start - new_start;
+            let range_end = ann.range.end - new_start;
+            ann.range = range_start..range_end;
+        }
+    }
+
+    let ann_end = snippet
+        .annotations
+        .iter()
+        .map(|ann| ann.range.end)
+        .max()
+        .unwrap_or(snippet.source.len());
+    if let Some(end_offset) = snippet.source[ann_end..].find('\n') {
+        let new_end = ann_end + end_offset;
+        snippet.source = &snippet.source[..new_end];
+    }
+
+    snippet
+}
+
 fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
     enum Line {
         Fold(usize),
diff --git a/tests/fixtures/no-color/fold_bad_origin_line.svg b/tests/fixtures/no-color/fold_bad_origin_line.svg
index 5ed2228..13a0834 100644
--- a/tests/fixtures/no-color/fold_bad_origin_line.svg
+++ b/tests/fixtures/no-color/fold_bad_origin_line.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -22,13 +22,11 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>  |</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>...</tspan>
+    <tspan x="10px" y="82px"><tspan>3 | invalid syntax</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>3 | invalid syntax</tspan>
+    <tspan x="10px" y="100px"><tspan>  | -------------- error here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  | -------------- error here</tspan>
-</tspan>
-    <tspan x="10px" y="136px"><tspan>  |</tspan>
+    <tspan x="10px" y="118px"><tspan>  |</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/no-color/fold_leading.svg b/tests/fixtures/no-color/fold_leading.svg
index c85f9d7..72887a2 100644
--- a/tests/fixtures/no-color/fold_leading.svg
+++ b/tests/fixtures/no-color/fold_leading.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="236px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -22,23 +22,11 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   |</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan> 1 | [workspace]</tspan>
+    <tspan x="10px" y="82px"><tspan>11 | workspace = 20</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan> 2 | </tspan>
+    <tspan x="10px" y="100px"><tspan>   |             ^^</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan> 3 | [package]</tspan>
-</tspan>
-    <tspan x="10px" y="136px"><tspan> 4 | name = "hello"</tspan>
-</tspan>
-    <tspan x="10px" y="154px"><tspan>...</tspan>
-</tspan>
-    <tspan x="10px" y="172px"><tspan>10 | [lints]</tspan>
-</tspan>
-    <tspan x="10px" y="190px"><tspan>11 | workspace = 20</tspan>
-</tspan>
-    <tspan x="10px" y="208px"><tspan>   |             ^^</tspan>
-</tspan>
-    <tspan x="10px" y="226px"><tspan>   |</tspan>
+    <tspan x="10px" y="118px"><tspan>   |</tspan>
 </tspan>
   </text>
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 3b02489..ebe03ff 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -14,7 +14,6 @@ fn test_i_29() {
 error: oops
  --> <current file>:2:8
   |
-1 | First line
 2 | Second oops line
   |        ^^^^ oops
   |"#]]

From 6e55eccc4ab63394b3221c4c51fe09cfdc8a330f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 14:41:10 -0500
Subject: [PATCH 153/302] fix(render): Fold like rustc

Before, we elided all small sets of unhighlighted lines and then
partially elided large sets of unhighlighted lines (favoring the start).

In contrast, rustc shows 2 lines of context, split between the start and
end of the fold.
If there are 3 unhighlighted lines, it just skips folding.
This makes a lot more sense.
See https://github.com/rust-lang/rust/blob/2627e9f3012a97d3136b3e11bf6bd0853c38a534/compiler/rustc_errors/src/emitter.rs#L1878C17-L1939C18

In contrast to rustc, I made the amount of context a variable to make
the intent clearer and to open the door to it being configurable.
---
 examples/expected_type.svg                    |  14 +-
 src/renderer/display_list.rs                  | 130 ++++++------------
 .../fixtures/no-color/fold_ann_multiline.svg  |  18 +--
 3 files changed, 59 insertions(+), 103 deletions(-)

diff --git a/examples/expected_type.svg b/examples/expected_type.svg
index ae06504..a355dbc 100644
--- a/examples/expected_type.svg
+++ b/examples/expected_type.svg
@@ -1,4 +1,4 @@
-<svg width="860px" height="200px" xmlns="http://www.w3.org/2000/svg">
+<svg width="860px" height="218px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -29,15 +29,17 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-blue bold">                                   ----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info</tspan><tspan class="fg-bright-blue bold">: while parsing this struct</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>...</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan>                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan>                     ,</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-red bold">                         ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">   |</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-red bold">                         ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
-    <tspan x="10px" y="190px">
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">   |</tspan>
+</tspan>
+    <tspan x="10px" y="208px">
 </tspan>
   </text>
 
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index fa8274c..ead3db9 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -919,105 +919,65 @@ fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_>
     snippet
 }
 
-fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
-    enum Line {
-        Fold(usize),
-        Source(usize),
-    }
+fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
+    const INNER_CONTEXT: usize = 1;
+    const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1;
 
     let mut lines = vec![];
-    let mut no_annotation_lines_counter = 0;
-    for (idx, line) in body.iter().enumerate() {
-        match line {
-            DisplayLine::Source {
-                line: DisplaySourceLine::Content { .. },
-                annotations,
-                ..
-            } => {
+    let mut unhighlighed_lines = vec![];
+    for line in body {
+        match &line {
+            DisplayLine::Source { annotations, .. } => {
                 if annotations.is_empty() {
-                    no_annotation_lines_counter += 1;
-                    continue;
+                    unhighlighed_lines.push(line);
                 } else {
-                    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));
+                    if lines.is_empty() {
+                        // Ignore leading unhighlighed lines
+                        unhighlighed_lines.clear();
+                    }
+                    match unhighlighed_lines.len() {
+                        0 => {}
+                        n if n <= INNER_UNFOLD_SIZE => {
+                            // Rather than render `...`, don't fold
+                            lines.append(&mut unhighlighed_lines);
                         }
-                    } else {
-                        for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) {
-                            lines.push(Line::Source(i));
+                        _ => {
+                            lines.extend(unhighlighed_lines.drain(..INNER_CONTEXT));
+                            let inline_marks = lines
+                                .last()
+                                .and_then(|line| {
+                                    if let DisplayLine::Source {
+                                        ref inline_marks, ..
+                                    } = line
+                                    {
+                                        let mut inline_marks = inline_marks.clone();
+                                        for mark in &mut inline_marks {
+                                            mark.mark_type = DisplayMarkType::AnnotationThrough;
+                                        }
+                                        Some(inline_marks)
+                                    } else {
+                                        None
+                                    }
+                                })
+                                .unwrap_or_default();
+                            lines.push(DisplayLine::Fold {
+                                inline_marks: inline_marks.clone(),
+                            });
+                            unhighlighed_lines
+                                .drain(..unhighlighed_lines.len().saturating_sub(INNER_CONTEXT));
+                            lines.append(&mut unhighlighed_lines);
                         }
                     }
-                    no_annotation_lines_counter = 0;
+                    lines.push(line);
                 }
             }
-            DisplayLine::Source { .. } => {
-                no_annotation_lines_counter += 1;
-                continue;
-            }
             _ => {
-                no_annotation_lines_counter += 1;
-            }
-        }
-        lines.push(Line::Source(idx));
-    }
-
-    let mut new_body = vec![];
-    let mut removed = 0;
-    for line in lines {
-        match line {
-            Line::Source(i) => {
-                new_body.push(body.remove(i - removed));
-                removed += 1;
-            }
-            Line::Fold(i) => {
-                if let DisplayLine::Source {
-                    line: DisplaySourceLine::Content { .. },
-                    ref inline_marks,
-                    ref annotations,
-                    ..
-                } = body.get(i - removed).unwrap()
-                {
-                    if !annotations.is_empty() {
-                        new_body.push(DisplayLine::Fold {
-                            inline_marks: inline_marks.clone(),
-                        });
-                    } else {
-                        unreachable!()
-                    }
-                } else {
-                    unreachable!()
-                }
+                unhighlighed_lines.push(line);
             }
         }
     }
 
-    new_body
+    lines
 }
 
 fn format_body(
diff --git a/tests/fixtures/no-color/fold_ann_multiline.svg b/tests/fixtures/no-color/fold_ann_multiline.svg
index 5fa6e81..0d2d67c 100644
--- a/tests/fixtures/no-color/fold_ann_multiline.svg
+++ b/tests/fixtures/no-color/fold_ann_multiline.svg
@@ -1,4 +1,4 @@
-<svg width="869px" height="272px" xmlns="http://www.w3.org/2000/svg">
+<svg width="869px" height="218px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,21 +28,15 @@
 </tspan>
     <tspan x="10px" y="118px"><tspan>52 | /     for ann in annotations {</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>53 | |         match (ann.range.0, ann.range.1) {</tspan>
+    <tspan x="10px" y="136px"><tspan>...  |</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>54 | |             (None, None) =&gt; continue,</tspan>
+    <tspan x="10px" y="154px"><tspan>71 | |         }</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>55 | |             (Some(start), Some(end)) if start &gt; end_index || end &lt; start_index =&gt; continue,</tspan>
+    <tspan x="10px" y="172px"><tspan>72 | |     }</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan>...  |</tspan>
+    <tspan x="10px" y="190px"><tspan>   | |_____^ expected enum `std::option::Option`, found ()</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>71 | |         }</tspan>
-</tspan>
-    <tspan x="10px" y="226px"><tspan>72 | |     }</tspan>
-</tspan>
-    <tspan x="10px" y="244px"><tspan>   | |_____^ expected enum `std::option::Option`, found ()</tspan>
-</tspan>
-    <tspan x="10px" y="262px"><tspan>   |</tspan>
+    <tspan x="10px" y="208px"><tspan>   |</tspan>
 </tspan>
   </text>
 

From a2c27b5b4b5c108c07c6e252afeb011f1d8b3e17 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 21 Mar 2024 15:36:16 -0500
Subject: [PATCH 154/302] docs: Update changelog

---
 CHANGELOG.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad3df89..c91e63d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+### Fixes
+
+- Switch `fold` to use rustc's logic: always show first and last line of folded section and detect if its worth folding
+- When `fold`ing the start of a `source`, don't show anything, like we do for the end of the `source`
+- Render an underline for an empty span on `Annotation`s
+
 ## [0.11.0] - 2024-03-15
 
 ### Breaking Changes

From 54a4d181680f17b3d1b3ad61e95e66cf41748563 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 21 Mar 2024 14:44:01 -0600
Subject: [PATCH 155/302] chore: Release annotate-snippets version 0.11.1

---
 CHANGELOG.md | 5 ++++-
 Cargo.lock   | 2 +-
 Cargo.toml   | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c91e63d..85f967e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+## [0.11.1] - 2024-03-21
+
 ### Fixes
 
 - Switch `fold` to use rustc's logic: always show first and last line of folded section and detect if its worth folding
@@ -124,7 +126,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Update the syntax to Rust 2018 idioms. (#4)
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...HEAD
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...HEAD
+[0.11.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...0.11.1
 [0.11.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...0.11.0
 [0.10.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...0.10.2
 [0.10.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...0.10.1
diff --git a/Cargo.lock b/Cargo.lock
index c18b289..479ee8c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "annotate-snippets"
-version = "0.11.0"
+version = "0.11.1"
 dependencies = [
  "anstream",
  "anstyle",
diff --git a/Cargo.toml b/Cargo.toml
index 17b6600..a9a7c28 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "annotate-snippets"
-version = "0.11.0"
+version = "0.11.1"
 edition = "2021"
 rust-version = "1.73"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]

From 8ec86ab9a22aa7333af26113d8b725333966635f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 25 Mar 2024 12:33:58 -0500
Subject: [PATCH 156/302] chore: Normalize clippy lint names

---
 Cargo.toml | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index e51c5dd..d03936f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,12 +20,12 @@ include = [
 missing_docs = "warn"
 rust_2018_idioms = "warn"
 unreachable_pub = "warn"
-unsafe-op-in-unsafe-fn = "warn"
+unsafe_op_in_unsafe_fn = "warn"
 unsafe_code = "warn"
-unused-crate-dependencies = "warn"
-unused-lifetimes = "warn"
-unused-macro-rules = "warn"
-unused-qualifications = "warn"
+unused_crate_dependencies = "warn"
+unused_lifetimes = "warn"
+unused_macro_rules = "warn"
+unused_qualifications = "warn"
 
 [workspace.lints.clippy]
 bool_assert_comparison = "allow"

From 8e647d9cd40a6891d524737d97d93a43a9e7b965 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 09:35:59 -0500
Subject: [PATCH 157/302] chore: Encourage use of workspace.dependencies

---
 Cargo.toml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Cargo.toml b/Cargo.toml
index d03936f..e07a473 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,8 @@ include = [
   "examples/**/*"
 ]
 
+[workspace.dependencies]
+
 [workspace.lints.rust]
 missing_docs = "warn"
 rust_2018_idioms = "warn"

From 126eb3d4dc4f59bcbee11d9d55545f01f75fb734 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 09:48:35 -0500
Subject: [PATCH 158/302] chore: Encourage a single test binary

---
 Cargo.lock                   | 49 ++++++++++++++++++++++++++++++++++++
 Cargo.toml                   |  2 ++
 tests/testsuite/delete_me.rs |  0
 tests/testsuite/main.rs      |  1 +
 4 files changed, 52 insertions(+)
 create mode 100644 tests/testsuite/delete_me.rs
 create mode 100644 tests/testsuite/main.rs

diff --git a/Cargo.lock b/Cargo.lock
index 49c1f2d..e50f286 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5,3 +5,52 @@ version = 3
 [[package]]
 name = "PROJECT"
 version = "0.0.1"
+dependencies = [
+ "automod",
+]
+
+[[package]]
+name = "automod"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edf3ee19dbc0a46d740f6f0926bde8c50f02bdbc7b536842da28f6ac56513a8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
diff --git a/Cargo.toml b/Cargo.toml
index e07a473..d5e3b01 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,6 +17,7 @@ include = [
 ]
 
 [workspace.dependencies]
+automod = "1.0.14"
 
 [workspace.lints.rust]
 missing_docs = "warn"
@@ -129,6 +130,7 @@ default = []
 [dependencies]
 
 [dev-dependencies]
+automod.workspace = true
 
 [lints]
 workspace = true
diff --git a/tests/testsuite/delete_me.rs b/tests/testsuite/delete_me.rs
new file mode 100644
index 0000000..e69de29
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
new file mode 100644
index 0000000..4441374
--- /dev/null
+++ b/tests/testsuite/main.rs
@@ -0,0 +1 @@
+automod::dir!("tests/testsuite");

From c8b190be3a7397d63ffb175f8387ef98e7896b5a Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 09:50:22 -0500
Subject: [PATCH 159/302] chore(ci): Use latest SARIF

Now that we run clippy on stable, we can do this
---
 .github/workflows/ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 12d398c..52ce7f2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -128,9 +128,9 @@ jobs:
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools
-      run: cargo install clippy-sarif --version 0.3.4 --locked  # Held back due to msrv
+      run: cargo install clippy-sarif --locked
     - name: Install SARIF tools
-      run: cargo install sarif-fmt --version 0.3.4 --locked # Held back due to msrv
+      run: cargo install sarif-fmt --locked
     - name: Check
       run: >
         cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated

From 9b1b56620156971664aaf0f7a693bf3bc72ca0cb Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 09:51:51 -0500
Subject: [PATCH 160/302] chore(ci): Fix all rust-version-specific checks to
 stable

---
 .github/workflows/ci.yml | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 52ce7f2..42c6be7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -91,7 +91,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: stable
+        toolchain: "1.76"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -106,9 +106,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        # Not MSRV because its harder to jump between versions and people are
-        # more likely to have stable
-        toolchain: stable
+        toolchain: "1.76"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting

From 92d486c4b03efa984a9d03aa7279a1febe84d816 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 09:53:02 -0500
Subject: [PATCH 161/302] chore(ci): Speed up lockfile check

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 42c6be7..7a455a4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -81,7 +81,7 @@ jobs:
         toolchain: stable
     - uses: Swatinem/rust-cache@v2
     - name: "Is lockfile updated?"
-      run: cargo fetch --locked
+      run: cargo update --workspace --locked
   docs:
     name: Docs
     runs-on: ubuntu-latest

From 9258d9af7b87bc0394ef09be7e65bf6152d99f4b Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 09:57:23 -0500
Subject: [PATCH 162/302] chore(ci): More exhaustively check features

---
 .github/workflows/ci.yml        | 15 ++++-----------
 .github/workflows/rust-next.yml | 18 ++++++------------
 2 files changed, 10 insertions(+), 23 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7a455a4..134c317 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -44,14 +44,11 @@ jobs:
       with:
         toolchain: ${{ matrix.rust }}
     - uses: Swatinem/rust-cache@v2
+    - uses: taiki-e/install-action@cargo-hack
     - name: Build
       run: cargo test --workspace --no-run
-    - name: Default features
-      run: cargo test --workspace
-    - name: All features
-      run: cargo test --workspace --all-features
-    - name: No-default features
-      run: cargo test --workspace --no-default-features
+    - name: Test
+      run: cargo hack test --feature-powerset --workspace
   msrv:
     name: "Check MSRV"
     runs-on: ubuntu-latest
@@ -65,11 +62,7 @@ jobs:
     - uses: Swatinem/rust-cache@v2
     - uses: taiki-e/install-action@cargo-hack
     - name: Default features
-      run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets
-    - name: All features
-      run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets --all-features
-    - name: No-default features
-      run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets --no-default-features
+      run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets
   lockfile:
     runs-on: ubuntu-latest
     steps:
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index 49e5d8c..e673b65 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -36,14 +36,11 @@ jobs:
       with:
         toolchain: ${{ matrix.rust }}
     - uses: Swatinem/rust-cache@v2
+    - uses: taiki-e/install-action@cargo-hack
     - name: Build
       run: cargo test --workspace --no-run
-    - name: Default features
-      run: cargo test --workspace
-    - name: All features
-      run: cargo test --workspace --all-features
-    - name: No-default features
-      run: cargo test --workspace --no-default-features
+    - name: Test
+      run: cargo hack test --feature-powerset --workspace
   latest:
     name: "Check latest dependencies"
     runs-on: ubuntu-latest
@@ -55,13 +52,10 @@ jobs:
       with:
         toolchain: stable
     - uses: Swatinem/rust-cache@v2
+    - uses: taiki-e/install-action@cargo-hack
     - name: Update dependencues
       run: cargo update
     - name: Build
       run: cargo test --workspace --no-run
-    - name: Default features
-      run: cargo test --workspace
-    - name: All features
-      run: cargo test --workspace --all-features
-    - name: No-default features
-      run: cargo test --workspace --no-default-features
+    - name: Test
+      run: cargo hack test --feature-powerset --workspace

From 2714cca7c31a9c73716e88a93693c119c527d7f1 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 09:58:31 -0500
Subject: [PATCH 163/302] chore(ci): Don't check for unused crates

---
 Cargo.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index d5e3b01..4097fef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,7 +25,6 @@ rust_2018_idioms = "warn"
 unreachable_pub = "warn"
 unsafe_op_in_unsafe_fn = "warn"
 unsafe_code = "warn"
-unused_crate_dependencies = "warn"
 unused_lifetimes = "warn"
 unused_macro_rules = "warn"
 unused_qualifications = "warn"

From 314eef7f5fb7e415e8cd92887e5e878e9bfa929b Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 10:38:57 -0500
Subject: [PATCH 164/302] chore: Dont check must_use

See https://github.com/rust-lang/rust-clippy/issues/8339
---
 Cargo.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index 4097fef..666e6e3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -81,7 +81,6 @@ rc_mutex = "warn"
 redundant_feature_names = "warn"
 ref_option_ref = "warn"
 rest_pat_in_fully_bound_structs = "warn"
-return_self_not_must_use = "warn"
 same_functions_in_if_condition = "warn"
 self_named_module_files = "warn"
 semicolon_if_nothing_returned = "warn"

From 6a9d2bf50fa78d8f277b77be836b48fad8c7c764 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 12:45:57 -0500
Subject: [PATCH 165/302] chore: Don't warn on unsafe

This works well when a package is a safe abstraction but to universally
apply in a template to all members of a workspace doesn't make sense.
---
 Cargo.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index 666e6e3..50bfea5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,7 +24,6 @@ missing_docs = "warn"
 rust_2018_idioms = "warn"
 unreachable_pub = "warn"
 unsafe_op_in_unsafe_fn = "warn"
-unsafe_code = "warn"
 unused_lifetimes = "warn"
 unused_macro_rules = "warn"
 unused_qualifications = "warn"

From 8d4b1b6c8daf3c32828bf92725811cf433917081 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 13:24:28 -0500
Subject: [PATCH 166/302] chore: Remove clippy::tests_outside_test_module

See https://github.com/rust-lang/rust-clippy/issues/11024
---
 Cargo.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index 50bfea5..ed593eb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -89,7 +89,6 @@ string_add = "warn"
 string_add_assign = "warn"
 string_lit_as_bytes = "warn"
 string_to_string = "warn"
-tests_outside_test_module = "warn"
 todo = "warn"
 trait_duplication_in_bounds = "warn"
 unwrap_used = "warn"

From 99e034bbbbae7d60bb68d68c6d0db8338a97b030 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 28 Mar 2024 15:10:58 -0500
Subject: [PATCH 167/302] chore: Move print lints to lib.rs

While there is a config for ignoring these in tests, it doesn't help
with examples.
---
 Cargo.toml | 2 --
 src/lib.rs | 2 ++
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index ed593eb..faf1e63 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -73,8 +73,6 @@ needless_continue = "warn"
 needless_for_each = "warn"
 negative_feature_names = "warn"
 path_buf_push_overwrite = "warn"
-print_stderr = "warn"
-print_stdout = "warn"
 ptr_as_ptr = "warn"
 rc_mutex = "warn"
 redundant_feature_names = "warn"
diff --git a/src/lib.rs b/src/lib.rs
index 8ce46b5..2eabbd0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,6 @@
 //! > DESCRIPTION
 
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![warn(clippy::print_stderr)]
+#![warn(clippy::print_stdout)]
 #![allow(non_snake_case)] // TODO: Delete me

From a516bda4adb0f367da527488697ea308fbe58b38 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 1 Apr 2024 08:58:36 -0500
Subject: [PATCH 168/302] chore: Drop workspace.dependencies

Without automated checks, this will make it harder to track breaking
changes.
---
 Cargo.toml | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index faf1e63..983e090 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,9 +16,6 @@ include = [
   "examples/**/*"
 ]
 
-[workspace.dependencies]
-automod = "1.0.14"
-
 [workspace.lints.rust]
 missing_docs = "warn"
 rust_2018_idioms = "warn"
@@ -124,7 +121,7 @@ default = []
 [dependencies]
 
 [dev-dependencies]
-automod.workspace = true
+automod = "1.0.14"
 
 [lints]
 workspace = true

From ebc70d00f9259146592b7987bfcb8a0cb6c16661 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 1 Apr 2024 09:11:24 -0500
Subject: [PATCH 169/302] chore: Only check missing_docs in lib

This also fires in examples and other places.

While docs in examples would be nice,
it isn't universally applicable and `allow`s would undermine the
examples.
---
 Cargo.toml | 1 -
 src/lib.rs | 1 +
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index 983e090..715131b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,6 @@ include = [
 ]
 
 [workspace.lints.rust]
-missing_docs = "warn"
 rust_2018_idioms = "warn"
 unreachable_pub = "warn"
 unsafe_op_in_unsafe_fn = "warn"
diff --git a/src/lib.rs b/src/lib.rs
index 2eabbd0..39877b7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
 //! > DESCRIPTION
 
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![warn(missing_docs)]
 #![warn(clippy::print_stderr)]
 #![warn(clippy::print_stdout)]
 #![allow(non_snake_case)] // TODO: Delete me

From 3278d49444c33ece610de3fb5547bd885124dfe7 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 1 Apr 2024 10:35:16 -0500
Subject: [PATCH 170/302] chore: Allow print in tests

---
 .clippy.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.clippy.toml b/.clippy.toml
index 293c14f..027eef4 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -1,4 +1,5 @@
 warn-on-all-wildcard-imports = true
+allow-print-in-tests = true
 allow-expect-in-tests = true
 allow-unwrap-in-tests = true
 allow-dbg-in-tests = true

From d634de649f30d5a4deade46333606bc63897d05e Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 1 Apr 2024 11:36:58 -0500
Subject: [PATCH 171/302] chore(ci): Ensure CI job always runs

---
 .github/workflows/ci.yml | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 134c317..95b13b4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,11 +23,13 @@ jobs:
     permissions:
       contents: none
     name: CI
-    needs: [test, msrv, docs, rustfmt, clippy]
+    needs: [test, msrv, lockfile, docs, rustfmt, clippy]
     runs-on: ubuntu-latest
+    if: "always()"
     steps:
-      - name: Done
-        run: exit 0
+      - name: Failed
+        run: exit 1
+        if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')"
   test:
     name: Test
     strategy:

From 2570b58a0feaf355dede9080a9f4c98f8ba5580b Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 1 Apr 2024 12:31:49 -0500
Subject: [PATCH 172/302] chore(ci): Skip branch protections

---
 .github/settings.yml | 26 +++++++++++++++-----------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/.github/settings.yml b/.github/settings.yml
index 7d5e4fc..08983ae 100644
--- a/.github/settings.yml
+++ b/.github/settings.yml
@@ -42,14 +42,18 @@ labels:
     color: '#c2e0c6'
     description: "Help wanted!"
 
-branches:
-  - name: main
-    protection:
-      required_pull_request_reviews: null
-      required_conversation_resolution: true
-      required_status_checks:
-        # Required. Require branches to be up to date before merging.
-        strict: false
-        contexts: ["CI", "Lint Commits", "Spell Check with Typos"]
-      enforce_admins: false
-      restrictions: null
+# This serves more as documentation.
+# Branch protection API was replaced by rulesets but settings isn't updated.
+# See https://github.com/repository-settings/app/issues/825
+#
+# branches:
+#   - name: main
+#     protection:
+#       required_pull_request_reviews: null
+#       required_conversation_resolution: true
+#       required_status_checks:
+#         # Required. Require branches to be up to date before merging.
+#         strict: false
+#         contexts: ["CI", "Lint Commits", "Spell Check with Typos"]
+#       enforce_admins: false
+#       restrictions: null

From afd275590c5568e8f7ca60abc1f33b20e3679c03 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 1 Apr 2024 12:36:16 -0500
Subject: [PATCH 173/302] chore(ci): Don't block on Lint Commits

---
 .github/settings.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/settings.yml b/.github/settings.yml
index 08983ae..457eed6 100644
--- a/.github/settings.yml
+++ b/.github/settings.yml
@@ -54,6 +54,6 @@ labels:
 #       required_status_checks:
 #         # Required. Require branches to be up to date before merging.
 #         strict: false
-#         contexts: ["CI", "Lint Commits", "Spell Check with Typos"]
+#         contexts: ["CI", "Spell Check with Typos"]
 #       enforce_admins: false
 #       restrictions: null

From 14225df351a4510a6fad72e716b29173347aac84 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 16 Apr 2024 21:46:56 -0500
Subject: [PATCH 174/302] chore(ci): Auto-merge linter version updates

---
 .github/renovate.json5 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 373fc0e..62ca46b 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -72,6 +72,7 @@
       schedule: [
         '* * * * *',
       ],
+      automerge: true,
     },
     // Goals:
     // - Keep version reqs low, ignoring compatible normal/build dependencies

From be30b1bba034344c1a7c526b2b1898a8767471c5 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 26 Apr 2024 09:20:18 -0500
Subject: [PATCH 175/302] chore(ci): Try again with not auto-updating MSRV

The overhead for MSRV bumping is a lot lower and its annoying merging
all of the PRs (and I don't want these auto-merged)
---
 .github/renovate.json5 | 33 ---------------------------------
 1 file changed, 33 deletions(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 62ca46b..c184420 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -7,24 +7,6 @@
   configMigration: true,
   dependencyDashboard: true,
   customManagers: [
-    {
-      customType: 'regex',
-      fileMatch: [
-        '^rust-toolchain\\.toml$',
-        'Cargo.toml$',
-        'clippy.toml$',
-        '\\.clippy.toml$',
-        '^\\.github/workflows/ci.yml$',
-        '^\\.github/workflows/rust-next.yml$',
-      ],
-      matchStrings: [
-        'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)',
-        '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV',
-      ],
-      depNameTemplate: 'MSRV',
-      packageNameTemplate: 'rust-lang/rust',
-      datasourceTemplate: 'github-releases',
-    },
     {
       customType: 'regex',
       fileMatch: [
@@ -45,21 +27,6 @@
     },
   ],
   packageRules: [
-    {
-      commitMessageTopic: 'MSRV',
-      matchManagers: [
-        'custom.regex',
-      ],
-      matchPackageNames: [
-        'MSRV',
-      ],
-      minimumReleaseAge: '336 days',  // 8 releases * 6 weeks per release * 7 days per week
-      internalChecksFilter: 'strict',
-      extractVersion: '^(?<version>\\d+\\.\\d+)',  // Drop the patch version
-      schedule: [
-        '* * * * *',
-      ],
-    },
     {
       commitMessageTopic: 'Rust Stable',
       matchManagers: [

From a01f25da96e8bd3e216fbc19ac9883ab3bf969ce Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 26 Apr 2024 09:23:28 -0500
Subject: [PATCH 176/302] chore(ci): Reduce noisy lints

Want to add this back in later but this is slowing down migration of my
repos.
---
 src/lib.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/lib.rs b/src/lib.rs
index 39877b7..2eabbd0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,6 @@
 //! > DESCRIPTION
 
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
-#![warn(missing_docs)]
 #![warn(clippy::print_stderr)]
 #![warn(clippy::print_stdout)]
 #![allow(non_snake_case)] // TODO: Delete me

From 82cf9a62b027c10c6fafdcaaee24c4e92d7da61d Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 26 Apr 2024 09:35:55 -0500
Subject: [PATCH 177/302] chore(ci): Reduce noisy lints

---
 Cargo.toml | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 715131b..898251e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,9 +35,6 @@ debug_assert_with_mut_call = "warn"
 doc_markdown = "warn"
 empty_enum = "warn"
 enum_glob_use = "warn"
-exhaustive_enums = "warn"
-exhaustive_structs = "warn"
-exit = "warn"
 expl_impl_clone_on_copy = "warn"
 explicit_deref_methods = "warn"
 explicit_into_iter_loop = "warn"
@@ -85,7 +82,6 @@ string_lit_as_bytes = "warn"
 string_to_string = "warn"
 todo = "warn"
 trait_duplication_in_bounds = "warn"
-unwrap_used = "warn"
 verbose_file_reads = "warn"
 wildcard_imports = "warn"
 zero_sized_map_values = "warn"

From 181a2cf5e673d0f6f42133a5b30ccafd86b0106d Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 26 Apr 2024 11:36:19 -0500
Subject: [PATCH 178/302] chore(ci): Allow prelude wildcard imports

---
 .clippy.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.clippy.toml b/.clippy.toml
index 027eef4..1d4c5dc 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -1,4 +1,3 @@
-warn-on-all-wildcard-imports = true
 allow-print-in-tests = true
 allow-expect-in-tests = true
 allow-unwrap-in-tests = true

From 51de731521efb05c5503e05c33036d8fa439bc5a Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 26 Apr 2024 15:59:46 -0500
Subject: [PATCH 179/302] chore(ci): Lint clippy::items_after_statements seems
 too strict

---
 Cargo.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index 898251e..5a90580 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,7 +51,6 @@ inconsistent_struct_constructor = "warn"
 inefficient_to_string = "warn"
 infinite_loop = "warn"
 invalid_upcast_comparisons = "warn"
-items_after_statements = "warn"
 large_digit_groups = "warn"
 large_stack_arrays = "warn"
 large_types_passed_by_value = "warn"

From d1840f0518b5ed6c4d2258e36de8f5fa0c166fbf Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 27 Apr 2024 11:38:55 -0600
Subject: [PATCH 180/302] feat: Add Debug to public types

---
 src/renderer/mod.rs | 2 +-
 src/snippet.rs      | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index ee63da9..845d293 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -24,7 +24,7 @@ use stylesheet::Stylesheet;
 pub const DEFAULT_TERM_WIDTH: usize = 140;
 
 /// A renderer for [`Message`]s
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct Renderer {
     anonymized_line_numbers: bool,
     term_width: usize,
diff --git a/src/snippet.rs b/src/snippet.rs
index e7d4bef..8e9a3a8 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -15,6 +15,7 @@ use std::ops::Range;
 /// Primary structure provided for formatting
 ///
 /// See [`Level::title`] to create a [`Message`]
+#[derive(Debug)]
 pub struct Message<'a> {
     pub(crate) level: Level,
     pub(crate) id: Option<&'a str>,
@@ -55,6 +56,7 @@ impl<'a> Message<'a> {
 ///
 /// One `Snippet` is meant to represent a single, continuous,
 /// slice of source code that you want to annotate.
+#[derive(Debug)]
 pub struct Snippet<'a> {
     pub(crate) origin: Option<&'a str>,
     pub(crate) line_start: usize,

From b816eb9d568aa2d08c8cf370c7609d0d3346d5e9 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 27 Apr 2024 11:41:16 -0600
Subject: [PATCH 181/302] chore: Warn when public types missing debug

---
 src/lib.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/lib.rs b/src/lib.rs
index a2e4231..bfd2dc6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
 #![deny(rust_2018_idioms)]
+#![warn(missing_debug_implementations)]
 
 //! A library for formatting of text or programming code snippets.
 //!

From 12109c20deaf1021cda7ebef4690ffd9c40c169b Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 27 Apr 2024 11:53:41 -0600
Subject: [PATCH 182/302] chore: Update CHANGELOG.md

---
 CHANGELOG.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85f967e..62e20e2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+### Added
+
+- All public types now implement `Debug` [#119](https://github.com/rust-lang/annotate-snippets-rs/pull/119)
+
 ## [0.11.1] - 2024-03-21
 
 ### Fixes

From c8d76d6d502841cf5014cb80c6b3581d1c68c23e Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 27 Apr 2024 11:55:28 -0600
Subject: [PATCH 183/302] chore: Release annotate-snippets version 0.11.2

---
 CHANGELOG.md | 5 ++++-
 Cargo.lock   | 2 +-
 Cargo.toml   | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 62e20e2..d379628 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+## [0.11.2] - 2024-04-27
+
 ### Added
 
 - All public types now implement `Debug` [#119](https://github.com/rust-lang/annotate-snippets-rs/pull/119)
@@ -130,7 +132,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Update the syntax to Rust 2018 idioms. (#4)
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...HEAD
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...HEAD
+[0.11.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...0.11.2
 [0.11.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...0.11.1
 [0.11.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...0.11.0
 [0.10.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...0.10.2
diff --git a/Cargo.lock b/Cargo.lock
index 479ee8c..db244d1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "annotate-snippets"
-version = "0.11.1"
+version = "0.11.2"
 dependencies = [
  "anstream",
  "anstyle",
diff --git a/Cargo.toml b/Cargo.toml
index a9a7c28..5a5d40b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "annotate-snippets"
-version = "0.11.1"
+version = "0.11.2"
 edition = "2021"
 rust-version = "1.73"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]

From a6f3a123f8b57be381b3f03520bd6ebf86da351e Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 1 May 2024 02:10:48 +0000
Subject: [PATCH 184/302] chore(deps): update rust crate serde to 1.0.199

---
 Cargo.lock | 8 ++++----
 Cargo.toml | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index db244d1..49e824e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -701,18 +701,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.197"
+version = "1.0.199"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.197"
+version = "1.0.199"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/Cargo.toml b/Cargo.toml
index 5a5d40b..d7ff69b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,7 +32,7 @@ anstream = "0.6.13"
 criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
-serde = { version = "1.0.197", features = ["derive"] }
+serde = { version = "1.0.199", features = ["derive"] }
 snapbox = { version = "0.5.9", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] }
 toml = "0.5.11"
 

From edf68fac18e2520df402eb96bc3681a59e74fe3f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 24 May 2024 09:37:04 -0500
Subject: [PATCH 185/302] chore: Remove unused feature

---
 Cargo.lock | 67 +-----------------------------------------------------
 Cargo.toml |  2 +-
 2 files changed, 2 insertions(+), 67 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 49e824e..2ee2a33 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -108,12 +108,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
-[[package]]
-name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
 [[package]]
 name = "bitflags"
 version = "2.4.1"
@@ -221,15 +215,6 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 
-[[package]]
-name = "content_inspector"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
-dependencies = [
- "memchr",
-]
-
 [[package]]
 name = "criterion"
 version = "0.5.1"
@@ -305,12 +290,6 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
 
-[[package]]
-name = "dunce"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
-
 [[package]]
 name = "either"
 version = "1.9.0"
@@ -348,24 +327,6 @@ dependencies = [
  "serde_json",
 ]
 
-[[package]]
-name = "fastrand"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
-
-[[package]]
-name = "filetime"
-version = "0.2.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "windows-sys 0.52.0",
-]
-
 [[package]]
 name = "glob"
 version = "0.3.1"
@@ -621,15 +582,6 @@ dependencies = [
  "crossbeam-utils",
 ]
 
-[[package]]
-name = "redox_syscall"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
-dependencies = [
- "bitflags 1.3.2",
-]
-
 [[package]]
 name = "regex"
 version = "1.10.2"
@@ -665,7 +617,7 @@ version = "0.38.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -745,10 +697,7 @@ dependencies = [
  "anstream",
  "anstyle",
  "anstyle-svg",
- "content_inspector",
- "dunce",
  "escargot",
- "filetime",
  "ignore",
  "libc",
  "libtest-mimic",
@@ -757,9 +706,7 @@ dependencies = [
  "serde_json",
  "similar",
  "snapbox-macros",
- "tempfile",
  "wait-timeout",
- "walkdir",
  "windows-sys 0.52.0",
 ]
 
@@ -789,18 +736,6 @@ dependencies = [
  "unicode-ident",
 ]
 
-[[package]]
-name = "tempfile"
-version = "3.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
-dependencies = [
- "cfg-if",
- "fastrand",
- "rustix",
- "windows-sys 0.52.0",
-]
-
 [[package]]
 name = "termcolor"
 version = "1.4.1"
diff --git a/Cargo.toml b/Cargo.toml
index d7ff69b..9b7d867 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,7 +33,7 @@ criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
 serde = { version = "1.0.199", features = ["derive"] }
-snapbox = { version = "0.5.9", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] }
+snapbox = { version = "0.5.9", features = ["diff", "harness", "term-svg", "cmd", "examples"] }
 toml = "0.5.11"
 
 [[bench]]

From 3d43314f355632f7718c73a9e7b5ab64afc7b6b9 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 24 May 2024 09:37:38 -0500
Subject: [PATCH 186/302] chore: Upgrade snapbox

---
 Cargo.lock | 8 ++++----
 Cargo.toml | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 2ee2a33..c0eb93d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -690,9 +690,9 @@ checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
 
 [[package]]
 name = "snapbox"
-version = "0.5.9"
+version = "0.5.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ac441e1ecf678f68423d47f376d53fabce1afba92c8f68e31508eb27df8562a"
+checksum = "f37d101fcafc8e73748fd8a1b7048f5979f93d372fd17027d7724c1643bc379b"
 dependencies = [
  "anstream",
  "anstyle",
@@ -712,9 +712,9 @@ dependencies = [
 
 [[package]]
 name = "snapbox-macros"
-version = "0.3.8"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1c4b838b05d15ab22754068cb73500b2f3b07bf09d310e15b27f88160f1de40"
+checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d"
 dependencies = [
  "anstream",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 9b7d867..548157f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,7 +33,7 @@ criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
 serde = { version = "1.0.199", features = ["derive"] }
-snapbox = { version = "0.5.9", features = ["diff", "harness", "term-svg", "cmd", "examples"] }
+snapbox = { version = "0.5.14", features = ["diff", "harness", "term-svg", "cmd", "examples"] }
 toml = "0.5.11"
 
 [[bench]]

From 123a2228588e1c9ff5afe0239bb8920ce0b52887 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 24 May 2024 09:37:52 -0500
Subject: [PATCH 187/302] refactor: Resolve snapbox deprecations

---
 tests/examples.rs      |  2 +-
 tests/fixtures/main.rs |  1 -
 tests/formatter.rs     | 32 ++++++++++++++++----------------
 3 files changed, 17 insertions(+), 18 deletions(-)

diff --git a/tests/examples.rs b/tests/examples.rs
index 6025f5e..c866548 100644
--- a/tests/examples.rs
+++ b/tests/examples.rs
@@ -33,5 +33,5 @@ fn assert_example(target: &str, expected: snapbox::Data) {
         .env("CLICOLOR_FORCE", "1")
         .assert()
         .success()
-        .stdout_eq(expected);
+        .stdout_eq_(expected.raw());
 }
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index 841b363..78fe961 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -10,7 +10,6 @@ fn main() {
     #[cfg(not(windows))]
     snapbox::harness::Harness::new("tests/fixtures/", setup, test)
         .select(["*/*.toml"])
-        .action_env("SNAPSHOTS")
         .test();
 }
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index ebe03ff..f8793c1 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,6 +1,6 @@
 use annotate_snippets::{Level, Renderer, Snippet};
 
-use snapbox::{assert_eq, str};
+use snapbox::{assert_data_eq, str};
 
 #[test]
 fn test_i_29() {
@@ -20,7 +20,7 @@ error: oops
     .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(snippets).to_string());
+    assert_data_eq!(renderer.render(snippets).to_string(), expected);
 }
 
 #[test]
@@ -41,7 +41,7 @@ error
     .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(snippets).to_string());
+    assert_data_eq!(renderer.render(snippets).to_string(), expected);
 }
 
 #[test]
@@ -64,7 +64,7 @@ error
     .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(snippets).to_string());
+    assert_data_eq!(renderer.render(snippets).to_string(), expected);
 }
 
 #[test]
@@ -88,7 +88,7 @@ error
     .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(snippets).to_string());
+    assert_data_eq!(renderer.render(snippets).to_string(), expected);
 }
 
 #[test]
@@ -109,7 +109,7 @@ error
     .indent(false);
 
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(snippets).to_string());
+    assert_data_eq!(renderer.render(snippets).to_string(), expected);
 }
 
 #[test]
@@ -118,7 +118,7 @@ fn test_format_title() {
 
     let expected = str![r#"error[E0001]: This is a title"#];
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }
 
 #[test]
@@ -136,7 +136,7 @@ error
      |"#]]
     .indent(false);
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }
 
 #[test]
@@ -159,7 +159,7 @@ error
      |"#]]
     .indent(false);
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }
 
 #[test]
@@ -183,7 +183,7 @@ error
      |"#]]
     .indent(false);
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }
 
 #[test]
@@ -196,7 +196,7 @@ error
  = error: This __is__ a title"#]]
     .indent(false);
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }
 
 #[test]
@@ -227,7 +227,7 @@ error
    |"#]]
     .indent(false);
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }
 
 #[test]
@@ -246,7 +246,7 @@ error
   |"#]]
     .indent(false);
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }
 
 #[test]
@@ -267,7 +267,7 @@ error
   |"#]]
     .indent(false);
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }
 
 #[test]
@@ -282,7 +282,7 @@ error
  |"#]]
     .indent(false);
     let renderer = Renderer::plain();
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }
 
 #[test]
@@ -301,5 +301,5 @@ LL | abc
    |"#]]
     .indent(false);
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_eq(expected, renderer.render(input).to_string());
+    assert_data_eq!(renderer.render(input).to_string(), expected);
 }

From eb5e8580e1eeac82eacf042c42ce04942444fad1 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 24 May 2024 09:39:44 -0500
Subject: [PATCH 188/302] refactor: Migrate from snapbox::harness to tryfn

---
 Cargo.lock             | 29 ++++++++++++++++++++++++++---
 Cargo.toml             |  3 ++-
 tests/fixtures/main.rs |  8 ++++----
 3 files changed, 32 insertions(+), 8 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index c0eb93d..333030f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -27,8 +27,9 @@ dependencies = [
  "difference",
  "glob",
  "serde",
- "snapbox",
+ "snapbox 0.5.14",
  "toml",
+ "tryfn",
  "unicode-width",
 ]
 
@@ -698,9 +699,7 @@ dependencies = [
  "anstyle",
  "anstyle-svg",
  "escargot",
- "ignore",
  "libc",
- "libtest-mimic",
  "normalize-line-endings",
  "os_pipe",
  "serde_json",
@@ -710,6 +709,19 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "snapbox"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28bef451bfd0a7a395fb3979e130422658926bb2e0d9a168e72aca4c9b5c5738"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "normalize-line-endings",
+ "similar",
+ "snapbox-macros",
+]
+
 [[package]]
 name = "snapbox-macros"
 version = "0.3.9"
@@ -773,6 +785,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "tryfn"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "493e1390312bb94363f762687be32a1bd01c3333dfad25a5a7fffab1edc64839"
+dependencies = [
+ "ignore",
+ "libtest-mimic",
+ "snapbox 0.6.0",
+]
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.12"
diff --git a/Cargo.toml b/Cargo.toml
index 548157f..b49d940 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,8 +33,9 @@ criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
 serde = { version = "1.0.199", features = ["derive"] }
-snapbox = { version = "0.5.14", features = ["diff", "harness", "term-svg", "cmd", "examples"] }
+snapbox = { version = "0.5.14", features = ["diff", "term-svg", "cmd", "examples"] }
 toml = "0.5.11"
+tryfn = "0.2.1"
 
 [[bench]]
 name = "simple"
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index 78fe961..c0e351c 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -8,15 +8,15 @@ use std::error::Error;
 
 fn main() {
     #[cfg(not(windows))]
-    snapbox::harness::Harness::new("tests/fixtures/", setup, test)
+    tryfn::Harness::new("tests/fixtures/", setup, test)
         .select(["*/*.toml"])
         .test();
 }
 
-fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case {
+fn setup(input_path: std::path::PathBuf) -> tryfn::Case {
     let name = input_path.file_name().unwrap().to_str().unwrap().to_owned();
-    let expected = input_path.with_extension("svg");
-    snapbox::harness::Case {
+    let expected = tryfn::Data::read_from(&input_path.with_extension("svg"), None);
+    tryfn::Case {
         name,
         fixture: input_path,
         expected,

From 7624aca2fbc368ec6a91be9ed82f48193d25946b Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 24 May 2024 09:42:23 -0500
Subject: [PATCH 189/302] chore: Upgrade to snapbox 0.6

---
 Cargo.lock         | 21 ++++-------------
 Cargo.toml         |  2 +-
 tests/formatter.rs | 56 +++++++++++++++++++++++-----------------------
 3 files changed, 33 insertions(+), 46 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 333030f..074ab74 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -27,7 +27,7 @@ dependencies = [
  "difference",
  "glob",
  "serde",
- "snapbox 0.5.14",
+ "snapbox",
  "toml",
  "tryfn",
  "unicode-width",
@@ -691,9 +691,9 @@ checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
 
 [[package]]
 name = "snapbox"
-version = "0.5.14"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f37d101fcafc8e73748fd8a1b7048f5979f93d372fd17027d7724c1643bc379b"
+checksum = "28bef451bfd0a7a395fb3979e130422658926bb2e0d9a168e72aca4c9b5c5738"
 dependencies = [
  "anstream",
  "anstyle",
@@ -709,19 +709,6 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
-[[package]]
-name = "snapbox"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28bef451bfd0a7a395fb3979e130422658926bb2e0d9a168e72aca4c9b5c5738"
-dependencies = [
- "anstream",
- "anstyle",
- "normalize-line-endings",
- "similar",
- "snapbox-macros",
-]
-
 [[package]]
 name = "snapbox-macros"
 version = "0.3.9"
@@ -793,7 +780,7 @@ checksum = "493e1390312bb94363f762687be32a1bd01c3333dfad25a5a7fffab1edc64839"
 dependencies = [
  "ignore",
  "libtest-mimic",
- "snapbox 0.6.0",
+ "snapbox",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index b49d940..72bf895 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,7 +33,7 @@ criterion = "0.5.1"
 difference = "2.0.0"
 glob = "0.3.1"
 serde = { version = "1.0.199", features = ["derive"] }
-snapbox = { version = "0.5.14", features = ["diff", "term-svg", "cmd", "examples"] }
+snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] }
 toml = "0.5.11"
 tryfn = "0.2.1"
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index f8793c1..5b746e1 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -16,8 +16,8 @@ error: oops
   |
 2 | Second oops line
   |        ^^^^ oops
-  |"#]]
-    .indent(false);
+  |
+"#]];
 
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(snippets).to_string(), expected);
@@ -37,8 +37,8 @@ error
   |
 1 | こんにちは、世界
   |             ^^^^ world
-  |"#]]
-    .indent(false);
+  |
+"#]];
 
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(snippets).to_string(), expected);
@@ -60,8 +60,8 @@ error
   |  _____^
 2 | | ございます
   | |______^ Good morning
-  |"#]]
-    .indent(false);
+  |
+"#]];
 
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(snippets).to_string(), expected);
@@ -84,8 +84,8 @@ error
   | ^^^^^^ Sushi1
 2 | 食べたい🍣
   |     ---- note: Sushi2
-  |"#]]
-    .indent(false);
+  |
+"#]];
 
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(snippets).to_string(), expected);
@@ -105,8 +105,8 @@ error
   |
 1 | こんにちは、新しいWorld!
   |             ^^^^^^^^^^^ New world
-  |"#]]
-    .indent(false);
+  |
+"#]];
 
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(snippets).to_string(), expected);
@@ -133,8 +133,8 @@ error
      |
 5402 | This is line 1
 5403 | This is line 2
-     |"#]]
-    .indent(false);
+     |
+"#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -156,8 +156,8 @@ error
     ::: file2.rs
      |
    2 | This is slice 2
-     |"#]]
-    .indent(false);
+     |
+"#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -180,8 +180,8 @@ error
 5402 | This is line 1
 5403 | This is line 2
      |        -- info: Test annotation
-     |"#]]
-    .indent(false);
+     |
+"#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -193,8 +193,8 @@ fn test_format_footer_title() {
         .footer(Level::Error.title("This __is__ a title"));
     let expected = str![[r#"
 error
- = error: This __is__ a title"#]]
-    .indent(false);
+ = error: This __is__ a title
+"#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -224,8 +224,8 @@ error
    |
 56 | This is an example
 57 | of content lines
-   |"#]]
-    .indent(false);
+   |
+"#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -243,8 +243,8 @@ error
   |
 1 | tests
   | ----- help: Example string
-  |"#]]
-    .indent(false);
+  |
+"#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -264,8 +264,8 @@ error
 1 | tests
   | ----- help: Example string
   | ----- help: Second line
-  |"#]]
-    .indent(false);
+  |
+"#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -279,8 +279,8 @@ fn test_only_source() {
 error
 --> file.rs
  |
- |"#]]
-    .indent(false);
+ |
+"#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -298,8 +298,8 @@ LL | This is an example
 LL | of content lines
 LL | 
 LL | abc
-   |"#]]
-    .indent(false);
+   |
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(true);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }

From b8d0b9ef1247144c553fde60e4445d3b4d10d934 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 24 May 2024 09:42:47 -0500
Subject: [PATCH 190/302] refactor: Resolve deprecations

---
 tests/examples.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/examples.rs b/tests/examples.rs
index c866548..b657662 100644
--- a/tests/examples.rs
+++ b/tests/examples.rs
@@ -33,5 +33,5 @@ fn assert_example(target: &str, expected: snapbox::Data) {
         .env("CLICOLOR_FORCE", "1")
         .assert()
         .success()
-        .stdout_eq_(expected.raw());
+        .stdout_eq(expected.raw());
 }

From 78741e51bbbe6c83aaa98305623a8ffc6226493f Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 27 May 2024 21:45:38 -0500
Subject: [PATCH 191/302] chore: Remove lints that lead to bad code

---
 Cargo.toml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 5a90580..8090691 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,7 +58,6 @@ let_and_return = "allow"  # sometimes good to name what you are returning
 linkedlist = "warn"
 lossy_float_literal = "warn"
 macro_use_imports = "warn"
-match_wildcard_for_single_variants = "warn"
 mem_forget = "warn"
 mutex_integer = "warn"
 needless_continue = "warn"
@@ -73,7 +72,6 @@ rest_pat_in_fully_bound_structs = "warn"
 same_functions_in_if_condition = "warn"
 self_named_module_files = "warn"
 semicolon_if_nothing_returned = "warn"
-single_match_else = "warn"
 str_to_string = "warn"
 string_add = "warn"
 string_add_assign = "warn"

From 44916f6d2b8da4d778186083c31ff52a73187edf Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 29 May 2024 16:15:19 -0500
Subject: [PATCH 192/302] chore: Update deny config

---
 deny.toml | 176 +++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 136 insertions(+), 40 deletions(-)

diff --git a/deny.toml b/deny.toml
index 21fa937..b6ecbe9 100644
--- a/deny.toml
+++ b/deny.toml
@@ -4,32 +4,82 @@
 # * allow - No warning or error will be produced, though in some cases a note
 # will be
 
+# Root options
+
+# The graph table configures how the dependency graph is constructed and thus
+# which crates the checks are performed against
+[graph]
+# If 1 or more target triples (and optionally, target_features) are specified,
+# only the specified targets will be checked when running `cargo deny check`.
+# This means, if a particular package is only ever used as a target specific
+# dependency, such as, for example, the `nix` crate only being used via the
+# `target_family = "unix"` configuration, that only having windows targets in
+# this list would mean the nix crate, as well as any of its exclusive
+# dependencies not shared by any other crates, would be ignored, as the target
+# list here is effectively saying which targets you are building for.
+targets = [
+    # The triple can be any string, but only the target triples built in to
+    # rustc (as of 1.40) can be checked against actual config expressions
+    #"x86_64-unknown-linux-musl",
+    # You can also specify which target_features you promise are enabled for a
+    # particular target. target_features are currently not validated against
+    # the actual valid features supported by the target architecture.
+    #{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
+]
+# When creating the dependency graph used as the source of truth when checks are
+# executed, this field can be used to prune crates from the graph, removing them
+# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
+# is pruned from the graph, all of its dependencies will also be pruned unless
+# they are connected to another crate in the graph that hasn't been pruned,
+# so it should be used with care. The identifiers are [Package ID Specifications]
+# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
+#exclude = []
+# If true, metadata will be collected with `--all-features`. Note that this can't
+# be toggled off if true, if you want to conditionally enable `--all-features` it
+# is recommended to pass `--all-features` on the cmd line instead
+all-features = false
+# If true, metadata will be collected with `--no-default-features`. The same
+# caveat with `all-features` applies
+no-default-features = false
+# If set, these feature will be enabled when collecting metadata. If `--features`
+# is specified on the cmd line they will take precedence over this option.
+#features = []
+
+# The output table provides options for how/if diagnostics are outputted
+[output]
+# When outputting inclusion graphs in diagnostics that include features, this
+# option can be used to specify the depth at which feature edges will be added.
+# This option is included since the graphs can be quite large and the addition
+# of features from the crate(s) to all of the graph roots can be far too verbose.
+# This option can be overridden via `--feature-depth` on the cmd line
+feature-depth = 1
+
 # This section is considered when running `cargo deny check advisories`
 # More documentation for the advisories section can be found here:
 # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
 [advisories]
-# The lint level for security vulnerabilities
-vulnerability = "deny"
-# The lint level for unmaintained crates
-unmaintained = "warn"
-# The lint level for crates that have been yanked from their source registry
-yanked = "warn"
-# The lint level for crates with security notices. Note that as of
-# 2019-12-17 there are no security notice advisories in
-# https://github.com/rustsec/advisory-db
-notice = "warn"
+# The path where the advisory databases are cloned/fetched into
+#db-path = "$CARGO_HOME/advisory-dbs"
+# The url(s) of the advisory databases to use
+#db-urls = ["https://github.com/rustsec/advisory-db"]
 # A list of advisory IDs to ignore. Note that ignored advisories will still
 # output a note when they are encountered.
-#
-# e.g. "RUSTSEC-0000-0000",
 ignore = [
+    #"RUSTSEC-0000-0000",
+    #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
+    #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
+    #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
 ]
+# If this is true, then cargo deny will use the git executable to fetch advisory database.
+# If this is false, then it uses a built-in git library.
+# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
+# See Git Authentication for more information about setting up git authentication.
+#git-fetch-with-cli = true
 
 # This section is considered when running `cargo deny check licenses`
 # More documentation for the licenses section can be found here:
 # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
 [licenses]
-unlicensed = "deny"
 # List of explicitly allowed licenses
 # See https://spdx.org/licenses/ for list of possible licenses
 # [possible values: any SPDX 3.11 short identifier (+ optional exception)].
@@ -42,26 +92,8 @@ allow = [
     "Unicode-DFS-2016",
     "CC0-1.0",
     "ISC",
+    "OpenSSL",
 ]
-# List of explicitly disallowed licenses
-# See https://spdx.org/licenses/ for list of possible licenses
-# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
-deny = [
-]
-# Lint level for licenses considered copyleft
-copyleft = "deny"
-# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
-# * both - The license will be approved if it is both OSI-approved *AND* FSF
-# * either - The license will be approved if it is either OSI-approved *OR* FSF
-# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
-# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
-# * neither - This predicate is ignored and the default lint level is used
-allow-osi-fsf-free = "neither"
-# Lint level used when no other predicates are matched
-# 1. License isn't in the allow or deny lists
-# 2. License isn't copyleft
-# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
-default = "deny"
 # The confidence threshold for detecting a license from license text.
 # The higher the value, the more closely the license text must be to the
 # canonical license text of a valid SPDX license file.
@@ -72,7 +104,25 @@ confidence-threshold = 0.8
 exceptions = [
     # Each entry is the crate and version constraint, and its specific allow
     # list
-    #{ allow = ["Zlib"], name = "adler32", version = "*" },
+    #{ allow = ["Zlib"], crate = "adler32" },
+]
+
+# Some crates don't have (easily) machine readable licensing information,
+# adding a clarification entry for it allows you to manually specify the
+# licensing information
+[[licenses.clarify]]
+# The package spec the clarification applies to
+crate = "ring"
+# The SPDX expression for the license requirements of the crate
+expression = "MIT AND ISC AND OpenSSL"
+# One or more files in the crate's source used as the "source of truth" for
+# the license expression. If the contents match, the clarification will be used
+# when running the license check, otherwise the clarification will be ignored
+# and the crate will be checked normally, which may produce warnings or errors
+# depending on the rest of your configuration
+license-files = [
+# Each entry is a crate relative path, and the (opaque) hash of its contents
+{ path = "LICENSE", hash = 0xbd0eed23 }
 ]
 
 [licenses.private]
@@ -81,6 +131,12 @@ exceptions = [
 # To see how to mark a crate as unpublished (to the official registry),
 # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
 ignore = true
+# One or more private registries that you might publish crates to, if a crate
+# is only published to private registries, and ignore is true, the crate will
+# not have its license(s) checked
+registries = [
+    #"https://sekretz.com/registry
+]
 
 # This section is considered when running `cargo deny check bans`.
 # More documentation about the 'bans' section can be found here:
@@ -89,7 +145,7 @@ ignore = true
 # Lint level for when multiple versions of the same crate are detected
 multiple-versions = "warn"
 # Lint level for when a crate version requirement is `*`
-wildcards = "warn"
+wildcards = "allow"
 # The graph highlighting used when creating dotgraphs for crates
 # with multiple versions
 # * lowest-version - The path to the lowest versioned duplicate is highlighted
@@ -106,17 +162,53 @@ workspace-default-features = "allow"
 external-default-features = "allow"
 # List of crates that are allowed. Use with care!
 allow = [
-    #{ name = "ansi_term", version = "=0.11.0" },
+    #"ansi_term@0.11.0",
+    #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
 ]
 # List of crates to deny
 deny = [
-    # Each entry the name of a crate and a version range. If version is
-    # not specified, all versions will be matched.
-    #{ name = "ansi_term", version = "=0.11.0" },
-    #
+    #"ansi_term@0.11.0",
+    #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
     # Wrapper crates can optionally be specified to allow the crate when it
     # is a direct dependency of the otherwise banned crate
-    #{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
+    #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
+]
+
+# List of features to allow/deny
+# Each entry the name of a crate and a version range. If version is
+# not specified, all versions will be matched.
+#[[bans.features]]
+#crate = "reqwest"
+# Features to not allow
+#deny = ["json"]
+# Features to allow
+#allow = [
+#    "rustls",
+#    "__rustls",
+#    "__tls",
+#    "hyper-rustls",
+#    "rustls",
+#    "rustls-pemfile",
+#    "rustls-tls-webpki-roots",
+#    "tokio-rustls",
+#    "webpki-roots",
+#]
+# If true, the allowed features must exactly match the enabled feature set. If
+# this is set there is no point setting `deny`
+#exact = true
+
+# Certain crates/versions that will be skipped when doing duplicate detection.
+skip = [
+    #"ansi_term@0.11.0",
+    #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
+]
+# Similarly to `skip` allows you to skip certain crates during duplicate
+# detection. Unlike skip, it also includes the entire tree of transitive
+# dependencies starting at the specified crate, up to a certain depth, which is
+# by default infinite.
+skip-tree = [
+    #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
+    #{ crate = "ansi_term@0.11.0", depth = 20 },
 ]
 
 # This section is considered when running `cargo deny check sources`.
@@ -138,3 +230,7 @@ allow-git = []
 [sources.allow-org]
 # 1 or more github.com organizations to allow git sources for
 github = []
+# 1 or more gitlab.com organizations to allow git sources for
+gitlab = []
+# 1 or more bitbucket.org organizations to allow git sources for
+bitbucket = []

From dce69df2e44d87443dfd6955078bd912f53bddc8 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 1 Jun 2024 00:43:32 +0000
Subject: [PATCH 193/302] chore(deps): update compatible (dev)

---
 Cargo.lock | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 074ab74..70efddc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -35,15 +35,16 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.13"
+version = "0.6.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
 dependencies = [
  "anstyle",
  "anstyle-parse",
  "anstyle-query",
  "anstyle-wincon",
  "colorchoice",
+ "is_terminal_polyfill",
  "utf8parse",
 ]
 
@@ -401,6 +402,12 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
+
 [[package]]
 name = "itertools"
 version = "0.10.5"
@@ -654,18 +661,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.199"
+version = "1.0.203"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
+checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.199"
+version = "1.0.203"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
+checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -691,9 +698,9 @@ checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
 
 [[package]]
 name = "snapbox"
-version = "0.6.0"
+version = "0.6.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28bef451bfd0a7a395fb3979e130422658926bb2e0d9a168e72aca4c9b5c5738"
+checksum = "94204b12a4d3550420babdb4148c6639692e4e3e61060866929c5107f208aeb6"
 dependencies = [
  "anstream",
  "anstyle",

From ce6badcd188650dac4b3c97b69bde86a738917a0 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 3 Jun 2024 10:29:26 -0500
Subject: [PATCH 194/302] chore: Fix typo

---
 .github/workflows/rust-next.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index e673b65..ab49963 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -53,7 +53,7 @@ jobs:
         toolchain: stable
     - uses: Swatinem/rust-cache@v2
     - uses: taiki-e/install-action@cargo-hack
-    - name: Update dependencues
+    - name: Update dependencies
       run: cargo update
     - name: Build
       run: cargo test --workspace --no-run

From 1353a953a527b7ebc0b0a3f267fc47f56359e886 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 4 Jun 2024 15:33:16 -0500
Subject: [PATCH 195/302] chore: Encourage use of repository

---
 Cargo.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Cargo.toml b/Cargo.toml
index 8090691..c9695d7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -87,6 +87,7 @@ zero_sized_map_values = "warn"
 name = "PROJECT"
 version = "0.0.1"
 description = "DESCRIPTION"
+repository = "REPOSITORY"
 categories = []
 keywords = []
 license.workspace = true

From 7039c66c7f0a42b84136a2f166ce6446edbb9ce0 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 4 Jun 2024 15:33:50 -0500
Subject: [PATCH 196/302] chore: Encourage cloneable repositories

---
 Cargo.toml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index c9695d7..96cb234 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
 resolver = "2"
 
 [workspace.package]
+repository = "REPOSITORY"
 license = "MIT OR Apache-2.0"
 edition = "2021"
 rust-version = "1.65.0"  # MSRV
@@ -87,9 +88,9 @@ zero_sized_map_values = "warn"
 name = "PROJECT"
 version = "0.0.1"
 description = "DESCRIPTION"
-repository = "REPOSITORY"
 categories = []
 keywords = []
+repository.workspace = true
 license.workspace = true
 edition.workspace = true
 rust-version.workspace = true

From b5a6b53ecd4a377df293ea85fb695f1137573404 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 6 Jun 2024 13:59:28 -0500
Subject: [PATCH 197/302] chore: Update from epage/_rust template

---
 .clippy.toml                     |   4 +
 .github/renovate.json5           |  24 +++--
 .github/workflows/audit.yml      |   4 +
 .github/workflows/ci.yml         |  50 +++++----
 .github/workflows/committed.yml  |   4 +
 .github/workflows/pre-commit.yml |   6 +-
 .github/workflows/rust-next.yml  |  30 +++---
 .github/workflows/spelling.yml   |   4 +
 Cargo.toml                       |   3 +
 deny.toml                        | 176 ++++++++++++++++++++++++-------
 src/lib.rs                       |   8 +-
 tests/fixtures/main.rs           |   2 +-
 12 files changed, 222 insertions(+), 93 deletions(-)
 create mode 100644 .clippy.toml

diff --git a/.clippy.toml b/.clippy.toml
new file mode 100644
index 0000000..2e52a64
--- /dev/null
+++ b/.clippy.toml
@@ -0,0 +1,4 @@
+allow-print-in-tests = true
+allow-expect-in-tests = true
+allow-unwrap-in-tests = true
+allow-dbg-in-tests = true
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 87676d4..c184420 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -3,6 +3,7 @@
     'before 5am on the first day of the month',
   ],
   semanticCommits: 'enabled',
+  commitMessageLowerCase: 'never',
   configMigration: true,
   dependencyDashboard: true,
   customManagers: [
@@ -17,30 +18,35 @@
         '^\\.github/workflows/rust-next.yml$',
       ],
       matchStrings: [
-        'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)',
-        '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV',
+        'STABLE.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)',
+        '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?STABLE',
       ],
-      depNameTemplate: 'rust',
+      depNameTemplate: 'STABLE',
       packageNameTemplate: 'rust-lang/rust',
       datasourceTemplate: 'github-releases',
     },
   ],
   packageRules: [
     {
-      commitMessageTopic: 'MSRV',
+      commitMessageTopic: 'Rust Stable',
       matchManagers: [
         'custom.regex',
       ],
       matchPackageNames: [
-        'rust',
+        'STABLE',
       ],
-      minimumReleaseAge: '84 days',
-      internalChecksFilter: 'strict',
-      extractVersion: '^(?<version>\\d+\\.\\d+)',
+      extractVersion: '^(?<version>\\d+\\.\\d+)',  // Drop the patch version
       schedule: [
         '* * * * *',
       ],
+      automerge: true,
     },
+    // Goals:
+    // - Keep version reqs low, ignoring compatible normal/build dependencies
+    // - Take advantage of latest dev-dependencies
+    // - Rollup safe upgrades to reduce CI runner load
+    // - Help keep number of versions down by always using latest breaking change
+    // - Have lockfile and manifest in-sync
     {
       matchManagers: [
         'cargo',
@@ -66,6 +72,7 @@
       matchCurrentVersion: '>=1.0.0',
       matchUpdateTypes: [
         'minor',
+        'patch',
       ],
       enabled: false,
     },
@@ -93,6 +100,7 @@
       matchCurrentVersion: '>=1.0.0',
       matchUpdateTypes: [
         'minor',
+        'patch',
       ],
       automerge: true,
       groupName: 'compatible (dev)',
diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index 442e637..35b3da8 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -17,6 +17,10 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   security_audit:
     permissions:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f10a0db..a621652 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,21 +14,27 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   ci:
     permissions:
       contents: none
     name: CI
-    needs: [test, msrv, docs, rustfmt, clippy]
+    needs: [test, msrv, lockfile, docs, rustfmt, clippy]
     runs-on: ubuntu-latest
+    if: "always()"
     steps:
-      - name: Done
-        run: exit 0
+      - name: Failed
+        run: exit 1
+        if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')"
   test:
     name: Test
     strategy:
       matrix:
-        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
+        os: ["ubuntu-latest", "windows-latest", "macos-14"]
         rust: ["stable"]
     continue-on-error: ${{ matrix.rust != 'stable' }}
     runs-on: ${{ matrix.os }}
@@ -40,16 +46,13 @@ jobs:
       with:
         toolchain: ${{ matrix.rust }}
     - uses: Swatinem/rust-cache@v2
+    - uses: taiki-e/install-action@cargo-hack
     - name: Build
-      run: cargo test --no-run --workspace --all-features
-    - name: Default features
-      run: cargo test --workspace
-    - name: All features
-      run: cargo test --workspace --all-features
-    - name: No-default features
-      run: cargo test --workspace --no-default-features
+      run: cargo test --workspace --no-run
+    - name: Test
+      run: cargo hack test --feature-powerset --workspace
   msrv:
-    name: "Check MSRV: 1.73"  # MSRV
+    name: "Check MSRV"
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
@@ -57,14 +60,11 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.73"  # MSRV
+        toolchain: stable
     - uses: Swatinem/rust-cache@v2
+    - uses: taiki-e/install-action@cargo-hack
     - name: Default features
-      run: cargo check --workspace --all-targets
-    - name: All features
-      run: cargo check --workspace --all-targets --all-features
-    - name: No-default features
-      run: cargo check --workspace --all-targets --no-default-features
+      run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets
   lockfile:
     runs-on: ubuntu-latest
     steps:
@@ -76,7 +76,7 @@ jobs:
         toolchain: stable
     - uses: Swatinem/rust-cache@v2
     - name: "Is lockfile updated?"
-      run: cargo fetch --locked
+      run: cargo update --workspace --locked
   docs:
     name: Docs
     runs-on: ubuntu-latest
@@ -86,7 +86,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: stable
+        toolchain: "1.76"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -101,9 +101,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        # Not MSRV because its harder to jump between versions and people are
-        # more likely to have stable
-        toolchain: stable
+        toolchain: "1.76"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -119,13 +117,13 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.73"  # MSRV
+        toolchain: "1.76"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools
-      run: cargo install clippy-sarif --version 0.3.4 --locked  # Held back due to msrv
+      run: cargo install clippy-sarif --locked
     - name: Install SARIF tools
-      run: cargo install sarif-fmt --version 0.3.4 --locked # Held back due to msrv
+      run: cargo install sarif-fmt --locked
     - name: Check
       run: >
         cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated
diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml
index 0462558..e7a50fb 100644
--- a/.github/workflows/committed.yml
+++ b/.github/workflows/committed.yml
@@ -11,6 +11,10 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   committed:
     name: Lint Commits
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 6eb6ca1..3d04055 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -5,13 +5,17 @@ permissions: {} # none
 on:
   pull_request:
   push:
-    branches: [main]
+    branches: [master]
 
 env:
   RUST_BACKTRACE: 1
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   pre-commit:
     permissions:
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index d8c2d25..ab49963 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -12,12 +12,16 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   test:
     name: Test
     strategy:
       matrix:
-        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
+        os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"]
         rust: ["stable", "beta"]
         include:
         - os: ubuntu-latest
@@ -32,12 +36,11 @@ jobs:
       with:
         toolchain: ${{ matrix.rust }}
     - uses: Swatinem/rust-cache@v2
-    - name: Default features
-      run: cargo test --workspace
-    - name: All features
-      run: cargo test --workspace --all-features
-    - name: No-default features
-      run: cargo test --workspace --no-default-features
+    - uses: taiki-e/install-action@cargo-hack
+    - name: Build
+      run: cargo test --workspace --no-run
+    - name: Test
+      run: cargo hack test --feature-powerset --workspace
   latest:
     name: "Check latest dependencies"
     runs-on: ubuntu-latest
@@ -49,11 +52,10 @@ jobs:
       with:
         toolchain: stable
     - uses: Swatinem/rust-cache@v2
-    - name: Update dependencues
+    - uses: taiki-e/install-action@cargo-hack
+    - name: Update dependencies
       run: cargo update
-    - name: Default features
-      run: cargo test --workspace --all-targets
-    - name: All features
-      run: cargo test --workspace --all-targets --all-features
-    - name: No-default features
-      run: cargo test --workspace --all-targets --no-default-features
+    - name: Build
+      run: cargo test --workspace --no-run
+    - name: Test
+      run: cargo hack test --feature-powerset --workspace
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
index 12f7585..8e58d9e 100644
--- a/.github/workflows/spelling.yml
+++ b/.github/workflows/spelling.yml
@@ -10,6 +10,10 @@ env:
   CARGO_TERM_COLOR: always
   CLICOLOR: 1
 
+concurrency:
+  group: "${{ github.workflow }}-${{ github.ref }}"
+  cancel-in-progress: true
+
 jobs:
   spelling:
     name: Spell Check with Typos
diff --git a/Cargo.toml b/Cargo.toml
index 72bf895..a317c71 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,3 +48,6 @@ harness = false
 [features]
 default = []
 testing-colors = []
+
+[lints.rust]
+rust_2018_idioms = "warn"
diff --git a/deny.toml b/deny.toml
index 21fa937..b6ecbe9 100644
--- a/deny.toml
+++ b/deny.toml
@@ -4,32 +4,82 @@
 # * allow - No warning or error will be produced, though in some cases a note
 # will be
 
+# Root options
+
+# The graph table configures how the dependency graph is constructed and thus
+# which crates the checks are performed against
+[graph]
+# If 1 or more target triples (and optionally, target_features) are specified,
+# only the specified targets will be checked when running `cargo deny check`.
+# This means, if a particular package is only ever used as a target specific
+# dependency, such as, for example, the `nix` crate only being used via the
+# `target_family = "unix"` configuration, that only having windows targets in
+# this list would mean the nix crate, as well as any of its exclusive
+# dependencies not shared by any other crates, would be ignored, as the target
+# list here is effectively saying which targets you are building for.
+targets = [
+    # The triple can be any string, but only the target triples built in to
+    # rustc (as of 1.40) can be checked against actual config expressions
+    #"x86_64-unknown-linux-musl",
+    # You can also specify which target_features you promise are enabled for a
+    # particular target. target_features are currently not validated against
+    # the actual valid features supported by the target architecture.
+    #{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
+]
+# When creating the dependency graph used as the source of truth when checks are
+# executed, this field can be used to prune crates from the graph, removing them
+# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
+# is pruned from the graph, all of its dependencies will also be pruned unless
+# they are connected to another crate in the graph that hasn't been pruned,
+# so it should be used with care. The identifiers are [Package ID Specifications]
+# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
+#exclude = []
+# If true, metadata will be collected with `--all-features`. Note that this can't
+# be toggled off if true, if you want to conditionally enable `--all-features` it
+# is recommended to pass `--all-features` on the cmd line instead
+all-features = false
+# If true, metadata will be collected with `--no-default-features`. The same
+# caveat with `all-features` applies
+no-default-features = false
+# If set, these feature will be enabled when collecting metadata. If `--features`
+# is specified on the cmd line they will take precedence over this option.
+#features = []
+
+# The output table provides options for how/if diagnostics are outputted
+[output]
+# When outputting inclusion graphs in diagnostics that include features, this
+# option can be used to specify the depth at which feature edges will be added.
+# This option is included since the graphs can be quite large and the addition
+# of features from the crate(s) to all of the graph roots can be far too verbose.
+# This option can be overridden via `--feature-depth` on the cmd line
+feature-depth = 1
+
 # This section is considered when running `cargo deny check advisories`
 # More documentation for the advisories section can be found here:
 # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
 [advisories]
-# The lint level for security vulnerabilities
-vulnerability = "deny"
-# The lint level for unmaintained crates
-unmaintained = "warn"
-# The lint level for crates that have been yanked from their source registry
-yanked = "warn"
-# The lint level for crates with security notices. Note that as of
-# 2019-12-17 there are no security notice advisories in
-# https://github.com/rustsec/advisory-db
-notice = "warn"
+# The path where the advisory databases are cloned/fetched into
+#db-path = "$CARGO_HOME/advisory-dbs"
+# The url(s) of the advisory databases to use
+#db-urls = ["https://github.com/rustsec/advisory-db"]
 # A list of advisory IDs to ignore. Note that ignored advisories will still
 # output a note when they are encountered.
-#
-# e.g. "RUSTSEC-0000-0000",
 ignore = [
+    #"RUSTSEC-0000-0000",
+    #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
+    #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
+    #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
 ]
+# If this is true, then cargo deny will use the git executable to fetch advisory database.
+# If this is false, then it uses a built-in git library.
+# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
+# See Git Authentication for more information about setting up git authentication.
+#git-fetch-with-cli = true
 
 # This section is considered when running `cargo deny check licenses`
 # More documentation for the licenses section can be found here:
 # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
 [licenses]
-unlicensed = "deny"
 # List of explicitly allowed licenses
 # See https://spdx.org/licenses/ for list of possible licenses
 # [possible values: any SPDX 3.11 short identifier (+ optional exception)].
@@ -42,26 +92,8 @@ allow = [
     "Unicode-DFS-2016",
     "CC0-1.0",
     "ISC",
+    "OpenSSL",
 ]
-# List of explicitly disallowed licenses
-# See https://spdx.org/licenses/ for list of possible licenses
-# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
-deny = [
-]
-# Lint level for licenses considered copyleft
-copyleft = "deny"
-# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
-# * both - The license will be approved if it is both OSI-approved *AND* FSF
-# * either - The license will be approved if it is either OSI-approved *OR* FSF
-# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
-# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
-# * neither - This predicate is ignored and the default lint level is used
-allow-osi-fsf-free = "neither"
-# Lint level used when no other predicates are matched
-# 1. License isn't in the allow or deny lists
-# 2. License isn't copyleft
-# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
-default = "deny"
 # The confidence threshold for detecting a license from license text.
 # The higher the value, the more closely the license text must be to the
 # canonical license text of a valid SPDX license file.
@@ -72,7 +104,25 @@ confidence-threshold = 0.8
 exceptions = [
     # Each entry is the crate and version constraint, and its specific allow
     # list
-    #{ allow = ["Zlib"], name = "adler32", version = "*" },
+    #{ allow = ["Zlib"], crate = "adler32" },
+]
+
+# Some crates don't have (easily) machine readable licensing information,
+# adding a clarification entry for it allows you to manually specify the
+# licensing information
+[[licenses.clarify]]
+# The package spec the clarification applies to
+crate = "ring"
+# The SPDX expression for the license requirements of the crate
+expression = "MIT AND ISC AND OpenSSL"
+# One or more files in the crate's source used as the "source of truth" for
+# the license expression. If the contents match, the clarification will be used
+# when running the license check, otherwise the clarification will be ignored
+# and the crate will be checked normally, which may produce warnings or errors
+# depending on the rest of your configuration
+license-files = [
+# Each entry is a crate relative path, and the (opaque) hash of its contents
+{ path = "LICENSE", hash = 0xbd0eed23 }
 ]
 
 [licenses.private]
@@ -81,6 +131,12 @@ exceptions = [
 # To see how to mark a crate as unpublished (to the official registry),
 # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
 ignore = true
+# One or more private registries that you might publish crates to, if a crate
+# is only published to private registries, and ignore is true, the crate will
+# not have its license(s) checked
+registries = [
+    #"https://sekretz.com/registry
+]
 
 # This section is considered when running `cargo deny check bans`.
 # More documentation about the 'bans' section can be found here:
@@ -89,7 +145,7 @@ ignore = true
 # Lint level for when multiple versions of the same crate are detected
 multiple-versions = "warn"
 # Lint level for when a crate version requirement is `*`
-wildcards = "warn"
+wildcards = "allow"
 # The graph highlighting used when creating dotgraphs for crates
 # with multiple versions
 # * lowest-version - The path to the lowest versioned duplicate is highlighted
@@ -106,17 +162,53 @@ workspace-default-features = "allow"
 external-default-features = "allow"
 # List of crates that are allowed. Use with care!
 allow = [
-    #{ name = "ansi_term", version = "=0.11.0" },
+    #"ansi_term@0.11.0",
+    #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
 ]
 # List of crates to deny
 deny = [
-    # Each entry the name of a crate and a version range. If version is
-    # not specified, all versions will be matched.
-    #{ name = "ansi_term", version = "=0.11.0" },
-    #
+    #"ansi_term@0.11.0",
+    #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
     # Wrapper crates can optionally be specified to allow the crate when it
     # is a direct dependency of the otherwise banned crate
-    #{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
+    #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
+]
+
+# List of features to allow/deny
+# Each entry the name of a crate and a version range. If version is
+# not specified, all versions will be matched.
+#[[bans.features]]
+#crate = "reqwest"
+# Features to not allow
+#deny = ["json"]
+# Features to allow
+#allow = [
+#    "rustls",
+#    "__rustls",
+#    "__tls",
+#    "hyper-rustls",
+#    "rustls",
+#    "rustls-pemfile",
+#    "rustls-tls-webpki-roots",
+#    "tokio-rustls",
+#    "webpki-roots",
+#]
+# If true, the allowed features must exactly match the enabled feature set. If
+# this is set there is no point setting `deny`
+#exact = true
+
+# Certain crates/versions that will be skipped when doing duplicate detection.
+skip = [
+    #"ansi_term@0.11.0",
+    #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
+]
+# Similarly to `skip` allows you to skip certain crates during duplicate
+# detection. Unlike skip, it also includes the entire tree of transitive
+# dependencies starting at the specified crate, up to a certain depth, which is
+# by default infinite.
+skip-tree = [
+    #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
+    #{ crate = "ansi_term@0.11.0", depth = 20 },
 ]
 
 # This section is considered when running `cargo deny check sources`.
@@ -138,3 +230,7 @@ allow-git = []
 [sources.allow-org]
 # 1 or more github.com organizations to allow git sources for
 github = []
+# 1 or more gitlab.com organizations to allow git sources for
+gitlab = []
+# 1 or more bitbucket.org organizations to allow git sources for
+bitbucket = []
diff --git a/src/lib.rs b/src/lib.rs
index bfd2dc6..ed9b3f8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,3 @@
-#![deny(rust_2018_idioms)]
-#![warn(missing_debug_implementations)]
-
 //! A library for formatting of text or programming code snippets.
 //!
 //! It's primary purpose is to build an ASCII-graphical representation of the snippet
@@ -40,6 +37,11 @@
 //! cargo add annotate-snippets --dev --feature testing-colors
 //! ```
 
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![warn(clippy::print_stderr)]
+#![warn(clippy::print_stdout)]
+#![warn(missing_debug_implementations)]
+
 pub mod renderer;
 mod snippet;
 
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index c0e351c..319e03f 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -26,7 +26,7 @@ fn setup(input_path: std::path::PathBuf) -> tryfn::Case {
 fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> {
     let src = std::fs::read_to_string(input_path)?;
     let (renderer, message): (Renderer, Message<'_>) =
-        toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.message.into()))?;
+        toml::from_str(&src).map(|a: Fixture<'_>| (a.renderer.into(), a.message.into()))?;
     let actual = renderer.render(message).to_string();
     Ok(Data::from(actual).coerce_to(DataFormat::TermSvg))
 }

From 9c43a845b0a537f01b6c039626b56b9de91982b9 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 6 Jun 2024 14:00:12 -0500
Subject: [PATCH 198/302] chore: Update lints from epage/_rust template

---
 Cargo.toml                    | 66 ++++++++++++++++++++++++++++++++++-
 benches/simple.rs             |  2 +-
 src/renderer/display_list.rs  | 64 ++++++++++++++++-----------------
 src/renderer/margin.rs        |  4 +--
 tests/fixtures/deserialize.rs | 14 ++++----
 tests/fixtures/main.rs        |  2 +-
 6 files changed, 108 insertions(+), 44 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index a317c71..5022f46 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,4 +50,68 @@ default = []
 testing-colors = []
 
 [lints.rust]
-rust_2018_idioms = "warn"
+rust_2018_idioms = { level = "warn", priority = -1 }
+unreachable_pub = "warn"
+unsafe_op_in_unsafe_fn = "warn"
+unused_lifetimes = "warn"
+unused_macro_rules = "warn"
+unused_qualifications = "warn"
+
+[lints.clippy]
+bool_assert_comparison = "allow"
+branches_sharing_code = "allow"
+checked_conversions = "warn"
+collapsible_else_if = "allow"
+create_dir = "warn"
+dbg_macro = "warn"
+debug_assert_with_mut_call = "warn"
+doc_markdown = "warn"
+empty_enum = "warn"
+enum_glob_use = "warn"
+expl_impl_clone_on_copy = "warn"
+explicit_deref_methods = "warn"
+explicit_into_iter_loop = "warn"
+fallible_impl_from = "warn"
+filter_map_next = "warn"
+flat_map_option = "warn"
+float_cmp_const = "warn"
+fn_params_excessive_bools = "warn"
+from_iter_instead_of_collect = "warn"
+if_same_then_else = "allow"
+implicit_clone = "warn"
+imprecise_flops = "warn"
+inconsistent_struct_constructor = "warn"
+inefficient_to_string = "warn"
+infinite_loop = "warn"
+invalid_upcast_comparisons = "warn"
+large_digit_groups = "warn"
+large_stack_arrays = "warn"
+large_types_passed_by_value = "warn"
+let_and_return = "allow"  # sometimes good to name what you are returning
+linkedlist = "warn"
+lossy_float_literal = "warn"
+macro_use_imports = "warn"
+mem_forget = "warn"
+mutex_integer = "warn"
+needless_continue = "warn"
+needless_for_each = "warn"
+negative_feature_names = "warn"
+path_buf_push_overwrite = "warn"
+ptr_as_ptr = "warn"
+rc_mutex = "warn"
+redundant_feature_names = "warn"
+ref_option_ref = "warn"
+rest_pat_in_fully_bound_structs = "warn"
+same_functions_in_if_condition = "warn"
+self_named_module_files = "warn"
+semicolon_if_nothing_returned = "warn"
+str_to_string = "warn"
+string_add = "warn"
+string_add_assign = "warn"
+string_lit_as_bytes = "warn"
+string_to_string = "warn"
+todo = "warn"
+trait_duplication_in_bounds = "warn"
+verbose_file_reads = "warn"
+wildcard_imports = "warn"
+zero_sized_map_values = "warn"
diff --git a/benches/simple.rs b/benches/simple.rs
index fccb70b..723793e 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -50,7 +50,7 @@ fn create_snippet(renderer: Renderer) {
 
 pub fn criterion_benchmark(c: &mut Criterion) {
     c.bench_function("format", |b| {
-        b.iter(|| black_box(create_snippet(Renderer::plain())))
+        b.iter(|| black_box(create_snippet(Renderer::plain())));
     });
 }
 
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index ead3db9..d94a660 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1,4 +1,4 @@
-//! display_list module stores the output model for the snippet.
+//! `display_list` module stores the output model for the snippet.
 //!
 //! `DisplayList` is a central structure in the crate, which contains
 //! the structured list of lines to be displayed.
@@ -48,9 +48,9 @@ const WARNING_TXT: &str = "warning";
 
 /// List of lines to be displayed.
 pub(crate) struct DisplayList<'a> {
-    pub body: Vec<DisplaySet<'a>>,
-    pub stylesheet: &'a Stylesheet,
-    pub anonymized_line_numbers: bool,
+    pub(crate) body: Vec<DisplaySet<'a>>,
+    pub(crate) stylesheet: &'a Stylesheet,
+    pub(crate) anonymized_line_numbers: bool,
 }
 
 impl<'a> PartialEq for DisplayList<'a> {
@@ -147,8 +147,8 @@ impl<'a> DisplayList<'a> {
 
 #[derive(Debug, PartialEq)]
 pub(crate) struct DisplaySet<'a> {
-    pub display_lines: Vec<DisplayLine<'a>>,
-    pub margin: Margin,
+    pub(crate) display_lines: Vec<DisplayLine<'a>>,
+    pub(crate) margin: Margin,
 }
 
 impl<'a> DisplaySet<'a> {
@@ -493,15 +493,15 @@ impl<'a> DisplaySet<'a> {
 
 /// Inline annotation which can be used in either Raw or Source line.
 #[derive(Debug, PartialEq)]
-pub struct Annotation<'a> {
-    pub annotation_type: DisplayAnnotationType,
-    pub id: Option<&'a str>,
-    pub label: Vec<DisplayTextFragment<'a>>,
+pub(crate) struct Annotation<'a> {
+    pub(crate) annotation_type: DisplayAnnotationType,
+    pub(crate) id: Option<&'a str>,
+    pub(crate) label: Vec<DisplayTextFragment<'a>>,
 }
 
 /// A single line used in `DisplayList`.
 #[derive(Debug, PartialEq)]
-pub enum DisplayLine<'a> {
+pub(crate) enum DisplayLine<'a> {
     /// A line with `lineno` portion of the slice.
     Source {
         lineno: Option<usize>,
@@ -519,7 +519,7 @@ pub enum DisplayLine<'a> {
 
 /// A source line.
 #[derive(Debug, PartialEq)]
-pub enum DisplaySourceLine<'a> {
+pub(crate) enum DisplaySourceLine<'a> {
     /// A line with the content of the Snippet.
     Content {
         text: &'a str,
@@ -530,17 +530,17 @@ pub enum DisplaySourceLine<'a> {
 }
 
 #[derive(Debug, PartialEq)]
-pub struct DisplaySourceAnnotation<'a> {
-    pub annotation: Annotation<'a>,
-    pub range: (usize, usize),
-    pub annotation_type: DisplayAnnotationType,
-    pub annotation_part: DisplayAnnotationPart,
+pub(crate) struct DisplaySourceAnnotation<'a> {
+    pub(crate) annotation: Annotation<'a>,
+    pub(crate) range: (usize, usize),
+    pub(crate) annotation_type: DisplayAnnotationType,
+    pub(crate) 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)]
-pub enum DisplayRawLine<'a> {
+pub(crate) enum DisplayRawLine<'a> {
     /// A line which provides information about the location of the given
     /// slice in the project structure.
     Origin {
@@ -566,23 +566,23 @@ pub enum DisplayRawLine<'a> {
 
 /// An inline text fragment which any label is composed of.
 #[derive(Debug, PartialEq)]
-pub struct DisplayTextFragment<'a> {
-    pub content: &'a str,
-    pub style: DisplayTextStyle,
+pub(crate) struct DisplayTextFragment<'a> {
+    pub(crate) content: &'a str,
+    pub(crate) style: DisplayTextStyle,
 }
 
 /// A style for the `DisplayTextFragment` which can be visually formatted.
 ///
 /// This information may be used to emphasis parts of the label.
 #[derive(Debug, Clone, Copy, PartialEq)]
-pub enum DisplayTextStyle {
+pub(crate) enum DisplayTextStyle {
     Regular,
     Emphasis,
 }
 
 /// An indicator of what part of the annotation a given `Annotation` is.
 #[derive(Debug, Clone, PartialEq)]
-pub enum DisplayAnnotationPart {
+pub(crate) enum DisplayAnnotationPart {
     /// A standalone, single-line annotation.
     Standalone,
     /// A continuation of a multi-line label of an annotation.
@@ -595,14 +595,14 @@ pub enum DisplayAnnotationPart {
 
 /// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
 #[derive(Debug, Clone, PartialEq)]
-pub struct DisplayMark {
-    pub mark_type: DisplayMarkType,
-    pub annotation_type: DisplayAnnotationType,
+pub(crate) struct DisplayMark {
+    pub(crate) mark_type: DisplayMarkType,
+    pub(crate) annotation_type: DisplayAnnotationType,
 }
 
 /// A type of the `DisplayMark`.
 #[derive(Debug, Clone, PartialEq)]
-pub enum DisplayMarkType {
+pub(crate) enum DisplayMarkType {
     /// A mark indicating a multiline annotation going through the current line.
     AnnotationThrough,
     /// A mark indicating a multiline annotation starting on the given line.
@@ -617,7 +617,7 @@ pub enum DisplayMarkType {
 /// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`.
 /// * `ColorStylesheet` may use different colors for different annotations.
 #[derive(Debug, Clone, PartialEq)]
-pub enum DisplayAnnotationType {
+pub(crate) enum DisplayAnnotationType {
     None,
     Error,
     Warning,
@@ -642,7 +642,7 @@ impl From<snippet::Level> for DisplayAnnotationType {
 /// for multi-slice cases.
 // TODO: private
 #[derive(Debug, Clone, PartialEq)]
-pub enum DisplayHeaderType {
+pub(crate) enum DisplayHeaderType {
     /// Initial header is the first header in the snippet.
     Initial,
 
@@ -728,9 +728,9 @@ fn format_message(
     }
 
     if let Some(first) = sets.first_mut() {
-        body.into_iter().for_each(|line| {
+        for line in body {
             first.display_lines.insert(0, line);
-        });
+        }
     } else {
         sets.push(DisplaySet {
             display_lines: body,
@@ -1335,7 +1335,7 @@ const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
 ];
 
 fn normalize_whitespace(str: &str) -> String {
-    let mut s = str.to_string();
+    let mut s = str.to_owned();
     for (c, replacement) in OUTPUT_REPLACEMENTS {
         s = s.replace(*c, replacement);
     }
diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs
index 3f1b28b..c484416 100644
--- a/src/renderer/margin.rs
+++ b/src/renderer/margin.rs
@@ -5,7 +5,7 @@ const LONG_WHITESPACE: usize = 20;
 const LONG_WHITESPACE_PADDING: usize = 4;
 
 #[derive(Clone, Copy, Debug, PartialEq)]
-pub struct Margin {
+pub(crate) struct Margin {
     /// The available whitespace in the left that can be consumed when centering.
     whitespace_left: usize,
     /// The column of the beginning of left-most span.
@@ -24,7 +24,7 @@ pub struct Margin {
 }
 
 impl Margin {
-    pub fn new(
+    pub(crate) fn new(
         whitespace_left: usize,
         span_left: usize,
         span_right: usize,
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 2d1452b..3ddef79 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -5,11 +5,11 @@ use annotate_snippets::renderer::DEFAULT_TERM_WIDTH;
 use annotate_snippets::{Annotation, Level, Message, Renderer, Snippet};
 
 #[derive(Deserialize)]
-pub struct Fixture<'a> {
+pub(crate) struct Fixture<'a> {
     #[serde(default)]
-    pub renderer: RendererDef,
+    pub(crate) renderer: RendererDef,
     #[serde(borrow)]
-    pub message: MessageDef<'a>,
+    pub(crate) message: MessageDef<'a>,
 }
 
 #[derive(Deserialize)]
@@ -88,7 +88,7 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
         } = val;
         let mut snippet = Snippet::source(source).line_start(line_start).fold(fold);
         if let Some(origin) = origin {
-            snippet = snippet.origin(origin)
+            snippet = snippet.origin(origin);
         }
         snippet = snippet.annotations(annotations);
         snippet
@@ -127,11 +127,11 @@ impl<'a> From<AnnotationDef<'a>> for Annotation<'a> {
 }
 
 #[derive(Serialize, Deserialize)]
-pub struct LabelDef<'a> {
+pub(crate) struct LabelDef<'a> {
     #[serde(with = "LevelDef")]
-    pub level: Level,
+    pub(crate) level: Level,
     #[serde(borrow)]
-    pub label: &'a str,
+    pub(crate) label: &'a str,
 }
 
 #[allow(dead_code)]
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index 319e03f..81d0246 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -15,7 +15,7 @@ fn main() {
 
 fn setup(input_path: std::path::PathBuf) -> tryfn::Case {
     let name = input_path.file_name().unwrap().to_str().unwrap().to_owned();
-    let expected = tryfn::Data::read_from(&input_path.with_extension("svg"), None);
+    let expected = Data::read_from(&input_path.with_extension("svg"), None);
     tryfn::Case {
         name,
         fixture: input_path,

From 4ded7b25a0b0b6ac6bfafe8b5de1832f6f1bcbca Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 6 Jun 2024 14:32:46 -0500
Subject: [PATCH 199/302] chore: Drop MSRV to 1.65

---
 Cargo.lock | 450 +++++++++++++++++++++++++----------------------------
 Cargo.toml |   2 +-
 2 files changed, 217 insertions(+), 235 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 70efddc..1e3073f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
 
 [[package]]
 name = "aho-corasick"
-version = "1.1.2"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
 dependencies = [
  "memchr",
 ]
@@ -21,7 +21,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 name = "annotate-snippets"
 version = "0.11.2"
 dependencies = [
- "anstream",
+ "anstream 0.6.14",
  "anstyle",
  "criterion",
  "difference",
@@ -33,6 +33,21 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "anstream"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon 1.0.2",
+ "colorchoice",
+ "is-terminal",
+ "utf8parse",
+]
+
 [[package]]
 name = "anstream"
 version = "0.6.14"
@@ -42,7 +57,7 @@ dependencies = [
  "anstyle",
  "anstyle-parse",
  "anstyle-query",
- "anstyle-wincon",
+ "anstyle-wincon 3.0.3",
  "colorchoice",
  "is_terminal_polyfill",
  "utf8parse",
@@ -50,44 +65,44 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.4"
+version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
 
 [[package]]
 name = "anstyle-lossy"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9a0444767dbd4aea9355cb47a370eb184dbfe918875e127eff52cb9d1638181"
+checksum = "6fcff6599f06e21b0165c85052ccd6e67dc388ddd1c516a9dc5f55dc8cacf004"
 dependencies = [
  "anstyle",
 ]
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.0.2"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
 dependencies = [
  "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "anstyle-svg"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b6ddad447b448d6d5db36b31cbd3ff27c7af071619501998eeceab01968287a"
+checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa"
 dependencies = [
- "anstream",
+ "anstream 0.6.14",
  "anstyle",
  "anstyle-lossy",
  "html-escape",
@@ -96,31 +111,35 @@ dependencies = [
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.2"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
 dependencies = [
  "anstyle",
- "windows-sys 0.52.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
-name = "autocfg"
-version = "1.1.0"
+name = "anstyle-wincon"
+version = "3.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
 
 [[package]]
-name = "bitflags"
-version = "2.4.1"
+name = "autocfg"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
 
 [[package]]
 name = "bstr"
-version = "1.8.0"
+version = "1.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
+checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
 dependencies = [
  "memchr",
  "serde",
@@ -146,9 +165,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "ciborium"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
 dependencies = [
  "ciborium-io",
  "ciborium-ll",
@@ -157,15 +176,15 @@ dependencies = [
 
 [[package]]
 name = "ciborium-io"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
 
 [[package]]
 name = "ciborium-ll"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
 dependencies = [
  "ciborium-io",
  "half",
@@ -173,21 +192,22 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.4.7"
+version = "4.3.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
+checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487"
 dependencies = [
  "clap_builder",
  "clap_derive",
+ "once_cell",
 ]
 
 [[package]]
 name = "clap_builder"
-version = "4.4.7"
+version = "4.3.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
+checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e"
 dependencies = [
- "anstream",
+ "anstream 0.3.2",
  "anstyle",
  "clap_lex",
  "strsim",
@@ -195,9 +215,9 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.4.7"
+version = "4.3.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -207,15 +227,15 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.6.0"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
 
 [[package]]
 name = "colorchoice"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
 
 [[package]]
 name = "criterion"
@@ -255,36 +275,34 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-deque"
-version = "0.8.3"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
 dependencies = [
- "cfg-if",
  "crossbeam-epoch",
  "crossbeam-utils",
 ]
 
 [[package]]
 name = "crossbeam-epoch"
-version = "0.9.15"
+version = "0.9.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
 dependencies = [
- "autocfg",
- "cfg-if",
  "crossbeam-utils",
- "memoffset",
- "scopeguard",
 ]
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.16"
+version = "0.8.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
-dependencies = [
- "cfg-if",
-]
+checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
 
 [[package]]
 name = "difference"
@@ -294,19 +312,9 @@ checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
 
 [[package]]
 name = "either"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
-
-[[package]]
-name = "errno"
-version = "0.3.8"
+version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
-dependencies = [
- "libc",
- "windows-sys 0.52.0",
-]
+checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
 
 [[package]]
 name = "escape8259"
@@ -319,9 +327,9 @@ dependencies = [
 
 [[package]]
 name = "escargot"
-version = "0.5.10"
+version = "0.5.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f474c6844cbd04e783d0f25757583db4f491770ca618bedf2fb01815fc79939"
+checksum = "650eb5f6eeda986377996e9ed570cbc20cc16d30440696f82f129c863e4e3e83"
 dependencies = [
  "log",
  "once_cell",
@@ -350,9 +358,12 @@ dependencies = [
 
 [[package]]
 name = "half"
-version = "1.8.2"
+version = "2.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
+dependencies = [
+ "crunchy",
+]
 
 [[package]]
 name = "heck"
@@ -362,9 +373,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
 
 [[package]]
 name = "hermit-abi"
-version = "0.3.3"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
 
 [[package]]
 name = "html-escape"
@@ -377,36 +388,40 @@ dependencies = [
 
 [[package]]
 name = "ignore"
-version = "0.4.22"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
+checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
 dependencies = [
- "crossbeam-deque",
  "globset",
+ "lazy_static",
  "log",
  "memchr",
- "regex-automata",
+ "regex",
  "same-file",
+ "thread_local",
  "walkdir",
  "winapi-util",
 ]
 
 [[package]]
 name = "is-terminal"
-version = "0.4.9"
+version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
+checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
 dependencies = [
  "hermit-abi",
- "rustix",
- "windows-sys 0.48.0",
+ "libc",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "is_terminal_polyfill"
-version = "1.70.0"
+version = "1.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
+checksum = "b52b2de84ed0341893ce61ca1af04fa54eea0a764ecc38c6855cc5db84dc1927"
+dependencies = [
+ "is-terminal",
+]
 
 [[package]]
 name = "itertools"
@@ -419,30 +434,36 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.9"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
 [[package]]
 name = "js-sys"
-version = "0.3.65"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
 dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
 [[package]]
 name = "libc"
-version = "0.2.153"
+version = "0.2.155"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
 
 [[package]]
 name = "libtest-mimic"
-version = "0.7.0"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f0f4c6f44ecfd52e8b443f2ad18f2b996540135771561283c2352ce56a1c70b"
+checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58"
 dependencies = [
  "clap",
  "escape8259",
@@ -450,32 +471,17 @@ dependencies = [
  "threadpool",
 ]
 
-[[package]]
-name = "linux-raw-sys"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
-
 [[package]]
 name = "log"
-version = "0.4.20"
+version = "0.4.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
 
 [[package]]
 name = "memchr"
-version = "2.6.4"
+version = "2.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
-
-[[package]]
-name = "memoffset"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
-dependencies = [
- "autocfg",
-]
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
 
 [[package]]
 name = "normalize-line-endings"
@@ -485,9 +491,9 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
 
 [[package]]
 name = "num-traits"
-version = "0.2.17"
+version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 dependencies = [
  "autocfg",
 ]
@@ -504,9 +510,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.18.0"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "oorandom"
@@ -516,9 +522,9 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
 
 [[package]]
 name = "os_pipe"
-version = "1.1.5"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
+checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
 dependencies = [
  "libc",
  "windows-sys 0.52.0",
@@ -526,9 +532,9 @@ dependencies = [
 
 [[package]]
 name = "plotters"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
+checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3"
 dependencies = [
  "num-traits",
  "plotters-backend",
@@ -539,42 +545,42 @@ dependencies = [
 
 [[package]]
 name = "plotters-backend"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
+checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7"
 
 [[package]]
 name = "plotters-svg"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
+checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705"
 dependencies = [
  "plotters-backend",
 ]
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.78"
+version = "1.0.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.35"
+version = "1.0.36"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "rayon"
-version = "1.8.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
 dependencies = [
  "either",
  "rayon-core",
@@ -582,9 +588,9 @@ dependencies = [
 
 [[package]]
 name = "rayon-core"
-version = "1.12.0"
+version = "1.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
 dependencies = [
  "crossbeam-deque",
  "crossbeam-utils",
@@ -592,9 +598,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.10.2"
+version = "1.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -604,9 +610,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.4.3"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -615,34 +621,21 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
-
-[[package]]
-name = "rustix"
-version = "0.38.31"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
-dependencies = [
- "bitflags",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys 0.52.0",
-]
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
 [[package]]
 name = "rustversion"
-version = "1.0.14"
+version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
 
 [[package]]
 name = "ryu"
-version = "1.0.15"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
 
 [[package]]
 name = "same-file"
@@ -653,12 +646,6 @@ dependencies = [
  "winapi-util",
 ]
 
-[[package]]
-name = "scopeguard"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-
 [[package]]
 name = "serde"
 version = "1.0.203"
@@ -681,9 +668,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.108"
+version = "1.0.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
 dependencies = [
  "itoa",
  "ryu",
@@ -692,9 +679,9 @@ dependencies = [
 
 [[package]]
 name = "similar"
-version = "2.4.0"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
+checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
 
 [[package]]
 name = "snapbox"
@@ -702,7 +689,7 @@ version = "0.6.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94204b12a4d3550420babdb4148c6639692e4e3e61060866929c5107f208aeb6"
 dependencies = [
- "anstream",
+ "anstream 0.6.14",
  "anstyle",
  "anstyle-svg",
  "escargot",
@@ -722,7 +709,7 @@ version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d"
 dependencies = [
- "anstream",
+ "anstream 0.6.14",
 ]
 
 [[package]]
@@ -733,9 +720,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
 [[package]]
 name = "syn"
-version = "2.0.48"
+version = "2.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -751,6 +738,16 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
 [[package]]
 name = "threadpool"
 version = "1.8.1"
@@ -798,9 +795,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.11"
+version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
 
 [[package]]
 name = "utf8-width"
@@ -825,9 +822,9 @@ dependencies = [
 
 [[package]]
 name = "walkdir"
-version = "2.4.0"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
 dependencies = [
  "same-file",
  "winapi-util",
@@ -835,9 +832,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.88"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -845,9 +842,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.88"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
 dependencies = [
  "bumpalo",
  "log",
@@ -860,9 +857,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.88"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -870,9 +867,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.88"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -883,51 +880,29 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.88"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
 
 [[package]]
 name = "web-sys"
-version = "0.3.65"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
+checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
 [[package]]
 name = "winapi-util"
-version = "0.1.6"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
 dependencies = [
- "winapi",
+ "windows-sys 0.52.0",
 ]
 
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
 [[package]]
 name = "windows-sys"
 version = "0.48.0"
@@ -943,7 +918,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.4",
+ "windows-targets 0.52.5",
 ]
 
 [[package]]
@@ -963,17 +938,18 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.4",
- "windows_aarch64_msvc 0.52.4",
- "windows_i686_gnu 0.52.4",
- "windows_i686_msvc 0.52.4",
- "windows_x86_64_gnu 0.52.4",
- "windows_x86_64_gnullvm 0.52.4",
- "windows_x86_64_msvc 0.52.4",
+ "windows_aarch64_gnullvm 0.52.5",
+ "windows_aarch64_msvc 0.52.5",
+ "windows_i686_gnu 0.52.5",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.5",
+ "windows_x86_64_gnu 0.52.5",
+ "windows_x86_64_gnullvm 0.52.5",
+ "windows_x86_64_msvc 0.52.5",
 ]
 
 [[package]]
@@ -984,9 +960,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -996,9 +972,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -1008,9 +984,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.4"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -1020,9 +1002,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -1032,9 +1014,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -1044,9 +1026,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -1056,6 +1038,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.4"
+version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
diff --git a/Cargo.toml b/Cargo.toml
index 5022f46..12fadf6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
 name = "annotate-snippets"
 version = "0.11.2"
 edition = "2021"
-rust-version = "1.73"  # MSRV
+rust-version = "1.65"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]
 description = "Library for building code annotations"
 license = "Apache-2.0/MIT"

From 8908d92ea2198e3b18be16c3a5ef9953a8d29e15 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 6 Jun 2024 19:33:34 +0000
Subject: [PATCH 200/302] chore(deps): Update Rust Stable to v1.78

---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a621652..b643aba 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -86,7 +86,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.76"  # STABLE
+        toolchain: "1.78"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -101,7 +101,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.76"  # STABLE
+        toolchain: "1.78"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -117,7 +117,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.76"  # STABLE
+        toolchain: "1.78"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From 3c15fc804e7d98f6f3eb9fb9891b5b797d0f0f98 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 6 Jun 2024 14:54:09 -0500
Subject: [PATCH 201/302] docs: Update changelog

---
 CHANGELOG.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d379628..51665a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+### Fixes
+
+- Dropped MSRV to 1.65
+
 ## [0.11.2] - 2024-04-27
 
 ### Added

From 92275d29d8e6cb123e7e92f9a3d89c5a332d52cc Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 6 Jun 2024 14:04:54 -0600
Subject: [PATCH 202/302] chore: Release annotate-snippets version 0.11.3

---
 CHANGELOG.md | 5 ++++-
 Cargo.lock   | 2 +-
 Cargo.toml   | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 51665a9..40586d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+## [0.11.3] - 2024-06-06
+
 ### Fixes
 
 - Dropped MSRV to 1.65
@@ -136,7 +138,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Update the syntax to Rust 2018 idioms. (#4)
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...HEAD
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.3...HEAD
+[0.11.3]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...0.11.3
 [0.11.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...0.11.2
 [0.11.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...0.11.1
 [0.11.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...0.11.0
diff --git a/Cargo.lock b/Cargo.lock
index 1e3073f..a38d9da 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "annotate-snippets"
-version = "0.11.2"
+version = "0.11.3"
 dependencies = [
  "anstream 0.6.14",
  "anstyle",
diff --git a/Cargo.toml b/Cargo.toml
index 12fadf6..1b6bd10 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "annotate-snippets"
-version = "0.11.2"
+version = "0.11.3"
 edition = "2021"
 rust-version = "1.65"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]

From ca313bfb3d8895f8602724f782ad0012ddf26200 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 13 Jun 2024 14:20:48 +0000
Subject: [PATCH 203/302] chore(deps): Update Rust Stable to v1.79

---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b643aba..e1d62e4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -86,7 +86,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.78"  # STABLE
+        toolchain: "1.79"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -101,7 +101,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.78"  # STABLE
+        toolchain: "1.79"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -117,7 +117,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.78"  # STABLE
+        toolchain: "1.79"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From 004e6f9388aeb2755d9903d98f83d13ba7a9d172 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 14 Jun 2024 12:34:37 -0600
Subject: [PATCH 204/302] test: Cleanup ann_multiline2 source

---
 tests/fixtures/no-color/ann_multiline2.svg  | 4 ++--
 tests/fixtures/no-color/ann_multiline2.toml | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/tests/fixtures/no-color/ann_multiline2.svg b/tests/fixtures/no-color/ann_multiline2.svg
index 18a9bf6..de5c6f0 100644
--- a/tests/fixtures/no-color/ann_multiline2.svg
+++ b/tests/fixtures/no-color/ann_multiline2.svg
@@ -22,11 +22,11 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   |</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>26 |   This is an exampl</tspan>
+    <tspan x="10px" y="82px"><tspan>26 |   This is an example</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   |  ____________^</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>27 | | e of an edge case of an annotation overflowing</tspan>
+    <tspan x="10px" y="118px"><tspan>27 | | of an edge case of an annotation overflowing</tspan>
 </tspan>
     <tspan x="10px" y="136px"><tspan>   | |_^ this should not be on separate lines</tspan>
 </tspan>
diff --git a/tests/fixtures/no-color/ann_multiline2.toml b/tests/fixtures/no-color/ann_multiline2.toml
index dd85332..afb3aa9 100644
--- a/tests/fixtures/no-color/ann_multiline2.toml
+++ b/tests/fixtures/no-color/ann_multiline2.toml
@@ -5,8 +5,8 @@ title = "spacing error found"
 
 [[message.snippets]]
 source = """
-This is an exampl
-e of an edge case of an annotation overflowing
+This is an example
+of an edge case of an annotation overflowing
 to exactly one character on next line.
 """
 line_start = 26
@@ -15,4 +15,4 @@ fold = false
 [[message.snippets.annotations]]
 label = "this should not be on separate lines"
 level = "Error"
-range = [11, 18]
+range = [11, 19]

From c68600d669efa3c9a371067c770d711598b3422e Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 14 Jun 2024 16:46:53 -0600
Subject: [PATCH 205/302] fix: Improve annotating line endings

---
 src/renderer/display_list.rs                  |  87 +++-
 tests/fixtures/no-color/ann_multiline2.svg    |  14 +-
 .../fixtures/no-color/fold_ann_multiline.svg  |  14 +-
 tests/formatter.rs                            | 429 ++++++++++++++++++
 4 files changed, 511 insertions(+), 33 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index d94a660..bc46f7f 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -524,6 +524,7 @@ pub(crate) enum DisplaySourceLine<'a> {
     Content {
         text: &'a str,
         range: (usize, usize), // meta information for annotation placement.
+        end_line: EndLine,
     },
     /// An empty source line.
     Empty,
@@ -658,7 +659,8 @@ impl<'a> CursorLines<'a> {
     }
 }
 
-enum EndLine {
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub(crate) enum EndLine {
     Eof = 0,
     Crlf = 1,
     Lf = 2,
@@ -847,13 +849,20 @@ fn format_header<'a>(
 
         for item in body {
             if let DisplayLine::Source {
-                line: DisplaySourceLine::Content { text, range },
+                line:
+                    DisplaySourceLine::Content {
+                        text,
+                        range,
+                        end_line,
+                    },
                 lineno,
                 ..
             } = item
             {
-                if main_range >= range.0 && main_range <= range.1 {
-                    let char_column = text[0..(main_range - range.0)].chars().count();
+                if main_range >= range.0 && main_range <= range.1 + *end_line as usize {
+                    let char_column = text[0..(main_range - range.0).min(text.len())]
+                        .chars()
+                        .count();
                     col = char_column + 1;
                     line_offset = lineno.unwrap_or(1);
                     break;
@@ -927,8 +936,18 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
     let mut unhighlighed_lines = vec![];
     for line in body {
         match &line {
-            DisplayLine::Source { annotations, .. } => {
-                if annotations.is_empty() {
+            DisplayLine::Source {
+                annotations,
+                inline_marks,
+                ..
+            } => {
+                if annotations.is_empty()
+                    // A multiline start mark (`/`) needs be treated as an
+                    // annotation or the line could get folded.
+                    && inline_marks
+                        .iter()
+                        .all(|m| m.mark_type != DisplayMarkType::AnnotationStart)
+                {
                     unhighlighed_lines.push(line);
                 } else {
                     if lines.is_empty() {
@@ -1016,12 +1035,14 @@ fn format_body(
     for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() {
         let line_length: usize = line.len();
         let line_range = (current_index, current_index + line_length);
+        let end_line_size = end_line as usize;
         body.push(DisplayLine::Source {
             lineno: Some(current_line),
             inline_marks: vec![],
             line: DisplaySourceLine::Content {
                 text: line,
                 range: line_range,
+                end_line,
             },
             annotations: vec![],
         });
@@ -1045,7 +1066,7 @@ fn format_body(
         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;
+        current_index += line_length + end_line_size;
 
         // It would be nice to use filter_drain here once it's stable.
         annotations.retain(|annotation| {
@@ -1057,18 +1078,24 @@ fn format_body(
             };
             let label_right = annotation.label.map_or(0, |label| label.len() + 1);
             match annotation.range {
-                Range { start, .. } if start > line_end_index => true,
+                // This handles if the annotation is on the next line. We add
+                // the `end_line_size` to account for annotating the line end.
+                Range { start, .. } if start > line_end_index + end_line_size => true,
+                // This handles the case where an annotation is contained
+                // within the current line including any line-end characters.
                 Range { start, end }
-                    if start >= line_start_index && end <= line_end_index
-                        // Allow annotating eof or stripped eol
-                        || start == line_end_index && end - start <= 1 =>
+                    if start >= line_start_index
+                        // We add at least one to `line_end_index` to allow
+                        // highlighting the end of a file
+                        && end <= line_end_index + max(end_line_size, 1) =>
                 {
                     if let DisplayLine::Source {
                         ref mut annotations,
                         ..
                     } = body[body_idx]
                     {
-                        let annotation_start_col = line[0..(start - line_start_index)]
+                        let annotation_start_col = line
+                            [0..(start - line_start_index).min(line_length)]
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
                             .sum::<usize>();
@@ -1101,11 +1128,16 @@ fn format_body(
                     }
                     false
                 }
+                // This handles the case where a multiline annotation starts
+                // somewhere on the current line, including any line-end chars
                 Range { start, end }
                     if start >= line_start_index
-                        && start <= line_end_index
+                        // The annotation can start on a line ending
+                        && start <= line_end_index + end_line_size.saturating_sub(1)
                         && end > line_end_index =>
                 {
+                    // Special case for multiline annotations that start at the
+                    // beginning of a line, which requires a special mark (`/`)
                     if start - line_start_index == 0 {
                         if let DisplayLine::Source {
                             ref mut inline_marks,
@@ -1122,7 +1154,8 @@ fn format_body(
                         ..
                     } = body[body_idx]
                     {
-                        let annotation_start_col = line[0..(start - line_start_index)]
+                        let annotation_start_col = line
+                            [0..(start - line_start_index).min(line_length)]
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
                             .sum::<usize>();
@@ -1147,7 +1180,11 @@ fn format_body(
                     }
                     true
                 }
-                Range { start, end } if start < line_start_index && end > line_end_index => {
+                // This handles the case where a multiline annotation starts
+                // somewhere before this line and ends after it as well
+                Range { start, end }
+                    if start < line_start_index && end > line_end_index + max(end_line_size, 1) =>
+                {
                     if let DisplayLine::Source {
                         ref mut inline_marks,
                         ..
@@ -1160,10 +1197,14 @@ fn format_body(
                     }
                     true
                 }
+                // This handles the case where a multiline annotation ends
+                // somewhere on the current line, including any line-end chars
                 Range { start, end }
                     if start < line_start_index
                         && end >= line_start_index
-                        && end <= line_end_index =>
+                        // We add at least one to `line_end_index` to allow
+                        // highlighting the end of a file
+                        && end <= line_end_index + max(end_line_size, 1) =>
                 {
                     if let DisplayLine::Source {
                         ref mut inline_marks,
@@ -1175,13 +1216,21 @@ fn format_body(
                             mark_type: DisplayMarkType::AnnotationThrough,
                             annotation_type: DisplayAnnotationType::from(annotation.level),
                         });
-                        let end_mark = line[0..(end - line_start_index)]
+                        let end_mark = line[0..(end - line_start_index).min(line_length)]
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
                             .sum::<usize>()
                             .saturating_sub(1);
-
-                        let end_plus_one = end_mark + 1;
+                        // If the annotation ends on a line-end character, we
+                        // need to annotate one past the end of the line
+                        let (end_mark, end_plus_one) = if end > line_end_index
+                            // Special case for highlighting the end of a file
+                            || (end == line_end_index + 1 && end_line_size == 0)
+                        {
+                            (end_mark + 1, end_mark + 2)
+                        } else {
+                            (end_mark, end_mark + 1)
+                        };
 
                         span_left_margin = min(span_left_margin, end_mark);
                         span_right_margin = max(span_right_margin, end_plus_one);
diff --git a/tests/fixtures/no-color/ann_multiline2.svg b/tests/fixtures/no-color/ann_multiline2.svg
index de5c6f0..49c2c4b 100644
--- a/tests/fixtures/no-color/ann_multiline2.svg
+++ b/tests/fixtures/no-color/ann_multiline2.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -22,17 +22,15 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   |</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>26 |   This is an example</tspan>
+    <tspan x="10px" y="82px"><tspan>26 | This is an example</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   |  ____________^</tspan>
+    <tspan x="10px" y="100px"><tspan>   |            ^^^^^^^ this should not be on separate lines</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>27 | | of an edge case of an annotation overflowing</tspan>
+    <tspan x="10px" y="118px"><tspan>27 | of an edge case of an annotation overflowing</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>   | |_^ this should not be on separate lines</tspan>
+    <tspan x="10px" y="136px"><tspan>28 | to exactly one character on next line.</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>28 |   to exactly one character on next line.</tspan>
-</tspan>
-    <tspan x="10px" y="172px"><tspan>   |</tspan>
+    <tspan x="10px" y="154px"><tspan>   |</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/no-color/fold_ann_multiline.svg b/tests/fixtures/no-color/fold_ann_multiline.svg
index 0d2d67c..f82fe25 100644
--- a/tests/fixtures/no-color/fold_ann_multiline.svg
+++ b/tests/fixtures/no-color/fold_ann_multiline.svg
@@ -1,4 +1,4 @@
-<svg width="869px" height="218px" xmlns="http://www.w3.org/2000/svg">
+<svg width="869px" height="236px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,15 +28,17 @@
 </tspan>
     <tspan x="10px" y="118px"><tspan>52 | /     for ann in annotations {</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>...  |</tspan>
+    <tspan x="10px" y="136px"><tspan>53 | |         match (ann.range.0, ann.range.1) {</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>71 | |         }</tspan>
+    <tspan x="10px" y="154px"><tspan>...  |</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>72 | |     }</tspan>
+    <tspan x="10px" y="172px"><tspan>71 | |         }</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan>   | |_____^ expected enum `std::option::Option`, found ()</tspan>
+    <tspan x="10px" y="190px"><tspan>72 | |     }</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>   |</tspan>
+    <tspan x="10px" y="208px"><tspan>   | |_____^ expected enum `std::option::Option`, found ()</tspan>
+</tspan>
+    <tspan x="10px" y="226px"><tspan>   |</tspan>
 </tspan>
   </text>
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 5b746e1..fa3927c 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -303,3 +303,432 @@ LL | abc
     let renderer = Renderer::plain().anonymized_line_numbers(true);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
+
+#[test]
+fn issue_130() {
+    let input = Level::Error.title("dummy").snippet(
+        Snippet::source("foo\nbar\nbaz")
+            .origin("file/path")
+            .line_start(3)
+            .fold(true)
+            .annotation(Level::Error.span(4..11)), // bar\nbaz
+    );
+
+    let expected = str![[r#"
+error: dummy
+ --> file/path:4:1
+  |
+4 | / bar
+5 | | baz
+  | |___^
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn unterminated_string_multiline() {
+    let source = "\
+a\"
+// ...
+";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .fold(true)
+            .annotation(Level::Error.span(0..10)), // 1..10 works
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:1
+  |
+3 | / a"
+4 | | // ...
+  | |_______^
+  |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn char_and_nl_annotate_char() {
+    let source = "a\r\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(0..2)), // a\r
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:1
+  |
+3 | a
+  | ^
+4 | b
+  |"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn char_eol_annotate_char() {
+    let source = "a\r\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(0..3)), // a\r\n
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:1
+  |
+3 | a
+  | ^
+4 | b
+  |"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn char_eol_annotate_char_double_width() {
+    let snippets = Level::Error.title("").snippet(
+        Snippet::source("こん\r\nにちは\r\n世界")
+            .origin("<current file>")
+            .annotation(Level::Error.span(3..8)), // ん\r\n
+    );
+
+    let expected = str![[r#"
+error
+ --> <current file>:1:2
+  |
+1 | こん
+  |   ^^
+2 | にちは
+3 | 世界
+  |
+"#]];
+
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+}
+
+#[test]
+fn annotate_eol() {
+    let source = "a\r\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(1..2)), // \r
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 | a
+  |  ^
+4 | b
+  |"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn annotate_eol2() {
+    let source = "a\r\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(1..3)), // \r\n
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 | a
+  |  ^
+4 | b
+  |"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn annotate_eol3() {
+    let source = "a\r\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(2..3)), // \n
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 | a
+  |  ^
+4 | b
+  |"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn annotate_eol4() {
+    let source = "a\r\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(2..2)), // \n
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 | a
+  |  ^
+4 | b
+  |"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn annotate_eol_double_width() {
+    let snippets = Level::Error.title("").snippet(
+        Snippet::source("こん\r\nにちは\r\n世界")
+            .origin("<current file>")
+            .annotation(Level::Error.span(7..8)), // \n
+    );
+
+    let expected = str![[r#"
+error
+ --> <current file>:1:3
+  |
+1 | こん
+  |     ^
+2 | にちは
+3 | 世界
+  |
+"#]];
+
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+}
+
+#[test]
+fn multiline_eol_start() {
+    let source = "a\r\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(1..4)), // \r\nb
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 |   a
+  |  __^
+4 | | b
+  | |_^
+  |"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn multiline_eol_start2() {
+    let source = "a\r\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(2..4)), // \nb
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 |   a
+  |  __^
+4 | | b
+  | |_^
+  |"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn multiline_eol_start3() {
+    let source = "a\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(1..3)), // \nb
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 |   a
+  |  __^
+4 | | b
+  | |_^
+  |"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn multiline_eol_start_double_width() {
+    let snippets = Level::Error.title("").snippet(
+        Snippet::source("こん\r\nにちは\r\n世界")
+            .origin("<current file>")
+            .annotation(Level::Error.span(7..11)), // \r\nに
+    );
+
+    let expected = str![[r#"
+error
+ --> <current file>:1:3
+  |
+1 |   こん
+  |  _____^
+2 | | にちは
+  | |__^
+3 |   世界
+  |
+"#]];
+
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+}
+
+#[test]
+fn multiline_eol_start_eol_end() {
+    let source = "a\nb\nc";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(1..4)), // \nb\n
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 |   a
+  |  __^
+4 | | b
+  | |__^
+5 |   c
+  |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn multiline_eol_start_eol_end2() {
+    let source = "a\r\nb\r\nc";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(2..5)), // \nb\r
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 |   a
+  |  __^
+4 | | b
+  | |__^
+5 |   c
+  |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn multiline_eol_start_eol_end3() {
+    let source = "a\r\nb\r\nc";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(2..6)), // \nb\r\n
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 |   a
+  |  __^
+4 | | b
+  | |__^
+5 |   c
+  |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn multiline_eol_start_eof_end() {
+    let source = "a\r\nb";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(1..5)), // \r\nb(EOF)
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 |   a
+  |  __^
+4 | | b
+  | |__^
+  |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn multiline_eol_start_eof_end_double_width() {
+    let source = "ん\r\nに";
+    let input = Level::Error.title("").snippet(
+        Snippet::source(source)
+            .origin("file/path")
+            .line_start(3)
+            .annotation(Level::Error.span(3..9)), // \r\nに(EOF)
+    );
+    let expected = str![[r#"
+error
+ --> file/path:3:2
+  |
+3 |   ん
+  |  ___^
+4 | | に
+  | |___^
+  |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}

From b3b10c98824ff07963ed58c79a795c5b641db15e Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 14 Jun 2024 18:28:43 -0600
Subject: [PATCH 206/302] docs: Update changelog

---
 CHANGELOG.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40586d5..4c38f1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+### Fixes
+
+- Annotations for `\r\n` are now correctly handled [#131](https://github.com/rust-lang/annotate-snippets-rs/pull/131)
+
 ## [0.11.3] - 2024-06-06
 
 ### Fixes

From fb498f918087557f48dd34b81f3bf4081fe6e961 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 14 Jun 2024 18:30:02 -0600
Subject: [PATCH 207/302] chore: Release annotate-snippets version 0.11.4

---
 CHANGELOG.md | 5 ++++-
 Cargo.lock   | 2 +-
 Cargo.toml   | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c38f1f..1c7d2d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+## [0.11.4] - 2024-06-15
+
 ### Fixes
 
 - Annotations for `\r\n` are now correctly handled [#131](https://github.com/rust-lang/annotate-snippets-rs/pull/131)
@@ -142,7 +144,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Update the syntax to Rust 2018 idioms. (#4)
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.3...HEAD
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.4...HEAD
+[0.11.4]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.3...0.11.4
 [0.11.3]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...0.11.3
 [0.11.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...0.11.2
 [0.11.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...0.11.1
diff --git a/Cargo.lock b/Cargo.lock
index a38d9da..40a064e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "annotate-snippets"
-version = "0.11.3"
+version = "0.11.4"
 dependencies = [
  "anstream 0.6.14",
  "anstyle",
diff --git a/Cargo.toml b/Cargo.toml
index 1b6bd10..62ab387 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "annotate-snippets"
-version = "0.11.3"
+version = "0.11.4"
 edition = "2021"
 rust-version = "1.65"  # MSRV
 authors = ["Zibi Braniecki <gandalf@mozilla.com>"]

From 52bf734fc084c6a0aa5eb0a44b2d47c88c0868e4 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 19 Jun 2024 11:38:04 -0600
Subject: [PATCH 208/302] test: Add tests for multiple annotations per line

---
 tests/formatter.rs | 165 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 165 insertions(+)

diff --git a/tests/formatter.rs b/tests/formatter.rs
index fa3927c..d0ac369 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -732,3 +732,168 @@ error
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
+
+#[test]
+fn two_single_line_same_line() {
+    let source = r#"bar = { version = "0.1.0", optional = true }"#;
+    let input = Level::Error.title("unused optional dependency").snippet(
+        Snippet::source(source)
+            .origin("Cargo.toml")
+            .line_start(4)
+            .annotation(
+                Level::Error
+                    .span(0..3)
+                    .label("I need this to be really long so I can test overlaps"),
+            )
+            .annotation(
+                Level::Info
+                    .span(27..42)
+                    .label("This should also be long but not too long"),
+            ),
+    );
+    let expected = str![[r#"
+error: unused optional dependency
+ --> Cargo.toml:4:1
+  |
+4 | bar = { version = "0.1.0", optional = true }
+  | ^^^ I need this to be really long so I can test overlaps
+  |                            --------------- info: This should also be long but not too long
+  |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(false);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn multi_and_single() {
+    let source = r#"bar = { version = "0.1.0", optional = true }
+this is another line
+so is this
+bar = { version = "0.1.0", optional = true }
+"#;
+    let input = Level::Error.title("unused optional dependency").snippet(
+        Snippet::source(source)
+            .line_start(4)
+            .annotation(
+                Level::Error
+                    .span(41..119)
+                    .label("I need this to be really long so I can test overlaps"),
+            )
+            .annotation(
+                Level::Info
+                    .span(27..42)
+                    .label("This should also be long but not too long"),
+            ),
+    );
+    let expected = str![[r#"
+error: unused optional dependency
+  |
+4 |   bar = { version = "0.1.0", optional = true }
+  |  __________________________________________^
+  |                              --------------- info: This should also be long but not too long
+5 | | this is another line
+6 | | so is this
+7 | | bar = { version = "0.1.0", optional = true }
+  | |__________________________________________^ I need this to be really long so I can test overlaps
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn two_multi_and_single() {
+    let source = r#"bar = { version = "0.1.0", optional = true }
+this is another line
+so is this
+bar = { version = "0.1.0", optional = true }
+"#;
+    let input = Level::Error.title("unused optional dependency").snippet(
+        Snippet::source(source)
+            .line_start(4)
+            .annotation(
+                Level::Error
+                    .span(41..119)
+                    .label("I need this to be really long so I can test overlaps"),
+            )
+            .annotation(
+                Level::Error
+                    .span(8..102)
+                    .label("I need this to be really long so I can test overlaps"),
+            )
+            .annotation(
+                Level::Info
+                    .span(27..42)
+                    .label("This should also be long but not too long"),
+            ),
+    );
+    let expected = str![[r#"
+error: unused optional dependency
+  |
+4 |    bar = { version = "0.1.0", optional = true }
+  |   __________________________________________^
+  |   _________^
+  |                               --------------- info: This should also be long but not too long
+5 | || this is another line
+6 | || so is this
+7 | || bar = { version = "0.1.0", optional = true }
+  | ||__________________________________________^ I need this to be really long so I can test overlaps
+  | ||_________________________^ I need this to be really long so I can test overlaps
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn three_multi_and_single() {
+    let source = r#"bar = { version = "0.1.0", optional = true }
+this is another line
+so is this
+bar = { version = "0.1.0", optional = true }
+this is another line
+"#;
+    let input = Level::Error.title("unused optional dependency").snippet(
+        Snippet::source(source)
+            .line_start(4)
+            .annotation(
+                Level::Error
+                    .span(41..119)
+                    .label("I need this to be really long so I can test overlaps"),
+            )
+            .annotation(
+                Level::Error
+                    .span(8..102)
+                    .label("I need this to be really long so I can test overlaps"),
+            )
+            .annotation(
+                Level::Error
+                    .span(48..126)
+                    .label("I need this to be really long so I can test overlaps"),
+            )
+            .annotation(
+                Level::Info
+                    .span(27..42)
+                    .label("This should also be long but not too long"),
+            ),
+    );
+    let expected = str![[r#"
+error: unused optional dependency
+  |
+4 |     bar = { version = "0.1.0", optional = true }
+  |    __________________________________________^
+  |    _________^
+  |                                --------------- info: This should also be long but not too long
+5 |  || this is another line
+  |  ||____^
+6 | ||| so is this
+7 | ||| bar = { version = "0.1.0", optional = true }
+  | |||__________________________________________^ I need this to be really long so I can test overlaps
+  | |||_________________________^ I need this to be really long so I can test overlaps
+8 |   | this is another line
+  |   |____^ I need this to be really long so I can test overlaps
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}

From d1d3a628e5eab73c5524d919820d85e2969e80ac Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 19 Jun 2024 11:39:56 -0600
Subject: [PATCH 209/302] test: Add some of Rust's parser tests

---
 tests/rustc_tests.rs | 778 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 778 insertions(+)
 create mode 100644 tests/rustc_tests.rs

diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
new file mode 100644
index 0000000..f87b206
--- /dev/null
+++ b/tests/rustc_tests.rs
@@ -0,0 +1,778 @@
+//! These tests have been adapted from [Rust's parser tests][parser-tests].
+//!
+//! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs
+
+use annotate_snippets::{Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, str};
+
+#[test]
+fn ends_on_col0() {
+    let source = r#"
+fn foo() {
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(10..13).label("test")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:2:10
+  |
+2 |   fn foo() {
+  |  __________^
+3 | | }
+  | |_^ test
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn ends_on_col2() {
+    let source = r#"
+fn foo() {
+
+
+  }
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(10..17).label("test")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:2:10
+  |
+2 |   fn foo() {
+  |  __________^
+3 | | 
+4 | | 
+5 | |   }
+  | |___^ test
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn non_nested() {
+    let source = r#"
+fn foo() {
+  X0 Y0
+  X1 Y1
+  X2 Y2
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..32).label("`X` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(17..35)
+                    .label("`Y` is a good letter too"),
+            ),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |      X0 Y0
+  |   ___^
+  |   ______-
+4 | ||   X1 Y1
+5 | ||   X2 Y2
+  | ||____^ `X` is a good letter
+  | ||_______- `Y` is a good letter too
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn nested() {
+    let source = r#"
+fn foo() {
+  X0 Y0
+  Y1 X1
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..27).label("`X` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(17..24)
+                    .label("`Y` is a good letter too"),
+            ),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |      X0 Y0
+  |   ___^
+  |   ______-
+4 | ||   Y1 X1
+  | ||_______^ `X` is a good letter
+  | ||____- `Y` is a good letter too
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn different_overlap() {
+    let source = r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+  X2 Y2 Z2
+  X3 Y3 Z3
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(17..38).label("`X` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(31..49)
+                    .label("`Y` is a good letter too"),
+            ),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:6
+  |
+3 |      X0 Y0 Z0
+  |   ______^
+4 |  |   X1 Y1 Z1
+  |  |_________-
+5 | ||   X2 Y2 Z2
+  | ||____^ `X` is a good letter
+6 |  |   X3 Y3 Z3
+  |  |____- `Y` is a good letter too
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn triple_overlap() {
+    let source = r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+  X2 Y2 Z2
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..38).label("`X` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(17..41)
+                    .label("`Y` is a good letter too"),
+            )
+            .annotation(Level::Warning.span(20..44).label("`Z` label")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |       X0 Y0 Z0
+  |    ___^
+  |    ______-
+  |    _________-
+4 | |||   X1 Y1 Z1
+5 | |||   X2 Y2 Z2
+  | |||____^ `X` is a good letter
+  | |||_______- `Y` is a good letter too
+  | |||__________- `Z` label
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn triple_exact_overlap() {
+    let source = r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+  X2 Y2 Z2
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..38).label("`X` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(14..38)
+                    .label("`Y` is a good letter too"),
+            )
+            .annotation(Level::Warning.span(14..38).label("`Z` label")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |       X0 Y0 Z0
+  |    ___^
+  |    ___-
+  |    ___-
+4 | |||   X1 Y1 Z1
+5 | |||   X2 Y2 Z2
+  | |||____^ `X` is a good letter
+  | |||____- `Y` is a good letter too
+  | |||____- `Z` label
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn minimum_depth() {
+    let source = r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+  X2 Y2 Z2
+  X3 Y3 Z3
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(17..27).label("`X` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(28..44)
+                    .label("`Y` is a good letter too"),
+            )
+            .annotation(Level::Warning.span(36..52).label("`Z`")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:6
+  |
+3 |     X0 Y0 Z0
+  |  ______^
+4 | |   X1 Y1 Z1
+  | |____^ `X` is a good letter
+  | |______-
+5 | |   X2 Y2 Z2
+  | |__________- `Y` is a good letter too
+  | |___-
+6 | |   X3 Y3 Z3
+  | |_______- `Z`
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn non_overlapping() {
+    let source = r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+  X2 Y2 Z2
+  X3 Y3 Z3
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..27).label("`X` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(39..55)
+                    .label("`Y` is a good letter too"),
+            ),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |     X0 Y0 Z0
+  |  ___^
+4 | |   X1 Y1 Z1
+  | |____^ `X` is a good letter
+5 |     X2 Y2 Z2
+  |  ______-
+6 | |   X3 Y3 Z3
+  | |__________- `Y` is a good letter too
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn overlapping_start_and_end() {
+    let source = r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+  X2 Y2 Z2
+  X3 Y3 Z3
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(17..27).label("`X` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(31..55)
+                    .label("`Y` is a good letter too"),
+            ),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:6
+  |
+3 |     X0 Y0 Z0
+  |  ______^
+4 | |   X1 Y1 Z1
+  | |____^ `X` is a good letter
+  | |_________-
+5 | |   X2 Y2 Z2
+6 | |   X3 Y3 Z3
+  | |__________- `Y` is a good letter too
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn multiple_labels_primary_without_message() {
+    let source = r#"
+fn foo() {
+  a { b { c } d }
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(18..25).label(""))
+            .annotation(Level::Warning.span(14..27).label("`a` is a good letter"))
+            .annotation(Level::Warning.span(22..23).label("")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:7
+  |
+3 |   a { b { c } d }
+  |       ^^^^^^^
+  |   ------------- `a` is a good letter
+  |           -
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn multiple_labels_secondary_without_message() {
+    let source = r#"
+fn foo() {
+  a { b { c } d }
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..27).label("`a` is a good letter"))
+            .annotation(Level::Warning.span(18..25).label("")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |   a { b { c } d }
+  |   ^^^^^^^^^^^^^ `a` is a good letter
+  |       -------
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn multiple_labels_primary_without_message_2() {
+    let source = r#"
+fn foo() {
+  a { b { c } d }
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(18..25).label("`b` is a good letter"))
+            .annotation(Level::Warning.span(14..27).label(""))
+            .annotation(Level::Warning.span(22..23).label("")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:7
+  |
+3 |   a { b { c } d }
+  |       ^^^^^^^ `b` is a good letter
+  |   -------------
+  |           -
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn multiple_labels_secondary_without_message_2() {
+    let source = r#"
+fn foo() {
+  a { b { c } d }
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..27).label(""))
+            .annotation(Level::Warning.span(18..25).label("`b` is a good letter")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |   a { b { c } d }
+  |   ^^^^^^^^^^^^^
+  |       ------- `b` is a good letter
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn multiple_labels_secondary_without_message_3() {
+    let source = r#"
+fn foo() {
+  a  bc  d
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..18).label("`a` is a good letter"))
+            .annotation(Level::Warning.span(18..22).label("")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |   a  bc  d
+  |   ^^^^ `a` is a good letter
+  |       ----
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn multiple_labels_without_message() {
+    let source = r#"
+fn foo() {
+  a { b { c } d }
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..27).label(""))
+            .annotation(Level::Warning.span(18..25).label("")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |   a { b { c } d }
+  |   ^^^^^^^^^^^^^
+  |       -------
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn multiple_labels_without_message_2() {
+    let source = r#"
+fn foo() {
+  a { b { c } d }
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(18..25).label(""))
+            .annotation(Level::Warning.span(14..27).label(""))
+            .annotation(Level::Warning.span(22..23).label("")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:7
+  |
+3 |   a { b { c } d }
+  |       ^^^^^^^
+  |   -------------
+  |           -
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn multiple_labels_with_message() {
+    let source = r#"
+fn foo() {
+  a { b { c } d }
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..27).label("`a` is a good letter"))
+            .annotation(Level::Warning.span(18..25).label("`b` is a good letter")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |   a { b { c } d }
+  |   ^^^^^^^^^^^^^ `a` is a good letter
+  |       ------- `b` is a good letter
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn ingle_label_with_message() {
+    let source = r#"
+fn foo() {
+  a { b { c } d }
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..27).label("`a` is a good letter")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |   a { b { c } d }
+  |   ^^^^^^^^^^^^^ `a` is a good letter
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn single_label_without_message() {
+    let source = r#"
+fn foo() {
+  a { b { c } d }
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(14..27).label("")),
+    );
+
+    let expected = str![[r#"
+error: foo
+ --> test.rs:3:3
+  |
+3 |   a { b { c } d }
+  |   ^^^^^^^^^^^^^
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn long_snippet() {
+    let source = r#"
+fn foo() {
+  X0 Y0 Z0
+  X1 Y1 Z1
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+  X2 Y2 Z2
+  X3 Y3 Z3
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(17..27).label("`X` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(31..76)
+                    .label("`Y` is a good letter too"),
+            ),
+    );
+
+    let expected = str![[r#"
+error: foo
+  --> test.rs:3:6
+   |
+ 3 |     X0 Y0 Z0
+   |  ______^
+ 4 | |   X1 Y1 Z1
+   | |____^ `X` is a good letter
+   | |_________-
+ 5 | | 1
+...  |
+15 | |   X2 Y2 Z2
+16 | |   X3 Y3 Z3
+   | |__________- `Y` is a good letter too
+   |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+#[test]
+fn long_snippet_multiple_spans() {
+    let source = r#"
+fn foo() {
+  X0 Y0 Z0
+1
+2
+3
+  X1 Y1 Z1
+4
+5
+6
+  X2 Y2 Z2
+7
+8
+9
+10
+  X3 Y3 Z3
+}
+"#;
+    let input = Level::Error.title("foo").snippet(
+        Snippet::source(source)
+            .line_start(1)
+            .origin("test.rs")
+            .fold(true)
+            .annotation(Level::Error.span(17..73).label("`Y` is a good letter"))
+            .annotation(
+                Level::Warning
+                    .span(37..56)
+                    .label("`Z` is a good letter too"),
+            ),
+    );
+
+    let expected = str![[r#"
+error: foo
+  --> test.rs:3:6
+   |
+ 3 |      X0 Y0 Z0
+   |   ______^
+ 4 |  | 1
+ 5 |  | 2
+ 6 |  | 3
+ 7 |  |   X1 Y1 Z1
+   |  |_________-
+ 8 | || 4
+ 9 | || 5
+10 | || 6
+11 | ||   X2 Y2 Z2
+   | ||__________- `Z` is a good letter too
+12 |  | 7
+...   |
+15 |  | 10
+16 |  |   X3 Y3 Z3
+   |  |_______^ `Y` is a good letter
+   |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}

From 9ec2939584d9d4ad6c601020d5c68ca08bfceb66 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 19 Jun 2024 21:02:23 -0600
Subject: [PATCH 210/302] fix: Properly handle multiple annotations on one line

---
 examples/expected_type.svg                    |   8 +-
 examples/footer.svg                           |   8 +-
 examples/format.svg                           |  12 +-
 examples/multislice.svg                       |   8 +-
 src/renderer/display_list.rs                  | 768 ++++++++++++------
 src/renderer/mod.rs                           |   1 +
 src/renderer/styled_buffer.rs                 |  97 +++
 tests/fixtures/no-color/simple.svg            |   2 +-
 tests/fixtures/no-color/strip_line_non_ws.svg |  10 +-
 tests/formatter.rs                            |  46 +-
 tests/rustc_tests.rs                          | 177 ++--
 11 files changed, 750 insertions(+), 387 deletions(-)
 create mode 100644 src/renderer/styled_buffer.rs

diff --git a/examples/expected_type.svg b/examples/expected_type.svg
index a355dbc..ed19ef3 100644
--- a/examples/expected_type.svg
+++ b/examples/expected_type.svg
@@ -23,11 +23,11 @@
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> examples/footer.rs:29:25</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">   |</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan>                 annotations: vec![SourceAnnotation {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-blue bold">                                   ----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info</tspan><tspan class="fg-bright-blue bold">: while parsing this struct</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info: while parsing this struct</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan>                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan>
 </tspan>
@@ -35,9 +35,9 @@
 </tspan>
     <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-red bold">                         ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
+    <tspan x="10px" y="172px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                         </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">   |</tspan>
+    <tspan x="10px" y="190px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="208px">
 </tspan>
diff --git a/examples/footer.svg b/examples/footer.svg
index 34f81c8..76e7d77 100644
--- a/examples/footer.svg
+++ b/examples/footer.svg
@@ -24,15 +24,15 @@
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/multislice.rs:13:22</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">   |</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13 |</tspan><tspan>         slices: vec!["A",</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-red bold">                      ^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                      </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">   |</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">=</tspan><tspan> </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
+    <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan>              found type: `__&amp;__snippet::Annotation`</tspan>
 </tspan>
diff --git a/examples/format.svg b/examples/format.svg
index dd6c1c0..a427c94 100644
--- a/examples/format.svg
+++ b/examples/format.svg
@@ -24,15 +24,15 @@
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs:51:6</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">   |</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan>   ) -&gt; Option&lt;String&gt; {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">   |</tspan><tspan>  </tspan><tspan class="fg-yellow bold">      --------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option&lt;String&gt;` because of return type</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>        </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option&lt;String&gt;` because of return type</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan>       for ann in annotations {</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">   |</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">_____^</tspan>
+    <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">_____^</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
 </tspan>
@@ -52,7 +52,7 @@
 </tspan>
     <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 };</tspan>
 </tspan>
-    <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> </tspan>
+    <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan>
 </tspan>
     <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 return Some(format!(</tspan>
 </tspan>
@@ -74,9 +74,9 @@
 </tspan>
     <tspan x="10px" y="496px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
 </tspan>
-    <tspan x="10px" y="514px"><tspan class="fg-bright-blue bold">   |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan class="fg-bright-red bold">____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan>
+    <tspan x="10px" y="514px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan>
 </tspan>
-    <tspan x="10px" y="532px"><tspan class="fg-bright-blue bold">   |</tspan>
+    <tspan x="10px" y="532px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="550px">
 </tspan>
diff --git a/examples/multislice.svg b/examples/multislice.svg
index 2ab959a..216a359 100644
--- a/examples/multislice.svg
+++ b/examples/multislice.svg
@@ -23,19 +23,19 @@
 </tspan>
     <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">    |</tspan>
+    <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51 |</tspan><tspan> Foo</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">    |</tspan>
+    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">    |</tspan>
+    <tspan x="10px" y="136px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">    |</tspan>
+    <tspan x="10px" y="172px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="190px">
 </tspan>
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index bc46f7f..2c8dbf9 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -32,11 +32,13 @@
 //!
 //! 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::cmp::{max, min, Reverse};
+use std::collections::HashMap;
+use std::fmt::Display;
 use std::ops::Range;
 use std::{cmp, fmt};
 
+use crate::renderer::styled_buffer::StyledBuffer;
 use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH};
 
 const ANONYMIZED_LINE_NUM: &str = "LL";
@@ -83,19 +85,31 @@ impl<'a> Display for DisplayList<'a> {
         } else {
             ((lineno_width as f64).log10().floor() as usize) + 1
         };
-        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,
+
+        let multiline_depth = self.body.iter().fold(0, |max, set| {
+            set.display_lines.iter().fold(max, |max2, line| match line {
+                DisplayLine::Source { annotations, .. } => cmp::max(
+                    annotations.iter().fold(max2, |max3, line| {
+                        cmp::max(
+                            match line.annotation_part {
+                                DisplayAnnotationPart::Standalone => 0,
+                                DisplayAnnotationPart::LabelContinuation => 0,
+                                DisplayAnnotationPart::MultilineStart(depth) => depth + 1,
+                                DisplayAnnotationPart::MultilineEnd(depth) => depth + 1,
+                            },
+                            max3,
+                        )
+                    }),
+                    max,
+                ),
+                _ => max2,
             })
         });
-
-        let mut count_offset = 0;
+        let mut buffer = StyledBuffer::new();
         for set in self.body.iter() {
-            self.format_set(set, lineno_width, inline_marks_width, count_offset, f)?;
-            count_offset += set.display_lines.len();
+            self.format_set(set, lineno_width, multiline_depth, &mut buffer)?;
         }
-        Ok(())
+        write!(f, "{}", buffer.render(self.stylesheet)?)
     }
 }
 
@@ -119,27 +133,18 @@ impl<'a> DisplayList<'a> {
         &self,
         set: &DisplaySet<'_>,
         lineno_width: usize,
-        inline_marks_width: usize,
-        count_offset: usize,
-        f: &mut fmt::Formatter<'_>,
+        multiline_depth: usize,
+        buffer: &mut StyledBuffer,
     ) -> fmt::Result {
-        let body_len = self
-            .body
-            .iter()
-            .map(|set| set.display_lines.len())
-            .sum::<usize>();
-        for (i, line) in set.display_lines.iter().enumerate() {
+        for line in &set.display_lines {
             set.format_line(
                 line,
                 lineno_width,
-                inline_marks_width,
+                multiline_depth,
                 self.stylesheet,
                 self.anonymized_line_numbers,
-                f,
+                buffer,
             )?;
-            if i + count_offset + 1 < body_len {
-                f.write_char('\n')?;
-            }
         }
         Ok(())
     }
@@ -154,36 +159,27 @@ pub(crate) struct DisplaySet<'a> {
 impl<'a> DisplaySet<'a> {
     fn format_label(
         &self,
+        line_offset: usize,
         label: &[DisplayTextFragment<'_>],
         stylesheet: &Stylesheet,
-        f: &mut fmt::Formatter<'_>,
+        buffer: &mut StyledBuffer,
     ) -> fmt::Result {
-        let emphasis_style = stylesheet.emphasis();
-
         for fragment in label {
-            match fragment.style {
-                DisplayTextStyle::Regular => fragment.content.fmt(f)?,
-                DisplayTextStyle::Emphasis => {
-                    write!(
-                        f,
-                        "{}{}{}",
-                        emphasis_style.render(),
-                        fragment.content,
-                        emphasis_style.render_reset()
-                    )?;
-                }
-            }
+            let style = match fragment.style {
+                DisplayTextStyle::Regular => stylesheet.none(),
+                DisplayTextStyle::Emphasis => stylesheet.emphasis(),
+            };
+            buffer.append(line_offset, fragment.content, *style);
         }
         Ok(())
     }
-
     fn format_annotation(
         &self,
+        line_offset: usize,
         annotation: &Annotation<'_>,
         continuation: bool,
-        in_source: bool,
         stylesheet: &Stylesheet,
-        f: &mut fmt::Formatter<'_>,
+        buffer: &mut StyledBuffer,
     ) -> fmt::Result {
         let color = get_annotation_style(&annotation.annotation_type, stylesheet);
         let formatted_len = if let Some(id) = &annotation.id {
@@ -193,31 +189,27 @@ impl<'a> DisplaySet<'a> {
         };
 
         if continuation {
-            format_repeat_char(' ', formatted_len + 2, f)?;
-            return self.format_label(&annotation.label, stylesheet, f);
+            for _ in 0..formatted_len + 2 {
+                buffer.append(line_offset, " ", Style::new());
+            }
+            return self.format_label(line_offset, &annotation.label, stylesheet, buffer);
         }
         if formatted_len == 0 {
-            self.format_label(&annotation.label, stylesheet, f)
+            self.format_label(line_offset, &annotation.label, stylesheet, buffer)
         } else {
-            write!(f, "{}", color.render())?;
-            format_annotation_type(&annotation.annotation_type, f)?;
-            if let Some(id) = &annotation.id {
-                f.write_char('[')?;
-                f.write_str(id)?;
-                f.write_char(']')?;
-            }
-            write!(f, "{}", color.render_reset())?;
+            let id = match &annotation.id {
+                Some(id) => format!("[{}]", id),
+                None => String::new(),
+            };
+            buffer.append(
+                line_offset,
+                &format!("{}{}", annotation_type_str(&annotation.annotation_type), id),
+                *color,
+            );
 
             if !is_annotation_empty(annotation) {
-                if in_source {
-                    write!(f, "{}", color.render())?;
-                    f.write_str(": ")?;
-                    self.format_label(&annotation.label, stylesheet, f)?;
-                    write!(f, "{}", color.render_reset())?;
-                } else {
-                    f.write_str(": ")?;
-                    self.format_label(&annotation.label, stylesheet, f)?;
-                }
+                buffer.append(line_offset, ": ", stylesheet.none);
+                self.format_label(line_offset, &annotation.label, stylesheet, buffer)?;
             }
             Ok(())
         }
@@ -226,10 +218,11 @@ impl<'a> DisplaySet<'a> {
     #[inline]
     fn format_raw_line(
         &self,
+        line_offset: usize,
         line: &DisplayRawLine<'_>,
         lineno_width: usize,
         stylesheet: &Stylesheet,
-        f: &mut fmt::Formatter<'_>,
+        buffer: &mut StyledBuffer,
     ) -> fmt::Result {
         match line {
             DisplayRawLine::Origin {
@@ -242,34 +235,15 @@ impl<'a> DisplaySet<'a> {
                     DisplayHeaderType::Continuation => ":::",
                 };
                 let lineno_color = stylesheet.line_no();
-
+                buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color);
+                buffer.puts(line_offset, lineno_width + 4, path, stylesheet.none);
                 if let Some((col, row)) = pos {
-                    format_repeat_char(' ', lineno_width, f)?;
-                    write!(
-                        f,
-                        "{}{}{}",
-                        lineno_color.render(),
-                        header_sigil,
-                        lineno_color.render_reset()
-                    )?;
-                    f.write_char(' ')?;
-                    path.fmt(f)?;
-                    f.write_char(':')?;
-                    col.fmt(f)?;
-                    f.write_char(':')?;
-                    row.fmt(f)
-                } else {
-                    format_repeat_char(' ', lineno_width, f)?;
-                    write!(
-                        f,
-                        "{}{}{}",
-                        lineno_color.render(),
-                        header_sigil,
-                        lineno_color.render_reset()
-                    )?;
-                    f.write_char(' ')?;
-                    path.fmt(f)
+                    buffer.append(line_offset, ":", stylesheet.none);
+                    buffer.append(line_offset, col.to_string().as_str(), stylesheet.none);
+                    buffer.append(line_offset, ":", stylesheet.none);
+                    buffer.append(line_offset, row.to_string().as_str(), stylesheet.none);
                 }
+                Ok(())
             }
             DisplayRawLine::Annotation {
                 annotation,
@@ -278,35 +252,35 @@ impl<'a> DisplaySet<'a> {
             } => {
                 if *source_aligned {
                     if *continuation {
-                        format_repeat_char(' ', lineno_width + 3, f)?;
+                        for _ in 0..lineno_width + 3 {
+                            buffer.append(line_offset, " ", stylesheet.none);
+                        }
                     } else {
                         let lineno_color = stylesheet.line_no();
-                        format_repeat_char(' ', lineno_width, f)?;
-                        f.write_char(' ')?;
-                        write!(
-                            f,
-                            "{}={}",
-                            lineno_color.render(),
-                            lineno_color.render_reset()
-                        )?;
-                        f.write_char(' ')?;
+                        for _ in 0..lineno_width + 1 {
+                            buffer.append(line_offset, " ", stylesheet.none);
+                        }
+                        buffer.append(line_offset, "=", *lineno_color);
+                        buffer.append(line_offset, " ", *lineno_color);
                     }
                 }
-                self.format_annotation(annotation, *continuation, false, stylesheet, f)
+                self.format_annotation(line_offset, annotation, *continuation, stylesheet, buffer)
             }
         }
     }
 
+    // Adapted from https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/emitter.rs#L706-L1155
     #[inline]
     fn format_line(
         &self,
         dl: &DisplayLine<'_>,
         lineno_width: usize,
-        inline_marks_width: usize,
+        multiline_depth: usize,
         stylesheet: &Stylesheet,
         anonymized_line_numbers: bool,
-        f: &mut fmt::Formatter<'_>,
+        buffer: &mut StyledBuffer,
     ) -> fmt::Result {
+        let line_offset = buffer.num_lines();
         match dl {
             DisplayLine::Source {
                 lineno,
@@ -316,36 +290,45 @@ impl<'a> DisplaySet<'a> {
             } => {
                 let lineno_color = stylesheet.line_no();
                 if anonymized_line_numbers && lineno.is_some() {
-                    write!(f, "{}", lineno_color.render())?;
-                    f.write_str(ANONYMIZED_LINE_NUM)?;
-                    f.write_str(" |")?;
-                    write!(f, "{}", lineno_color.render_reset())?;
+                    let num = format!("{:>width$} |", ANONYMIZED_LINE_NUM, width = lineno_width);
+                    buffer.puts(line_offset, 0, &num, *lineno_color);
                 } else {
-                    write!(f, "{}", lineno_color.render())?;
                     match lineno {
-                        Some(n) => write!(f, "{:>width$}", n, width = lineno_width),
-                        None => format_repeat_char(' ', lineno_width, f),
-                    }?;
-                    f.write_str(" |")?;
-                    write!(f, "{}", lineno_color.render_reset())?;
+                        Some(n) => {
+                            let num = format!("{:>width$} |", n, width = lineno_width);
+                            buffer.puts(line_offset, 0, &num, *lineno_color);
+                        }
+                        None => {
+                            buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color);
+                        }
+                    };
                 }
-
                 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, stylesheet, f)?;
+                    // The width of the line number, a space, pipe, and a space
+                    // `123 | ` is `lineno_width + 3`.
+                    let width_offset = lineno_width + 3;
+                    let code_offset = if multiline_depth == 0 {
+                        width_offset
+                    } else {
+                        width_offset + multiline_depth + 1
+                    };
+
+                    // Add any inline marks to the code line
+                    if !inline_marks.is_empty() || 0 < multiline_depth {
+                        format_inline_marks(
+                            line_offset,
+                            inline_marks,
+                            lineno_width,
+                            stylesheet,
+                            buffer,
+                        )?;
                     }
-                    f.write_char(' ')?;
 
                     let text = normalize_whitespace(text);
                     let line_len = text.as_bytes().len();
-                    let mut left = self.margin.left(line_len);
+                    let 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
@@ -364,135 +347,341 @@ impl<'a> DisplaySet<'a> {
                             true
                         })
                         .collect();
-
+                    buffer.puts(line_offset, code_offset, &code, Style::new());
+                    if self.margin.was_cut_left() {
+                        // We have stripped some code/whitespace from the beginning, make it clear.
+                        buffer.puts(line_offset, code_offset, "...", *lineno_color);
+                    }
                     if self.margin.was_cut_right(line_len) {
-                        code[..taken.saturating_sub(3)].fmt(f)?;
-                        "...".fmt(f)?;
-                    } else {
-                        code.fmt(f)?;
+                        buffer.puts(line_offset, code_offset + taken - 3, "...", *lineno_color);
                     }
 
-                    let mut left: usize = text
+                    let 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);
+                    let mut annotations = annotations.clone();
+                    annotations.sort_by_key(|a| Reverse(a.range.0));
+
+                    let mut annotations_positions = vec![];
+                    let mut line_len = 0;
+                    let mut p = 0;
+                    for (i, annotation) in annotations.iter().enumerate() {
+                        for (j, next) in annotations.iter().enumerate() {
+                            // This label overlaps with another one and both take space (
+                            // they have text and are not multiline lines).
+                            if overlaps(next, annotation, 0)
+                                && annotation.has_label()
+                                && j > i
+                                && p == 0
+                            // We're currently on the first line, move the label one line down
+                            {
+                                // If we're overlapping with an un-labelled annotation with the same span
+                                // we can just merge them in the output
+                                if next.range.0 == annotation.range.0
+                                    && next.range.1 == annotation.range.1
+                                    && !next.has_label()
+                                {
+                                    continue;
+                                }
+
+                                // This annotation needs a new line in the output.
+                                p += 1;
+                                break;
+                            }
+                        }
+                        annotations_positions.push((p, annotation));
+                        for (j, next) in annotations.iter().enumerate() {
+                            if j > i {
+                                let l = next
+                                    .annotation
+                                    .label
+                                    .iter()
+                                    .map(|label| label.content)
+                                    .collect::<Vec<_>>()
+                                    .join("")
+                                    .len()
+                                    + 2;
+                                // Do not allow two labels to be in the same line if they
+                                // overlap including padding, to avoid situations like:
+                                //
+                                // fn foo(x: u32) {
+                                // -------^------
+                                // |      |
+                                // fn_spanx_span
+                                //
+                                // Both labels must have some text, otherwise they are not
+                                // overlapping. Do not add a new line if this annotation or
+                                // the next are vertical line placeholders. If either this
+                                // or the next annotation is multiline start/end, move it
+                                // to a new line so as not to overlap the horizontal lines.
+                                if (overlaps(next, annotation, l)
+                                    && annotation.has_label()
+                                    && next.has_label())
+                                    || (annotation.takes_space() && next.has_label())
+                                    || (annotation.has_label() && next.takes_space())
+                                    || (annotation.takes_space() && next.takes_space())
+                                    || (overlaps(next, annotation, l)
+                                        && next.range.1 <= annotation.range.1
+                                        && next.has_label()
+                                        && p == 0)
+                                // Avoid #42595.
+                                {
+                                    // This annotation needs a new line in the output.
+                                    p += 1;
+                                    break;
+                                }
+                            }
+                        }
+                        line_len = max(line_len, p);
                     }
 
-                    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,
-                            )?;
+                    if line_len != 0 {
+                        line_len += 1;
+                    }
+
+                    // Draw the column separator for any extra lines that were
+                    // created
+                    //
+                    // After this we will have:
+                    //
+                    // 2 |   fn foo() {
+                    //   |
+                    //   |
+                    //   |
+                    // 3 |
+                    // 4 |   }
+                    //   |
+                    if !annotations_positions.is_empty() {
+                        for pos in 0..=line_len {
+                            buffer.putc(
+                                line_offset + pos + 1,
+                                lineno_width + 1,
+                                '|',
+                                stylesheet.line_no,
+                            );
+                        }
+                    }
+
+                    // Write the horizontal lines for multiline annotations
+                    // (only the first and last lines need this).
+                    //
+                    // After this we will have:
+                    //
+                    // 2 |   fn foo() {
+                    //   |  __________
+                    //   |
+                    //   |
+                    // 3 |
+                    // 4 |   }
+                    //   |  _
+                    for &(pos, annotation) in &annotations_positions {
+                        let style = get_annotation_style(&annotation.annotation_type, stylesheet);
+                        let pos = pos + 1;
+                        match annotation.annotation_part {
+                            DisplayAnnotationPart::MultilineStart(depth)
+                            | DisplayAnnotationPart::MultilineEnd(depth) => {
+                                for col in width_offset + depth
+                                    ..(code_offset + annotation.range.0).saturating_sub(left)
+                                {
+                                    buffer.putc(line_offset + pos, col + 1, '_', *style);
+                                }
+                            }
+                            _ => {}
+                        }
+                    }
+
+                    // Write the vertical lines for labels that are on a different line as the underline.
+                    //
+                    // After this we will have:
+                    //
+                    // 2 |   fn foo() {
+                    //   |  __________
+                    //   | |    |
+                    //   | |
+                    // 3 | |
+                    // 4 | | }
+                    //   | |_
+                    for &(pos, annotation) in &annotations_positions {
+                        let style = get_annotation_style(&annotation.annotation_type, stylesheet);
+                        let pos = pos + 1;
+                        if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
+                            for p in line_offset + 2..=line_offset + pos {
+                                buffer.putc(
+                                    p,
+                                    (code_offset + annotation.range.0).saturating_sub(left),
+                                    '|',
+                                    *style,
+                                );
+                            }
+                        }
+                        match annotation.annotation_part {
+                            DisplayAnnotationPart::MultilineStart(depth) => {
+                                for p in line_offset + pos + 1..line_offset + line_len + 2 {
+                                    buffer.putc(p, width_offset + depth, '|', *style);
+                                }
+                            }
+                            DisplayAnnotationPart::MultilineEnd(depth) => {
+                                for p in line_offset..=line_offset + pos {
+                                    buffer.putc(p, width_offset + depth, '|', *style);
+                                }
+                            }
+                            _ => {}
+                        }
+                    }
+
+                    // Add in any inline marks for any extra lines that have
+                    // been created. Output should look like above.
+                    for inline_mark in inline_marks {
+                        if let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type {
+                            let style =
+                                get_annotation_style(&inline_mark.annotation_type, stylesheet);
+                            if annotations_positions.is_empty() {
+                                buffer.putc(line_offset, width_offset + depth, '|', *style);
+                            } else {
+                                for p in line_offset..=line_offset + line_len + 1 {
+                                    buffer.putc(p, width_offset + depth, '|', *style);
+                                }
+                            }
+                        }
+                    }
+
+                    // Write the labels on the annotations that actually have a label.
+                    //
+                    // After this we will have:
+                    //
+                    // 2 |   fn foo() {
+                    //   |  __________
+                    //   |      |
+                    //   |      something about `foo`
+                    // 3 |
+                    // 4 |   }
+                    //   |  _  test
+                    for &(pos, annotation) in &annotations_positions {
+                        if !is_annotation_empty(&annotation.annotation) {
+                            let style =
+                                get_annotation_style(&annotation.annotation_type, stylesheet);
+                            let mut formatted_len = if let Some(id) = &annotation.annotation.id {
+                                2 + id.len()
+                                    + annotation_type_len(&annotation.annotation.annotation_type)
+                            } else {
+                                annotation_type_len(&annotation.annotation.annotation_type)
+                            };
+                            let (pos, col) = if pos == 0 {
+                                (pos + 1, (annotation.range.1 + 1).saturating_sub(left))
+                            } else {
+                                (pos + 2, annotation.range.0.saturating_sub(left))
+                            };
+                            if annotation.annotation_part
+                                == DisplayAnnotationPart::LabelContinuation
+                            {
+                                formatted_len = 0;
+                            } else if formatted_len != 0 {
+                                formatted_len += 2;
+                                let id = match &annotation.annotation.id {
+                                    Some(id) => format!("[{}]", id),
+                                    None => String::new(),
+                                };
+                                buffer.puts(
+                                    line_offset + pos,
+                                    col + code_offset,
+                                    &format!(
+                                        "{}{}: ",
+                                        annotation_type_str(&annotation.annotation_type),
+                                        id
+                                    ),
+                                    *style,
+                                );
+                            } else {
+                                formatted_len = 0;
+                            }
+                            let mut before = 0;
+                            for fragment in &annotation.annotation.label {
+                                let inner_col = before + formatted_len + col + code_offset;
+                                buffer.puts(line_offset + pos, inner_col, fragment.content, *style);
+                                before += fragment.content.len();
+                            }
+                        }
+                    }
+
+                    // Sort from biggest span to smallest span so that smaller spans are
+                    // represented in the output:
+                    //
+                    // x | fn foo()
+                    //   | ^^^---^^
+                    //   | |  |
+                    //   | |  something about `foo`
+                    //   | something about `fn foo()`
+                    annotations_positions.sort_by_key(|(_, ann)| {
+                        // Decreasing order. When annotations share the same length, prefer `Primary`.
+                        Reverse(ann.len())
+                    });
+
+                    // Write the underlines.
+                    //
+                    // After this we will have:
+                    //
+                    // 2 |   fn foo() {
+                    //   |  ____-_____^
+                    //   |      |
+                    //   |      something about `foo`
+                    // 3 |
+                    // 4 |   }
+                    //   |  _^  test
+                    for &(_, annotation) in &annotations_positions {
+                        let mark = match annotation.annotation_type {
+                            DisplayAnnotationType::Error => '^',
+                            DisplayAnnotationType::Warning => '-',
+                            DisplayAnnotationType::Info => '-',
+                            DisplayAnnotationType::Note => '-',
+                            DisplayAnnotationType::Help => '-',
+                            DisplayAnnotationType::None => ' ',
+                        };
+                        let style = get_annotation_style(&annotation.annotation_type, stylesheet);
+                        for p in annotation.range.0..annotation.range.1 {
+                            buffer.putc(
+                                line_offset + 1,
+                                (code_offset + p).saturating_sub(left),
+                                mark,
+                                *style,
+                            );
                         }
-                        self.format_source_annotation(annotation, left, stylesheet, f)?;
                     }
                 } else if !inline_marks.is_empty() {
-                    f.write_char(' ')?;
-                    self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?;
+                    format_inline_marks(
+                        line_offset,
+                        inline_marks,
+                        lineno_width,
+                        stylesheet,
+                        buffer,
+                    )?;
                 }
                 Ok(())
             }
             DisplayLine::Fold { inline_marks } => {
-                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, stylesheet, f)?;
+                buffer.puts(line_offset, 0, "...", *stylesheet.line_no());
+                if !inline_marks.is_empty() || 0 < multiline_depth {
+                    format_inline_marks(
+                        line_offset,
+                        inline_marks,
+                        lineno_width,
+                        stylesheet,
+                        buffer,
+                    )?;
                 }
                 Ok(())
             }
-            DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, stylesheet, f),
-        }
-    }
-
-    fn format_inline_marks(
-        &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 = get_annotation_style(&mark.annotation_type, stylesheet);
-            write!(f, "{}", annotation_style.render())?;
-            f.write_char(match mark.mark_type {
-                DisplayMarkType::AnnotationThrough => '|',
-                DisplayMarkType::AnnotationStart => '/',
-            })?;
-            write!(f, "{}", annotation_style.render_reset())?;
-        }
-        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())?;
+            DisplayLine::Raw(line) => {
+                self.format_raw_line(line_offset, line, lineno_width, stylesheet, buffer)
+            }
         }
-        Ok(())
     }
 }
 
 /// Inline annotation which can be used in either Raw or Source line.
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub(crate) struct Annotation<'a> {
     pub(crate) annotation_type: DisplayAnnotationType,
     pub(crate) id: Option<&'a str>,
@@ -530,7 +719,7 @@ pub(crate) enum DisplaySourceLine<'a> {
     Empty,
 }
 
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub(crate) struct DisplaySourceAnnotation<'a> {
     pub(crate) annotation: Annotation<'a>,
     pub(crate) range: (usize, usize),
@@ -538,6 +727,34 @@ pub(crate) struct DisplaySourceAnnotation<'a> {
     pub(crate) annotation_part: DisplayAnnotationPart,
 }
 
+impl<'a> DisplaySourceAnnotation<'a> {
+    fn has_label(&self) -> bool {
+        !self
+            .annotation
+            .label
+            .iter()
+            .all(|label| label.content.is_empty())
+    }
+
+    // Length of this annotation as displayed in the stderr output
+    fn len(&self) -> usize {
+        // Account for usize underflows
+        if self.range.1 > self.range.0 {
+            self.range.1 - self.range.0
+        } else {
+            self.range.0 - self.range.1
+        }
+    }
+
+    fn takes_space(&self) -> bool {
+        // Multiline annotations always have to keep vertical space.
+        matches!(
+            self.annotation_part,
+            DisplayAnnotationPart::MultilineStart(_) | DisplayAnnotationPart::MultilineEnd(_)
+        )
+    }
+}
+
 /// Raw line - a line which does not have the `lineno` part and is not considered
 /// a part of the snippet.
 #[derive(Debug, PartialEq)]
@@ -566,7 +783,7 @@ pub(crate) enum DisplayRawLine<'a> {
 }
 
 /// An inline text fragment which any label is composed of.
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub(crate) struct DisplayTextFragment<'a> {
     pub(crate) content: &'a str,
     pub(crate) style: DisplayTextStyle,
@@ -589,9 +806,9 @@ pub(crate) enum DisplayAnnotationPart {
     /// A continuation of a multi-line label of an annotation.
     LabelContinuation,
     /// A line starting a multiline annotation.
-    MultilineStart,
+    MultilineStart(usize),
     /// A line ending a multiline annotation.
-    MultilineEnd,
+    MultilineEnd(usize),
 }
 
 /// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
@@ -605,7 +822,7 @@ pub(crate) struct DisplayMark {
 #[derive(Debug, Clone, PartialEq)]
 pub(crate) enum DisplayMarkType {
     /// A mark indicating a multiline annotation going through the current line.
-    AnnotationThrough,
+    AnnotationThrough(usize),
     /// A mark indicating a multiline annotation starting on the given line.
     AnnotationStart,
 }
@@ -969,10 +1186,7 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
                                         ref inline_marks, ..
                                     } = line
                                     {
-                                        let mut inline_marks = inline_marks.clone();
-                                        for mark in &mut inline_marks {
-                                            mark.mark_type = DisplayMarkType::AnnotationThrough;
-                                        }
+                                        let inline_marks = inline_marks.clone();
                                         Some(inline_marks)
                                     } else {
                                         None
@@ -1031,7 +1245,12 @@ fn format_body(
     let mut label_right_margin = 0;
     let mut max_line_len = 0;
 
+    let mut depth_map: HashMap<usize, usize> = HashMap::new();
+    let mut current_depth = 0;
     let mut annotations = snippet.annotations;
+    annotations.sort_by_key(|a| a.range.start);
+    let mut annotations = annotations.into_iter().enumerate().collect::<Vec<_>>();
+
     for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() {
         let line_length: usize = line.len();
         let line_range = (current_index, current_index + line_length);
@@ -1069,7 +1288,7 @@ fn format_body(
         current_index += line_length + end_line_size;
 
         // It would be nice to use filter_drain here once it's stable.
-        annotations.retain(|annotation| {
+        annotations.retain(|(key, annotation)| {
             let body_idx = idx;
             let annotation_type = match annotation.level {
                 snippet::Level::Error => DisplayAnnotationType::None,
@@ -1175,8 +1394,10 @@ fn format_body(
                             },
                             range,
                             annotation_type: DisplayAnnotationType::from(annotation.level),
-                            annotation_part: DisplayAnnotationPart::MultilineStart,
+                            annotation_part: DisplayAnnotationPart::MultilineStart(current_depth),
                         });
+                        depth_map.insert(*key, current_depth);
+                        current_depth += 1;
                     }
                     true
                 }
@@ -1190,8 +1411,9 @@ fn format_body(
                         ..
                     } = body[body_idx]
                     {
+                        let depth = depth_map.get(key).cloned().unwrap_or_default();
                         inline_marks.push(DisplayMark {
-                            mark_type: DisplayMarkType::AnnotationThrough,
+                            mark_type: DisplayMarkType::AnnotationThrough(depth),
                             annotation_type: DisplayAnnotationType::from(annotation.level),
                         });
                     }
@@ -1207,15 +1429,10 @@ fn format_body(
                         && end <= line_end_index + max(end_line_size, 1) =>
                 {
                     if let DisplayLine::Source {
-                        ref mut inline_marks,
                         ref mut annotations,
                         ..
                     } = body[body_idx]
                     {
-                        inline_marks.push(DisplayMark {
-                            mark_type: DisplayMarkType::AnnotationThrough,
-                            annotation_type: DisplayAnnotationType::from(annotation.level),
-                        });
                         let end_mark = line[0..(end - line_start_index).min(line_length)]
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
@@ -1237,6 +1454,7 @@ fn format_body(
                         label_right_margin = max(label_right_margin, end_plus_one + label_right);
 
                         let range = (end_mark, end_plus_one);
+                        let depth = depth_map.remove(key).unwrap_or(0);
                         annotations.push(DisplaySourceAnnotation {
                             annotation: Annotation {
                                 annotation_type,
@@ -1245,7 +1463,7 @@ fn format_body(
                             },
                             range,
                             annotation_type: DisplayAnnotationType::from(annotation.level),
-                            annotation_part: DisplayAnnotationPart::MultilineEnd,
+                            annotation_part: DisplayAnnotationPart::MultilineEnd(depth),
                         });
                     }
                     false
@@ -1253,6 +1471,12 @@ fn format_body(
                 _ => true,
             }
         });
+        // Reset the depth counter, but only after we've processed all
+        // annotations for a given line.
+        let max = depth_map.len();
+        if current_depth > max {
+            current_depth = max;
+        }
     }
 
     if snippet.fold {
@@ -1313,25 +1537,15 @@ fn format_body(
     }
 }
 
-fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-    for _ in 0..n {
-        f.write_char(c)?;
-    }
-    Ok(())
-}
-
 #[inline]
-fn format_annotation_type(
-    annotation_type: &DisplayAnnotationType,
-    f: &mut fmt::Formatter<'_>,
-) -> fmt::Result {
+fn annotation_type_str(annotation_type: &DisplayAnnotationType) -> &'static str {
     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(()),
+        DisplayAnnotationType::Error => ERROR_TXT,
+        DisplayAnnotationType::Help => HELP_TXT,
+        DisplayAnnotationType::Info => INFO_TXT,
+        DisplayAnnotationType::Note => NOTE_TXT,
+        DisplayAnnotationType::Warning => WARNING_TXT,
+        DisplayAnnotationType::None => "",
     }
 }
 
@@ -1390,3 +1604,33 @@ fn normalize_whitespace(str: &str) -> String {
     }
     s
 }
+
+fn overlaps(
+    a1: &DisplaySourceAnnotation<'_>,
+    a2: &DisplaySourceAnnotation<'_>,
+    padding: usize,
+) -> bool {
+    (a2.range.0..a2.range.1).contains(&a1.range.0)
+        || (a1.range.0..a1.range.1 + padding).contains(&a2.range.0)
+}
+
+fn format_inline_marks(
+    line: usize,
+    inline_marks: &[DisplayMark],
+    lineno_width: usize,
+    stylesheet: &Stylesheet,
+    buf: &mut StyledBuffer,
+) -> fmt::Result {
+    for mark in inline_marks.iter() {
+        let annotation_style = get_annotation_style(&mark.annotation_type, stylesheet);
+        match mark.mark_type {
+            DisplayMarkType::AnnotationThrough(depth) => {
+                buf.putc(line, 3 + lineno_width + depth, '|', *annotation_style);
+            }
+            DisplayMarkType::AnnotationStart => {
+                buf.putc(line, 3 + lineno_width, '/', *annotation_style);
+            }
+        };
+    }
+    Ok(())
+}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 845d293..b9edcc6 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -12,6 +12,7 @@
 
 mod display_list;
 mod margin;
+mod styled_buffer;
 pub(crate) mod stylesheet;
 
 use crate::snippet::Message;
diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs
new file mode 100644
index 0000000..ec834e1
--- /dev/null
+++ b/src/renderer/styled_buffer.rs
@@ -0,0 +1,97 @@
+//! Adapted from [styled_buffer]
+//!
+//! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs
+
+use crate::renderer::stylesheet::Stylesheet;
+use anstyle::Style;
+use std::fmt;
+use std::fmt::Write;
+
+#[derive(Debug)]
+pub(crate) struct StyledBuffer {
+    lines: Vec<Vec<StyledChar>>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub(crate) struct StyledChar {
+    ch: char,
+    style: Style,
+}
+
+impl StyledChar {
+    pub(crate) const SPACE: Self = StyledChar::new(' ', Style::new());
+
+    pub(crate) const fn new(ch: char, style: Style) -> StyledChar {
+        StyledChar { ch, style }
+    }
+}
+
+impl StyledBuffer {
+    pub(crate) fn new() -> StyledBuffer {
+        StyledBuffer { lines: vec![] }
+    }
+
+    fn ensure_lines(&mut self, line: usize) {
+        if line >= self.lines.len() {
+            self.lines.resize(line + 1, Vec::new());
+        }
+    }
+
+    pub(crate) fn render(&self, stylesheet: &Stylesheet) -> Result<String, fmt::Error> {
+        let mut str = String::new();
+        for (i, line) in self.lines.iter().enumerate() {
+            let mut current_style = stylesheet.none;
+            for ch in line {
+                if ch.style != current_style {
+                    if !line.is_empty() {
+                        write!(str, "{}", current_style.render_reset())?;
+                    }
+                    current_style = ch.style;
+                    write!(str, "{}", current_style.render())?;
+                }
+                write!(str, "{}", ch.ch)?;
+            }
+            write!(str, "{}", current_style.render_reset())?;
+            if i != self.lines.len() - 1 {
+                writeln!(str)?;
+            }
+        }
+        Ok(str)
+    }
+
+    /// Sets `chr` with `style` for given `line`, `col`.
+    /// If `line` does not exist in our buffer, adds empty lines up to the given
+    /// and fills the last line with unstyled whitespace.
+    pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) {
+        self.ensure_lines(line);
+        if col >= self.lines[line].len() {
+            self.lines[line].resize(col + 1, StyledChar::SPACE);
+        }
+        self.lines[line][col] = StyledChar::new(chr, style);
+    }
+
+    /// Sets `string` with `style` for given `line`, starting from `col`.
+    /// If `line` does not exist in our buffer, adds empty lines up to the given
+    /// and fills the last line with unstyled whitespace.
+    pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) {
+        let mut n = col;
+        for c in string.chars() {
+            self.putc(line, n, c, style);
+            n += 1;
+        }
+    }
+    /// For given `line` inserts `string` with `style` after old content of that line,
+    /// adding lines if needed
+    pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) {
+        if line >= self.lines.len() {
+            self.puts(line, 0, string, style);
+        } else {
+            let col = self.lines[line].len();
+            self.puts(line, col, string, style);
+        }
+    }
+
+    pub(crate) fn num_lines(&self) -> usize {
+        self.lines.len()
+    }
+}
diff --git a/tests/fixtures/no-color/simple.svg b/tests/fixtures/no-color/simple.svg
index 51a3a65..ae7b03c 100644
--- a/tests/fixtures/no-color/simple.svg
+++ b/tests/fixtures/no-color/simple.svg
@@ -26,7 +26,7 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan>    |           - expected one of `.`, `;`, `?`, or an operator here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>170 | </tspan>
+    <tspan x="10px" y="118px"><tspan>170 |</tspan>
 </tspan>
     <tspan x="10px" y="136px"><tspan>171 |         for line in &amp;self.body {</tspan>
 </tspan>
diff --git a/tests/fixtures/no-color/strip_line_non_ws.svg b/tests/fixtures/no-color/strip_line_non_ws.svg
index 2be3890..f1977dc 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="1196px" height="146px" xmlns="http://www.w3.org/2000/svg">
+<svg width="1196px" height="164px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -24,11 +24,13 @@
 </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>   |                                                  ^^ expected due to this</tspan>
+    <tspan x="10px" y="118px"><tspan>   |                                                  |</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>   |</tspan>
+    <tspan x="10px" y="136px"><tspan>   |                                                  expected due to this</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>   |</tspan>
 </tspan>
   </text>
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index d0ac369..7f914de 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -262,8 +262,10 @@ fn test_source_annotation_standalone_multiline() {
 error
   |
 1 | tests
-  | ----- help: Example string
-  | ----- help: Second line
+  | -----
+  | |
+  | help: Example string
+  | help: Second line
   |
 "#]];
     let renderer = Renderer::plain();
@@ -296,7 +298,7 @@ error
    |
 LL | This is an example
 LL | of content lines
-LL | 
+LL |
 LL | abc
    |
 "#]];
@@ -756,8 +758,9 @@ error: unused optional dependency
  --> Cargo.toml:4:1
   |
 4 | bar = { version = "0.1.0", optional = true }
-  | ^^^ I need this to be really long so I can test overlaps
-  |                            --------------- info: This should also be long but not too long
+  | ^^^                        --------------- info: This should also be long but not too long
+  | |
+  | I need this to be really long so I can test overlaps
   |
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
@@ -789,8 +792,9 @@ bar = { version = "0.1.0", optional = true }
 error: unused optional dependency
   |
 4 |   bar = { version = "0.1.0", optional = true }
-  |  __________________________________________^
-  |                              --------------- info: This should also be long but not too long
+  |  ____________________________--------------^
+  | |                            |
+  | |                            info: This should also be long but not too long
 5 | | this is another line
 6 | | so is this
 7 | | bar = { version = "0.1.0", optional = true }
@@ -831,14 +835,16 @@ bar = { version = "0.1.0", optional = true }
 error: unused optional dependency
   |
 4 |    bar = { version = "0.1.0", optional = true }
-  |   __________________________________________^
-  |   _________^
-  |                               --------------- info: This should also be long but not too long
+  |   _________^__________________--------------^
+  |  |         |                  |
+  |  |_________|                  info: This should also be long but not too long
+  | ||
 5 | || this is another line
 6 | || so is this
 7 | || bar = { version = "0.1.0", optional = true }
-  | ||__________________________________________^ I need this to be really long so I can test overlaps
-  | ||_________________________^ I need this to be really long so I can test overlaps
+  | ||_________________________^________________^ I need this to be really long so I can test overlaps
+  | |__________________________|
+  |                            I need this to be really long so I can test overlaps
   |
 "#]];
     let renderer = Renderer::plain();
@@ -881,15 +887,17 @@ this is another line
 error: unused optional dependency
   |
 4 |     bar = { version = "0.1.0", optional = true }
-  |    __________________________________________^
-  |    _________^
-  |                                --------------- info: This should also be long but not too long
-5 |  || this is another line
-  |  ||____^
+  |   __________^__________________--------------^
+  |  |          |                  |
+  |  |__________|                  info: This should also be long but not too long
+  | ||
+5 | ||  this is another line
+  | || ____^
 6 | ||| so is this
 7 | ||| bar = { version = "0.1.0", optional = true }
-  | |||__________________________________________^ I need this to be really long so I can test overlaps
-  | |||_________________________^ I need this to be really long so I can test overlaps
+  | |||_________________________^________________^ I need this to be really long so I can test overlaps
+  | |_|_________________________|
+  |   |                         I need this to be really long so I can test overlaps
 8 |   | this is another line
   |   |____^ I need this to be really long so I can test overlaps
   |
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index f87b206..ed2c7ad 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -55,8 +55,8 @@ error: foo
   |
 2 |   fn foo() {
   |  __________^
-3 | | 
-4 | | 
+3 | |
+4 | |
 5 | |   }
   | |___^ test
   |
@@ -91,12 +91,14 @@ error: foo
  --> test.rs:3:3
   |
 3 |      X0 Y0
-  |   ___^
-  |   ______-
+  |   ___^__-
+  |  |___|
+  | ||
 4 | ||   X1 Y1
 5 | ||   X2 Y2
-  | ||____^ `X` is a good letter
-  | ||_______- `Y` is a good letter too
+  | ||____^__- `Y` is a good letter too
+  | |_____|
+  |       `X` is a good letter
   |
 "#]];
     let renderer = Renderer::plain();
@@ -128,11 +130,13 @@ error: foo
  --> test.rs:3:3
   |
 3 |      X0 Y0
-  |   ___^
-  |   ______-
+  |   ___^__-
+  |  |___|
+  | ||
 4 | ||   Y1 X1
-  | ||_______^ `X` is a good letter
-  | ||____- `Y` is a good letter too
+  | ||____-__^ `X` is a good letter
+  |  |____|
+  |       `Y` is a good letter too
   |
 "#]];
     let renderer = Renderer::plain();
@@ -166,9 +170,9 @@ error: foo
  --> test.rs:3:6
   |
 3 |      X0 Y0 Z0
-  |   ______^
-4 |  |   X1 Y1 Z1
-  |  |_________-
+  |  _______^
+4 | |    X1 Y1 Z1
+  | | _________-
 5 | ||   X2 Y2 Z2
   | ||____^ `X` is a good letter
 6 |  |   X3 Y3 Z3
@@ -206,14 +210,16 @@ error: foo
  --> test.rs:3:3
   |
 3 |       X0 Y0 Z0
-  |    ___^
-  |    ______-
-  |    _________-
+  |    ___^__-__-
+  |   |___|__|
+  |  ||___|
+  | |||
 4 | |||   X1 Y1 Z1
 5 | |||   X2 Y2 Z2
-  | |||____^ `X` is a good letter
-  | |||_______- `Y` is a good letter too
-  | |||__________- `Z` label
+  | |||____^__-__- `Z` label
+  | ||_____|__|
+  | |______|  `Y` is a good letter too
+  |        `X` is a good letter
   |
 "#]];
     let renderer = Renderer::plain();
@@ -247,14 +253,17 @@ error: foo
  --> test.rs:3:3
   |
 3 |       X0 Y0 Z0
-  |    ___^
-  |    ___-
-  |    ___-
+  |  _____-
+  | | ____|
+  | || ___|
+  | |||
 4 | |||   X1 Y1 Z1
 5 | |||   X2 Y2 Z2
-  | |||____^ `X` is a good letter
-  | |||____- `Y` is a good letter too
-  | |||____- `Z` label
+  | |||    -
+  | |||____|
+  |  ||____`X` is a good letter
+  |   |____`Y` is a good letter too
+  |        `Z` label
   |
 "#]];
     let renderer = Renderer::plain();
@@ -288,16 +297,18 @@ fn foo() {
 error: foo
  --> test.rs:3:6
   |
-3 |     X0 Y0 Z0
-  |  ______^
-4 | |   X1 Y1 Z1
-  | |____^ `X` is a good letter
-  | |______-
-5 | |   X2 Y2 Z2
-  | |__________- `Y` is a good letter too
-  | |___-
-6 | |   X3 Y3 Z3
-  | |_______- `Z`
+3 |      X0 Y0 Z0
+  |  _______^
+4 | |    X1 Y1 Z1
+  | | ____^_-
+  | ||____|
+  |  |    `X` is a good letter
+5 |  |   X2 Y2 Z2
+  |  |___-______- `Y` is a good letter too
+  |   ___|
+  |  |
+6 |  |   X3 Y3 Z3
+  |  |_______- `Z`
   |
 "#]];
     let renderer = Renderer::plain();
@@ -370,14 +381,15 @@ fn foo() {
 error: foo
  --> test.rs:3:6
   |
-3 |     X0 Y0 Z0
-  |  ______^
-4 | |   X1 Y1 Z1
-  | |____^ `X` is a good letter
-  | |_________-
-5 | |   X2 Y2 Z2
-6 | |   X3 Y3 Z3
-  | |__________- `Y` is a good letter too
+3 |      X0 Y0 Z0
+  |  _______^
+4 | |    X1 Y1 Z1
+  | | ____^____-
+  | ||____|
+  |  |    `X` is a good letter
+5 |  |   X2 Y2 Z2
+6 |  |   X3 Y3 Z3
+  |  |__________- `Y` is a good letter too
   |
 "#]];
     let renderer = Renderer::plain();
@@ -405,9 +417,7 @@ error: foo
  --> test.rs:3:7
   |
 3 |   a { b { c } d }
-  |       ^^^^^^^
-  |   ------------- `a` is a good letter
-  |           -
+  |   ----^^^^-^^-- `a` is a good letter
   |
 "#]];
     let renderer = Renderer::plain();
@@ -434,8 +444,7 @@ error: foo
  --> test.rs:3:3
   |
 3 |   a { b { c } d }
-  |   ^^^^^^^^^^^^^ `a` is a good letter
-  |       -------
+  |   ^^^^-------^^ `a` is a good letter
   |
 "#]];
     let renderer = Renderer::plain();
@@ -463,9 +472,9 @@ error: foo
  --> test.rs:3:7
   |
 3 |   a { b { c } d }
-  |       ^^^^^^^ `b` is a good letter
-  |   -------------
-  |           -
+  |   ----^^^^-^^--
+  |       |
+  |       `b` is a good letter
   |
 "#]];
     let renderer = Renderer::plain();
@@ -492,8 +501,9 @@ error: foo
  --> test.rs:3:3
   |
 3 |   a { b { c } d }
-  |   ^^^^^^^^^^^^^
-  |       ------- `b` is a good letter
+  |   ^^^^-------^^
+  |       |
+  |       `b` is a good letter
   |
 "#]];
     let renderer = Renderer::plain();
@@ -520,8 +530,9 @@ error: foo
  --> test.rs:3:3
   |
 3 |   a  bc  d
-  |   ^^^^ `a` is a good letter
-  |       ----
+  |   ^^^^----
+  |   |
+  |   `a` is a good letter
   |
 "#]];
     let renderer = Renderer::plain();
@@ -548,8 +559,7 @@ error: foo
  --> test.rs:3:3
   |
 3 |   a { b { c } d }
-  |   ^^^^^^^^^^^^^
-  |       -------
+  |   ^^^^-------^^
   |
 "#]];
     let renderer = Renderer::plain();
@@ -577,9 +587,7 @@ error: foo
  --> test.rs:3:7
   |
 3 |   a { b { c } d }
-  |       ^^^^^^^
-  |   -------------
-  |           -
+  |   ----^^^^-^^--
   |
 "#]];
     let renderer = Renderer::plain();
@@ -606,8 +614,10 @@ error: foo
  --> test.rs:3:3
   |
 3 |   a { b { c } d }
-  |   ^^^^^^^^^^^^^ `a` is a good letter
-  |       ------- `b` is a good letter
+  |   ^^^^-------^^
+  |   |   |
+  |   |   `b` is a good letter
+  |   `a` is a good letter
   |
 "#]];
     let renderer = Renderer::plain();
@@ -702,16 +712,17 @@ fn foo() {
 error: foo
   --> test.rs:3:6
    |
- 3 |     X0 Y0 Z0
-   |  ______^
- 4 | |   X1 Y1 Z1
-   | |____^ `X` is a good letter
-   | |_________-
- 5 | | 1
-...  |
-15 | |   X2 Y2 Z2
-16 | |   X3 Y3 Z3
-   | |__________- `Y` is a good letter too
+ 3 |      X0 Y0 Z0
+   |  _______^
+ 4 | |    X1 Y1 Z1
+   | | ____^____-
+   | ||____|
+   |  |    `X` is a good letter
+ 5 |  | 1
+...   |
+15 |  |   X2 Y2 Z2
+16 |  |   X3 Y3 Z3
+   |  |__________- `Y` is a good letter too
    |
 "#]];
     let renderer = Renderer::plain();
@@ -755,22 +766,22 @@ error: foo
   --> test.rs:3:6
    |
  3 |      X0 Y0 Z0
-   |   ______^
- 4 |  | 1
- 5 |  | 2
- 6 |  | 3
- 7 |  |   X1 Y1 Z1
-   |  |_________-
+   |  _______^
+ 4 | |  1
+ 5 | |  2
+ 6 | |  3
+ 7 | |    X1 Y1 Z1
+   | | _________-
  8 | || 4
  9 | || 5
 10 | || 6
 11 | ||   X2 Y2 Z2
    | ||__________- `Z` is a good letter too
-12 |  | 7
-...   |
-15 |  | 10
-16 |  |   X3 Y3 Z3
-   |  |_______^ `Y` is a good letter
+12 | |  7
+...  |
+15 | |  10
+16 | |    X3 Y3 Z3
+   | |________^ `Y` is a good letter
    |
 "#]];
     let renderer = Renderer::plain();

From 0175871363182516e0b69fbe95d7a997f58564a3 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 19 Jun 2024 21:13:36 -0600
Subject: [PATCH 211/302] feat: Merge multiline annotations with matching spans

---
 src/renderer/display_list.rs | 52 ++++++++++++++++++++++++++++++++++++
 tests/rustc_tests.rs         | 23 ++++++++--------
 2 files changed, 63 insertions(+), 12 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 2c8dbf9..8d0c1f0 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1248,6 +1248,58 @@ fn format_body(
     let mut depth_map: HashMap<usize, usize> = HashMap::new();
     let mut current_depth = 0;
     let mut annotations = snippet.annotations;
+    let ranges = annotations
+        .iter()
+        .map(|a| a.range.clone())
+        .collect::<Vec<_>>();
+    // We want to merge multiline annotations that have the same range into one
+    // multiline annotation to save space. This is done by making any duplicate
+    // multiline annotations into a single-line annotation pointing at the end
+    // of the range.
+    //
+    // 3 |       X0 Y0 Z0
+    //   |  _____^
+    //   | | ____|
+    //   | || ___|
+    //   | |||
+    // 4 | |||   X1 Y1 Z1
+    // 5 | |||   X2 Y2 Z2
+    //   | |||    ^
+    //   | |||____|
+    //   |  ||____`X` is a good letter
+    //   |   |____`Y` is a good letter too
+    //   |        `Z` label
+    // Should be
+    // error: foo
+    //  --> test.rs:3:3
+    //   |
+    // 3 | /   X0 Y0 Z0
+    // 4 | |   X1 Y1 Z1
+    // 5 | |   X2 Y2 Z2
+    //   | |    ^
+    //   | |____|
+    //   |      `X` is a good letter
+    //   |      `Y` is a good letter too
+    //   |      `Z` label
+    //   |
+    ranges.iter().enumerate().for_each(|(r_idx, range)| {
+        annotations
+            .iter_mut()
+            .enumerate()
+            .skip(r_idx + 1)
+            .for_each(|(ann_idx, ann)| {
+                // Skip if the annotation's index matches the range index
+                if ann_idx != r_idx
+                    // We only want to merge multiline annotations
+                    && snippet.source[ann.range.clone()].lines().count() > 1
+                    // We only want to merge annotations that have the same range
+                    && ann.range.start == range.start
+                    && ann.range.end == range.end
+                {
+                    ann.range.start = ann.range.end.saturating_sub(1);
+                }
+            });
+    });
     annotations.sort_by_key(|a| a.range.start);
     let mut annotations = annotations.into_iter().enumerate().collect::<Vec<_>>();
 
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index ed2c7ad..8db9b80 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -248,22 +248,21 @@ fn foo() {
             .annotation(Level::Warning.span(14..38).label("`Z` label")),
     );
 
+    // This should have a `^` but we currently don't support the idea of a
+    // "primary" annotation, which would solve this
     let expected = str![[r#"
 error: foo
  --> test.rs:3:3
   |
-3 |       X0 Y0 Z0
-  |  _____-
-  | | ____|
-  | || ___|
-  | |||
-4 | |||   X1 Y1 Z1
-5 | |||   X2 Y2 Z2
-  | |||    -
-  | |||____|
-  |  ||____`X` is a good letter
-  |   |____`Y` is a good letter too
-  |        `Z` label
+3 |     X0 Y0 Z0
+  |  ___^
+4 | |   X1 Y1 Z1
+5 | |   X2 Y2 Z2
+  | |    -
+  | |____|
+  |      `X` is a good letter
+  |      `Y` is a good letter too
+  |      `Z` label
   |
 "#]];
     let renderer = Renderer::plain();

From 66bbd827167c037a2105719edbae4e8fd50f86a9 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 19 Jun 2024 21:21:54 -0600
Subject: [PATCH 212/302] feat: Match Rust's multiline start special case

---
 examples/format.svg          | 52 +++++++++++-----------
 src/renderer/display_list.rs | 86 +++++++++++++++++++-----------------
 tests/rustc_tests.rs         |  6 +--
 3 files changed, 73 insertions(+), 71 deletions(-)

diff --git a/examples/format.svg b/examples/format.svg
index a427c94..ac196c0 100644
--- a/examples/format.svg
+++ b/examples/format.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="560px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="542px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -30,55 +30,53 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>        </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option&lt;String&gt;` because of return type</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan>       for ann in annotations {</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan>     for ann in annotations {</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">_____^</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (None, None) =&gt; continue,</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">54 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (None, None) =&gt; continue,</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt; end_index =&gt; continue,</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">55 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt; end_index =&gt; continue,</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">56 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt;= start_index =&gt; {</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">56 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt;= start_index =&gt; {</tspan>
+    <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">57 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 let label = if let Some(ref label) = ann.label {</tspan>
 </tspan>
-    <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">57 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 let label = if let Some(ref label) = ann.label {</tspan>
+    <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">58 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     format!(" {}", label)</tspan>
 </tspan>
-    <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">58 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     format!(" {}", label)</tspan>
+    <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">59 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 } else {</tspan>
 </tspan>
-    <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">59 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 } else {</tspan>
+    <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">60 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     String::from("")</tspan>
 </tspan>
-    <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">60 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     String::from("")</tspan>
+    <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 };</tspan>
 </tspan>
-    <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 };</tspan>
+    <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan>
+    <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 return Some(format!(</tspan>
 </tspan>
-    <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 return Some(format!(</tspan>
+    <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">64 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "{}{}{}",</tspan>
 </tspan>
-    <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">64 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "{}{}{}",</tspan>
+    <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">65 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     " ".repeat(start - start_index),</tspan>
 </tspan>
-    <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">65 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     " ".repeat(start - start_index),</tspan>
+    <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">66 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "^".repeat(end - start),</tspan>
 </tspan>
-    <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">66 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "^".repeat(end - start),</tspan>
+    <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">67 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     label</tspan>
 </tspan>
-    <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">67 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     label</tspan>
+    <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">68 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 ));</tspan>
 </tspan>
-    <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">68 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 ));</tspan>
+    <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">69 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             }</tspan>
 </tspan>
-    <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">69 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             }</tspan>
+    <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">70 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             _ =&gt; continue,</tspan>
 </tspan>
-    <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">70 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             _ =&gt; continue,</tspan>
+    <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         }</tspan>
 </tspan>
-    <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         }</tspan>
+    <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
 </tspan>
-    <tspan x="10px" y="496px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
+    <tspan x="10px" y="496px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan>
 </tspan>
-    <tspan x="10px" y="514px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan>
+    <tspan x="10px" y="514px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="532px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
-</tspan>
-    <tspan x="10px" y="550px">
+    <tspan x="10px" y="532px">
 </tspan>
   </text>
 
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 8d0c1f0..955b4b2 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -442,6 +442,42 @@ impl<'a> DisplaySet<'a> {
                         line_len += 1;
                     }
 
+                    // This is a special case where we have a multiline
+                    // annotation that is at the start of the line disregarding
+                    // any leading whitespace, and no other multiline
+                    // annotations overlap it. In this case, we want to draw
+                    //
+                    // 2 |   fn foo() {
+                    //   |  _^
+                    // 3 | |
+                    // 4 | | }
+                    //   | |_^ test
+                    //
+                    // we simplify the output to:
+                    //
+                    // 2 | / fn foo() {
+                    // 3 | |
+                    // 4 | | }
+                    //   | |_^ test
+                    if multiline_depth == 1
+                        && annotations_positions.len() == 1
+                        && annotations_positions
+                            .first()
+                            .map_or(false, |(_, annotation)| {
+                                matches!(
+                                    annotation.annotation_part,
+                                    DisplayAnnotationPart::MultilineStart(_)
+                                ) && text
+                                    .chars()
+                                    .take(annotation.range.0)
+                                    .all(|c| c.is_whitespace())
+                            })
+                    {
+                        let (_, ann) = annotations_positions.remove(0);
+                        let style = get_annotation_style(&ann.annotation_type, stylesheet);
+                        buffer.putc(line_offset, 3 + lineno_width, '/', *style);
+                    }
+
                     // Draw the column separator for any extra lines that were
                     // created
                     //
@@ -535,15 +571,13 @@ impl<'a> DisplaySet<'a> {
                     // Add in any inline marks for any extra lines that have
                     // been created. Output should look like above.
                     for inline_mark in inline_marks {
-                        if let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type {
-                            let style =
-                                get_annotation_style(&inline_mark.annotation_type, stylesheet);
-                            if annotations_positions.is_empty() {
-                                buffer.putc(line_offset, width_offset + depth, '|', *style);
-                            } else {
-                                for p in line_offset..=line_offset + line_len + 1 {
-                                    buffer.putc(p, width_offset + depth, '|', *style);
-                                }
+                        let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type;
+                        let style = get_annotation_style(&inline_mark.annotation_type, stylesheet);
+                        if annotations_positions.is_empty() {
+                            buffer.putc(line_offset, width_offset + depth, '|', *style);
+                        } else {
+                            for p in line_offset..=line_offset + line_len + 1 {
+                                buffer.putc(p, width_offset + depth, '|', *style);
                             }
                         }
                     }
@@ -823,8 +857,6 @@ pub(crate) struct DisplayMark {
 pub(crate) enum DisplayMarkType {
     /// A mark indicating a multiline annotation going through the current line.
     AnnotationThrough(usize),
-    /// A mark indicating a multiline annotation starting on the given line.
-    AnnotationStart,
 }
 
 /// A type of the `Annotation` which may impact the sigils, style or text displayed.
@@ -1153,18 +1185,8 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
     let mut unhighlighed_lines = vec![];
     for line in body {
         match &line {
-            DisplayLine::Source {
-                annotations,
-                inline_marks,
-                ..
-            } => {
-                if annotations.is_empty()
-                    // A multiline start mark (`/`) needs be treated as an
-                    // annotation or the line could get folded.
-                    && inline_marks
-                        .iter()
-                        .all(|m| m.mark_type != DisplayMarkType::AnnotationStart)
-                {
+            DisplayLine::Source { annotations, .. } => {
+                if annotations.is_empty() {
                     unhighlighed_lines.push(line);
                 } else {
                     if lines.is_empty() {
@@ -1407,20 +1429,7 @@ fn format_body(
                         && start <= line_end_index + end_line_size.saturating_sub(1)
                         && end > line_end_index =>
                 {
-                    // Special case for multiline annotations that start at the
-                    // beginning of a line, which requires a special mark (`/`)
-                    if start - line_start_index == 0 {
-                        if let DisplayLine::Source {
-                            ref mut inline_marks,
-                            ..
-                        } = body[body_idx]
-                        {
-                            inline_marks.push(DisplayMark {
-                                mark_type: DisplayMarkType::AnnotationStart,
-                                annotation_type: DisplayAnnotationType::from(annotation.level),
-                            });
-                        }
-                    } else if let DisplayLine::Source {
+                    if let DisplayLine::Source {
                         ref mut annotations,
                         ..
                     } = body[body_idx]
@@ -1679,9 +1688,6 @@ fn format_inline_marks(
             DisplayMarkType::AnnotationThrough(depth) => {
                 buf.putc(line, 3 + lineno_width + depth, '|', *annotation_style);
             }
-            DisplayMarkType::AnnotationStart => {
-                buf.putc(line, 3 + lineno_width, '/', *annotation_style);
-            }
         };
     }
     Ok(())
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index 8db9b80..620ca45 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -254,8 +254,7 @@ fn foo() {
 error: foo
  --> test.rs:3:3
   |
-3 |     X0 Y0 Z0
-  |  ___^
+3 | /   X0 Y0 Z0
 4 | |   X1 Y1 Z1
 5 | |   X2 Y2 Z2
   | |    -
@@ -340,8 +339,7 @@ fn foo() {
 error: foo
  --> test.rs:3:3
   |
-3 |     X0 Y0 Z0
-  |  ___^
+3 | /   X0 Y0 Z0
 4 | |   X1 Y1 Z1
   | |____^ `X` is a good letter
 5 |     X2 Y2 Z2

From 2a274e149f7f6f7f80f08486bd34c4fc7b8d63c8 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 21 Jun 2024 14:21:41 -0400
Subject: [PATCH 213/302] chore(ci): Auto-update Mac now that latest uses m1

---
 .github/workflows/ci.yml        | 2 +-
 .github/workflows/rust-next.yml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 95b13b4..6e06499 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,7 +34,7 @@ jobs:
     name: Test
     strategy:
       matrix:
-        os: ["ubuntu-latest", "windows-latest", "macos-14"]
+        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
         rust: ["stable"]
     continue-on-error: ${{ matrix.rust != 'stable' }}
     runs-on: ${{ matrix.os }}
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index ab49963..e98386c 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -21,7 +21,7 @@ jobs:
     name: Test
     strategy:
       matrix:
-        os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"]
+        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
         rust: ["stable", "beta"]
         include:
         - os: ubuntu-latest

From b25bd3e02f2256f34daad1efca730ec581c139cf Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Mon, 24 Jun 2024 11:05:33 -0600
Subject: [PATCH 214/302] feat: Match Rust's overlapping multiline starts

---
 src/renderer/display_list.rs | 34 ++++++++++++++++++++++++++++++++--
 tests/rustc_tests.rs         | 17 +++++++----------
 2 files changed, 39 insertions(+), 12 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 955b4b2..eea4971 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -269,7 +269,7 @@ impl<'a> DisplaySet<'a> {
         }
     }
 
-    // Adapted from https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/emitter.rs#L706-L1155
+    // Adapted from https://github.com/rust-lang/rust/blob/d371d17496f2ce3a56da76aa083f4ef157572c20/compiler/rustc_errors/src/emitter.rs#L706-L1211
     #[inline]
     fn format_line(
         &self,
@@ -366,7 +366,7 @@ impl<'a> DisplaySet<'a> {
                     annotations.sort_by_key(|a| Reverse(a.range.0));
 
                     let mut annotations_positions = vec![];
-                    let mut line_len = 0;
+                    let mut line_len: usize = 0;
                     let mut p = 0;
                     for (i, annotation) in annotations.iter().enumerate() {
                         for (j, next) in annotations.iter().enumerate() {
@@ -442,6 +442,36 @@ impl<'a> DisplaySet<'a> {
                         line_len += 1;
                     }
 
+                    if annotations_positions.iter().all(|(_, ann)| {
+                        matches!(
+                            ann.annotation_part,
+                            DisplayAnnotationPart::MultilineStart(_)
+                        )
+                    }) {
+                        if let Some(max_pos) =
+                            annotations_positions.iter().map(|(pos, _)| *pos).max()
+                        {
+                            // Special case the following, so that we minimize overlapping multiline spans.
+                            //
+                            // 3 │       X0 Y0 Z0
+                            //   │ ┏━━━━━┛  │  │     < We are writing these lines
+                            //   │ ┃┌───────┘  │     < by reverting the "depth" of
+                            //   │ ┃│┌─────────┘     < their multilne spans.
+                            // 4 │ ┃││   X1 Y1 Z1
+                            // 5 │ ┃││   X2 Y2 Z2
+                            //   │ ┃│└────╿──│──┘ `Z` label
+                            //   │ ┃└─────│──┤
+                            //   │ ┗━━━━━━┥  `Y` is a good letter too
+                            //   ╰╴       `X` is a good letter
+                            for (pos, _) in &mut annotations_positions {
+                                *pos = max_pos - *pos;
+                            }
+                            // We know then that we don't need an additional line for the span label, saving us
+                            // one line of vertical space.
+                            line_len = line_len.saturating_sub(1);
+                        }
+                    }
+
                     // This is a special case where we have a multiline
                     // annotation that is at the start of the line disregarding
                     // any leading whitespace, and no other multiline
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index 620ca45..54b7321 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -91,9 +91,8 @@ error: foo
  --> test.rs:3:3
   |
 3 |      X0 Y0
-  |   ___^__-
-  |  |___|
-  | ||
+  |  ____^  -
+  | | ______|
 4 | ||   X1 Y1
 5 | ||   X2 Y2
   | ||____^__- `Y` is a good letter too
@@ -130,9 +129,8 @@ error: foo
  --> test.rs:3:3
   |
 3 |      X0 Y0
-  |   ___^__-
-  |  |___|
-  | ||
+  |  ____^  -
+  | | ______|
 4 | ||   Y1 X1
   | ||____-__^ `X` is a good letter
   |  |____|
@@ -210,10 +208,9 @@ error: foo
  --> test.rs:3:3
   |
 3 |       X0 Y0 Z0
-  |    ___^__-__-
-  |   |___|__|
-  |  ||___|
-  | |||
+  |  _____^  -  -
+  | | _______|  |
+  | || _________|
 4 | |||   X1 Y1 Z1
 5 | |||   X2 Y2 Z2
   | |||____^__-__- `Z` label

From 10e6e40bc668d4912d560b8ca871a5a312cd431d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 1 Jul 2024 00:49:35 +0000
Subject: [PATCH 215/302] chore(deps): Update Rust crate snapbox to v0.6.10

---
 Cargo.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 40a064e..d47f14d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -685,9 +685,9 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
 
 [[package]]
 name = "snapbox"
-version = "0.6.7"
+version = "0.6.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94204b12a4d3550420babdb4148c6639692e4e3e61060866929c5107f208aeb6"
+checksum = "40e14d10e4c2b4331ac24c33baa5a03e1fbca81c045b285b53b2a612d28569fb"
 dependencies = [
  "anstream 0.6.14",
  "anstyle",

From 0547ff2d0f135d541faef3735143b40c174b4c3a Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 4 Jul 2024 12:54:40 -0400
Subject: [PATCH 216/302] docs(contrib): Clarify our policies

---
 CONTRIBUTING.md | 45 ++++++++++++++++++++++++++++++---------------
 1 file changed, 30 insertions(+), 15 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e9d7079..1a6dd1c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -29,27 +29,42 @@ to re-work some of it and the discouragement that goes along with that.
 
 ### Process
 
-Before posting a PR, we request that the commit history get cleaned up.
-However, we recommend avoiding this during the review to make it easier to
-check how feedback was handled. Once the PR is ready, we'll ask you to clean up
-the commit history from the review.  Once you let us know this is done, we can
-move forward with merging!  If you are uncomfortable with these parts of git,
-let us know and we can help.
-
-For commit messages, we use [Conventional](https://www.conventionalcommits.org)
-style.  If you already wrote your commits and don't feel comfortable changing
-them, don't worry and go ahead and create your PR.  We'll work with you on the
-best route forward. You can check your branch locally with
-[`committed`](https://github.com/crate-ci/committed).
-
 As a heads up, we'll be running your PR through the following gauntlet:
 - warnings turned to compile errors
 - `cargo test`
 - `rustfmt`
 - `clippy`
 - `rustdoc`
-- [`committed`](https://github.com/crate-ci/committed)
-- [`typos`](https://github.com/crate-ci/typos)
+- [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit styl
+- [`typos`](https://github.com/crate-ci/typos) to check spelling
+
+Not everything can be checked automatically though.
+
+We request that the commit history get cleaned up.
+We ask that commits are atomic, meaning they are complete and have a single responsibility.
+PRs shoukd tell a cohesive story, with test and refactor commits that keep the
+fix or feature commits simple and clear.
+
+Specifically, we would encouage
+- File renames be isolated into their own commit
+- Add tests in a commit before their feature or fix, showing the current behavior.
+  The diff for the feature/fix commit will then show how the behavior changed,
+  making it clearer to reviewrs and the community and showing people that the
+  test is verifying the expected state.
+  - e.g. [clap#5520](https://github.com/clap-rs/clap/pull/5520)
+
+Note that we are talking about ideals.
+We understand having a clean history requires more advanced git skills;
+feel free to ask us for help!
+We might even suggest where it would work to be lax.
+We also understand that editing some early commits may cause a lot of churn
+with merge conflicts which can make it not worth editing all of the history.
+
+For code organization, we recommend
+- Grouping `impl` blocks next to their type (or trait)
+- Grouping private items after the `pub` item that uses them.
+  - The intent is to help people quickly find the "relevant" details, allowing them to "dig deeper" as needed.  Or put another way, the `pub` items serve as a table-of-contents.
+  - The exact order is fuzzy; do what makes sense
 
 ## Releasing
 

From eb4e999f1b679936ce1d11aa68b923066aff2ab1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jalil=20David=20Salam=C3=A9=20Messina?=
 <60845989+jalil-salame@users.noreply.github.com>
Date: Thu, 4 Jul 2024 19:06:12 +0200
Subject: [PATCH 217/302] Fix typos in CONTRIBUTING.md

I found this through [mastodon][1] and found the typos jarring.

[1]: https://hachyderm.io/@epage/112729287446906823
---
 CONTRIBUTING.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1a6dd1c..87d9134 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,14 +35,14 @@ As a heads up, we'll be running your PR through the following gauntlet:
 - `rustfmt`
 - `clippy`
 - `rustdoc`
-- [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit styl
+- [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit style
 - [`typos`](https://github.com/crate-ci/typos) to check spelling
 
 Not everything can be checked automatically though.
 
-We request that the commit history get cleaned up.
+We request that the commit history gets cleaned up.
 We ask that commits are atomic, meaning they are complete and have a single responsibility.
-PRs shoukd tell a cohesive story, with test and refactor commits that keep the
+PRs should tell a cohesive story, with test and refactor commits that keep the
 fix or feature commits simple and clear.
 
 Specifically, we would encouage

From bdb06a11df6cf4d8b06848520f18609ab07c7b5e Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Tue, 9 Jul 2024 11:06:45 -0500
Subject: [PATCH 218/302] chore(ci): Verify version requirements

---
 .github/workflows/ci.yml | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6e06499..d49017e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,7 +23,7 @@ jobs:
     permissions:
       contents: none
     name: CI
-    needs: [test, msrv, lockfile, docs, rustfmt, clippy]
+    needs: [test, msrv, lockfile, docs, rustfmt, clippy, minimal-versions]
     runs-on: ubuntu-latest
     if: "always()"
     steps:
@@ -65,6 +65,24 @@ jobs:
     - uses: taiki-e/install-action@cargo-hack
     - name: Default features
       run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets
+  minimal-versions:
+    name: Minimal versions
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install stable Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: stable
+    - name: Install nightly Rust
+      uses: dtolnay/rust-toolchain@stable
+      with:
+        toolchain: nightly
+    - name: Downgrade dependencies to minimal versions
+      run: cargo +nightly generate-lockfile -Z minimal-versions
+    - name: Compile with minimal versions
+      run: cargo +stable check --workspace --all-features --locked
   lockfile:
     runs-on: ubuntu-latest
     steps:

From 87d9ae55c792a4f37b3f989250c1a3512df2926e Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 25 Jul 2024 15:48:09 -0500
Subject: [PATCH 219/302] chore: Fix clippy::lint_groups_priority for 1.80

---
 Cargo.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index 96cb234..90d89f6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,7 @@ include = [
 ]
 
 [workspace.lints.rust]
-rust_2018_idioms = "warn"
+rust_2018_idioms = { level = "warn", priority = -1 }
 unreachable_pub = "warn"
 unsafe_op_in_unsafe_fn = "warn"
 unused_lifetimes = "warn"

From c87ce7f7737563ec8d0c9e44ae8ae0bf984062e8 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 25 Jul 2024 21:11:48 +0000
Subject: [PATCH 220/302] chore(deps): Update Rust Stable to v1.80

---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e1d62e4..89809f5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -86,7 +86,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.79"  # STABLE
+        toolchain: "1.80"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -101,7 +101,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.79"  # STABLE
+        toolchain: "1.80"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -117,7 +117,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.79"  # STABLE
+        toolchain: "1.80"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From 553258af51034bc84dc9f951201e7b8f2285b57e Mon Sep 17 00:00:00 2001
From: Josh Triplett <josh@joshtriplett.org>
Date: Thu, 25 Jul 2024 15:35:58 -0700
Subject: [PATCH 221/302] Have clippy warn about uninlined format arguments

This makes clippy warn about `format!("{}", var)`, with a
machine-applicable fix converting to `format!("{var}")`.
---
 Cargo.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Cargo.toml b/Cargo.toml
index 90d89f6..97c7ed7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -80,6 +80,7 @@ string_lit_as_bytes = "warn"
 string_to_string = "warn"
 todo = "warn"
 trait_duplication_in_bounds = "warn"
+uninlined_format_args = "warn"
 verbose_file_reads = "warn"
 wildcard_imports = "warn"
 zero_sized_map_values = "warn"

From 43a10aa8619972e446213a1c8a6649893b082f4b Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 1 Aug 2024 00:38:57 +0000
Subject: [PATCH 222/302] chore(deps): Update compatible (dev)

---
 Cargo.lock | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index d47f14d..a686b02 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,7 +21,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 name = "annotate-snippets"
 version = "0.11.4"
 dependencies = [
- "anstream 0.6.14",
+ "anstream 0.6.15",
  "anstyle",
  "criterion",
  "difference",
@@ -50,9 +50,9 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.14"
+version = "0.6.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
+checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -102,7 +102,7 @@ version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa"
 dependencies = [
- "anstream 0.6.14",
+ "anstream 0.6.15",
  "anstyle",
  "anstyle-lossy",
  "html-escape",
@@ -648,18 +648,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.203"
+version = "1.0.204"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
+checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.203"
+version = "1.0.204"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
+checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -685,11 +685,11 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
 
 [[package]]
 name = "snapbox"
-version = "0.6.10"
+version = "0.6.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40e14d10e4c2b4331ac24c33baa5a03e1fbca81c045b285b53b2a612d28569fb"
+checksum = "027c936207f85d10d015e21faf5c676c7e08c453ed371adf55c0874c443ca77a"
 dependencies = [
- "anstream 0.6.14",
+ "anstream 0.6.15",
  "anstyle",
  "anstyle-svg",
  "escargot",
@@ -705,11 +705,11 @@ dependencies = [
 
 [[package]]
 name = "snapbox-macros"
-version = "0.3.9"
+version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d"
+checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af"
 dependencies = [
- "anstream 0.6.14",
+ "anstream 0.6.15",
 ]
 
 [[package]]
@@ -778,9 +778,9 @@ dependencies = [
 
 [[package]]
 name = "tryfn"
-version = "0.2.1"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "493e1390312bb94363f762687be32a1bd01c3333dfad25a5a7fffab1edc64839"
+checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396"
 dependencies = [
  "ignore",
  "libtest-mimic",

From c5443c45979b52a7ef3790c9604681a171ee76f8 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 21 Aug 2024 15:01:07 -0600
Subject: [PATCH 223/302] test: Move all fixture tests to color

---
 tests/fixtures/{no-color => color}/ann_eof.svg                    | 0
 tests/fixtures/{no-color => color}/ann_eof.toml                   | 0
 tests/fixtures/{no-color => color}/ann_insertion.svg              | 0
 tests/fixtures/{no-color => color}/ann_insertion.toml             | 0
 tests/fixtures/{no-color => color}/ann_multiline.svg              | 0
 tests/fixtures/{no-color => color}/ann_multiline.toml             | 0
 tests/fixtures/{no-color => color}/ann_multiline2.svg             | 0
 tests/fixtures/{no-color => color}/ann_multiline2.toml            | 0
 tests/fixtures/{no-color => color}/ann_removed_nl.svg             | 0
 tests/fixtures/{no-color => color}/ann_removed_nl.toml            | 0
 .../fixtures/{no-color => color}/ensure-emoji-highlight-width.svg | 0
 .../{no-color => color}/ensure-emoji-highlight-width.toml         | 0
 tests/fixtures/{no-color => color}/fold_ann_multiline.svg         | 0
 tests/fixtures/{no-color => color}/fold_ann_multiline.toml        | 0
 tests/fixtures/{no-color => color}/fold_bad_origin_line.svg       | 0
 tests/fixtures/{no-color => color}/fold_bad_origin_line.toml      | 0
 tests/fixtures/{no-color => color}/fold_leading.svg               | 0
 tests/fixtures/{no-color => color}/fold_leading.toml              | 0
 tests/fixtures/{no-color => color}/fold_trailing.svg              | 0
 tests/fixtures/{no-color => color}/fold_trailing.toml             | 0
 tests/fixtures/{no-color => color}/issue_9.svg                    | 0
 tests/fixtures/{no-color => color}/issue_9.toml                   | 0
 tests/fixtures/{no-color => color}/multiple_annotations.svg       | 0
 tests/fixtures/{no-color => color}/multiple_annotations.toml      | 0
 tests/fixtures/{no-color => color}/simple.svg                     | 0
 tests/fixtures/{no-color => color}/simple.toml                    | 0
 tests/fixtures/{no-color => color}/strip_line.svg                 | 0
 tests/fixtures/{no-color => color}/strip_line.toml                | 0
 tests/fixtures/{no-color => color}/strip_line_char.svg            | 0
 tests/fixtures/{no-color => color}/strip_line_char.toml           | 0
 tests/fixtures/{no-color => color}/strip_line_non_ws.svg          | 0
 tests/fixtures/{no-color => color}/strip_line_non_ws.toml         | 0
 32 files changed, 0 insertions(+), 0 deletions(-)
 rename tests/fixtures/{no-color => color}/ann_eof.svg (100%)
 rename tests/fixtures/{no-color => color}/ann_eof.toml (100%)
 rename tests/fixtures/{no-color => color}/ann_insertion.svg (100%)
 rename tests/fixtures/{no-color => color}/ann_insertion.toml (100%)
 rename tests/fixtures/{no-color => color}/ann_multiline.svg (100%)
 rename tests/fixtures/{no-color => color}/ann_multiline.toml (100%)
 rename tests/fixtures/{no-color => color}/ann_multiline2.svg (100%)
 rename tests/fixtures/{no-color => color}/ann_multiline2.toml (100%)
 rename tests/fixtures/{no-color => color}/ann_removed_nl.svg (100%)
 rename tests/fixtures/{no-color => color}/ann_removed_nl.toml (100%)
 rename tests/fixtures/{no-color => color}/ensure-emoji-highlight-width.svg (100%)
 rename tests/fixtures/{no-color => color}/ensure-emoji-highlight-width.toml (100%)
 rename tests/fixtures/{no-color => color}/fold_ann_multiline.svg (100%)
 rename tests/fixtures/{no-color => color}/fold_ann_multiline.toml (100%)
 rename tests/fixtures/{no-color => color}/fold_bad_origin_line.svg (100%)
 rename tests/fixtures/{no-color => color}/fold_bad_origin_line.toml (100%)
 rename tests/fixtures/{no-color => color}/fold_leading.svg (100%)
 rename tests/fixtures/{no-color => color}/fold_leading.toml (100%)
 rename tests/fixtures/{no-color => color}/fold_trailing.svg (100%)
 rename tests/fixtures/{no-color => color}/fold_trailing.toml (100%)
 rename tests/fixtures/{no-color => color}/issue_9.svg (100%)
 rename tests/fixtures/{no-color => color}/issue_9.toml (100%)
 rename tests/fixtures/{no-color => color}/multiple_annotations.svg (100%)
 rename tests/fixtures/{no-color => color}/multiple_annotations.toml (100%)
 rename tests/fixtures/{no-color => color}/simple.svg (100%)
 rename tests/fixtures/{no-color => color}/simple.toml (100%)
 rename tests/fixtures/{no-color => color}/strip_line.svg (100%)
 rename tests/fixtures/{no-color => color}/strip_line.toml (100%)
 rename tests/fixtures/{no-color => color}/strip_line_char.svg (100%)
 rename tests/fixtures/{no-color => color}/strip_line_char.toml (100%)
 rename tests/fixtures/{no-color => color}/strip_line_non_ws.svg (100%)
 rename tests/fixtures/{no-color => color}/strip_line_non_ws.toml (100%)

diff --git a/tests/fixtures/no-color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg
similarity index 100%
rename from tests/fixtures/no-color/ann_eof.svg
rename to tests/fixtures/color/ann_eof.svg
diff --git a/tests/fixtures/no-color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml
similarity index 100%
rename from tests/fixtures/no-color/ann_eof.toml
rename to tests/fixtures/color/ann_eof.toml
diff --git a/tests/fixtures/no-color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg
similarity index 100%
rename from tests/fixtures/no-color/ann_insertion.svg
rename to tests/fixtures/color/ann_insertion.svg
diff --git a/tests/fixtures/no-color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml
similarity index 100%
rename from tests/fixtures/no-color/ann_insertion.toml
rename to tests/fixtures/color/ann_insertion.toml
diff --git a/tests/fixtures/no-color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg
similarity index 100%
rename from tests/fixtures/no-color/ann_multiline.svg
rename to tests/fixtures/color/ann_multiline.svg
diff --git a/tests/fixtures/no-color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml
similarity index 100%
rename from tests/fixtures/no-color/ann_multiline.toml
rename to tests/fixtures/color/ann_multiline.toml
diff --git a/tests/fixtures/no-color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg
similarity index 100%
rename from tests/fixtures/no-color/ann_multiline2.svg
rename to tests/fixtures/color/ann_multiline2.svg
diff --git a/tests/fixtures/no-color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml
similarity index 100%
rename from tests/fixtures/no-color/ann_multiline2.toml
rename to tests/fixtures/color/ann_multiline2.toml
diff --git a/tests/fixtures/no-color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg
similarity index 100%
rename from tests/fixtures/no-color/ann_removed_nl.svg
rename to tests/fixtures/color/ann_removed_nl.svg
diff --git a/tests/fixtures/no-color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml
similarity index 100%
rename from tests/fixtures/no-color/ann_removed_nl.toml
rename to tests/fixtures/color/ann_removed_nl.toml
diff --git a/tests/fixtures/no-color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg
similarity index 100%
rename from tests/fixtures/no-color/ensure-emoji-highlight-width.svg
rename to tests/fixtures/color/ensure-emoji-highlight-width.svg
diff --git a/tests/fixtures/no-color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml
similarity index 100%
rename from tests/fixtures/no-color/ensure-emoji-highlight-width.toml
rename to tests/fixtures/color/ensure-emoji-highlight-width.toml
diff --git a/tests/fixtures/no-color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg
similarity index 100%
rename from tests/fixtures/no-color/fold_ann_multiline.svg
rename to tests/fixtures/color/fold_ann_multiline.svg
diff --git a/tests/fixtures/no-color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml
similarity index 100%
rename from tests/fixtures/no-color/fold_ann_multiline.toml
rename to tests/fixtures/color/fold_ann_multiline.toml
diff --git a/tests/fixtures/no-color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg
similarity index 100%
rename from tests/fixtures/no-color/fold_bad_origin_line.svg
rename to tests/fixtures/color/fold_bad_origin_line.svg
diff --git a/tests/fixtures/no-color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml
similarity index 100%
rename from tests/fixtures/no-color/fold_bad_origin_line.toml
rename to tests/fixtures/color/fold_bad_origin_line.toml
diff --git a/tests/fixtures/no-color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg
similarity index 100%
rename from tests/fixtures/no-color/fold_leading.svg
rename to tests/fixtures/color/fold_leading.svg
diff --git a/tests/fixtures/no-color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml
similarity index 100%
rename from tests/fixtures/no-color/fold_leading.toml
rename to tests/fixtures/color/fold_leading.toml
diff --git a/tests/fixtures/no-color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg
similarity index 100%
rename from tests/fixtures/no-color/fold_trailing.svg
rename to tests/fixtures/color/fold_trailing.svg
diff --git a/tests/fixtures/no-color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml
similarity index 100%
rename from tests/fixtures/no-color/fold_trailing.toml
rename to tests/fixtures/color/fold_trailing.toml
diff --git a/tests/fixtures/no-color/issue_9.svg b/tests/fixtures/color/issue_9.svg
similarity index 100%
rename from tests/fixtures/no-color/issue_9.svg
rename to tests/fixtures/color/issue_9.svg
diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/color/issue_9.toml
similarity index 100%
rename from tests/fixtures/no-color/issue_9.toml
rename to tests/fixtures/color/issue_9.toml
diff --git a/tests/fixtures/no-color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg
similarity index 100%
rename from tests/fixtures/no-color/multiple_annotations.svg
rename to tests/fixtures/color/multiple_annotations.svg
diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml
similarity index 100%
rename from tests/fixtures/no-color/multiple_annotations.toml
rename to tests/fixtures/color/multiple_annotations.toml
diff --git a/tests/fixtures/no-color/simple.svg b/tests/fixtures/color/simple.svg
similarity index 100%
rename from tests/fixtures/no-color/simple.svg
rename to tests/fixtures/color/simple.svg
diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/color/simple.toml
similarity index 100%
rename from tests/fixtures/no-color/simple.toml
rename to tests/fixtures/color/simple.toml
diff --git a/tests/fixtures/no-color/strip_line.svg b/tests/fixtures/color/strip_line.svg
similarity index 100%
rename from tests/fixtures/no-color/strip_line.svg
rename to tests/fixtures/color/strip_line.svg
diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/color/strip_line.toml
similarity index 100%
rename from tests/fixtures/no-color/strip_line.toml
rename to tests/fixtures/color/strip_line.toml
diff --git a/tests/fixtures/no-color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg
similarity index 100%
rename from tests/fixtures/no-color/strip_line_char.svg
rename to tests/fixtures/color/strip_line_char.svg
diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml
similarity index 100%
rename from tests/fixtures/no-color/strip_line_char.toml
rename to tests/fixtures/color/strip_line_char.toml
diff --git a/tests/fixtures/no-color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg
similarity index 100%
rename from tests/fixtures/no-color/strip_line_non_ws.svg
rename to tests/fixtures/color/strip_line_non_ws.svg
diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml
similarity index 100%
rename from tests/fixtures/no-color/strip_line_non_ws.toml
rename to tests/fixtures/color/strip_line_non_ws.toml

From a059969c14e8c10fa1799a9f8e073699cd438ade Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 21 Aug 2024 15:02:49 -0600
Subject: [PATCH 224/302] test: Make all color tests have color

---
 tests/fixtures/color/ann_eof.svg              | 15 +++++----
 tests/fixtures/color/ann_eof.toml             |  3 ++
 tests/fixtures/color/ann_insertion.svg        | 15 +++++----
 tests/fixtures/color/ann_insertion.toml       |  3 ++
 tests/fixtures/color/ann_multiline.svg        | 21 +++++++-----
 tests/fixtures/color/ann_multiline.toml       |  3 ++
 tests/fixtures/color/ann_multiline2.svg       | 19 ++++++-----
 tests/fixtures/color/ann_multiline2.toml      |  3 ++
 tests/fixtures/color/ann_removed_nl.svg       | 15 +++++----
 tests/fixtures/color/ann_removed_nl.toml      |  3 ++
 .../color/ensure-emoji-highlight-width.svg    | 15 +++++----
 .../color/ensure-emoji-highlight-width.toml   |  3 ++
 tests/fixtures/color/fold_ann_multiline.svg   | 28 +++++++++-------
 tests/fixtures/color/fold_ann_multiline.toml  |  3 ++
 tests/fixtures/color/fold_bad_origin_line.svg | 16 +++++----
 .../fixtures/color/fold_bad_origin_line.toml  |  3 ++
 tests/fixtures/color/fold_leading.svg         | 15 +++++----
 tests/fixtures/color/fold_leading.toml        |  3 ++
 tests/fixtures/color/fold_trailing.svg        | 15 +++++----
 tests/fixtures/color/fold_trailing.toml       |  3 ++
 tests/fixtures/color/issue_9.svg              | 28 +++++++++-------
 tests/fixtures/color/issue_9.toml             |  3 ++
 tests/fixtures/color/multiple_annotations.svg | 33 ++++++++++---------
 .../fixtures/color/multiple_annotations.toml  |  3 ++
 tests/fixtures/color/simple.svg               | 22 ++++++++-----
 tests/fixtures/color/simple.toml              |  3 ++
 tests/fixtures/color/strip_line.svg           | 15 +++++----
 tests/fixtures/color/strip_line.toml          |  2 +-
 tests/fixtures/color/strip_line_char.svg      | 15 +++++----
 tests/fixtures/color/strip_line_char.toml     |  2 +-
 tests/fixtures/color/strip_line_non_ws.svg    | 19 ++++++-----
 tests/fixtures/color/strip_line_non_ws.toml   |  1 +
 tests/fixtures/deserialize.rs                 | 11 ++++++-
 33 files changed, 231 insertions(+), 130 deletions(-)

diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg
index c8900d0..bb12aec 100644
--- a/tests/fixtures/color/ann_eof.svg
+++ b/tests/fixtures/color/ann_eof.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,17 +19,17 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:5</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:5</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  |</tspan>
+    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>1 | asdf</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  |     ^</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-red bold">^</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  |</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml
index 313d220..cee5f0f 100644
--- a/tests/fixtures/color/ann_eof.toml
+++ b/tests/fixtures/color/ann_eof.toml
@@ -10,3 +10,6 @@ origin = "Cargo.toml"
 label = ""
 level = "Error"
 range = [4, 4]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg
index b15b81b..1f4b6a2 100644
--- a/tests/fixtures/color/ann_insertion.svg
+++ b/tests/fixtures/color/ann_insertion.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,17 +19,17 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:3</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:3</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  |</tspan>
+    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>1 | asf</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asf</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  |   ^ 'd' belongs here</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>   </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">'d' belongs here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  |</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml
index ffd2140..bf7411e 100644
--- a/tests/fixtures/color/ann_insertion.toml
+++ b/tests/fixtures/color/ann_insertion.toml
@@ -10,3 +10,6 @@ origin = "Cargo.toml"
 label = "'d' belongs here"
 level = "Error"
 range = [2, 2]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg
index f4b4433..9130691 100644
--- a/tests/fixtures/color/ann_multiline.svg
+++ b/tests/fixtures/color/ann_multiline.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,23 +19,23 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error[E0027]: pattern does not mention fields `lineno`, `content`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0027]</tspan><tspan>: </tspan><tspan class="bold">pattern does not mention fields `lineno`, `content`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>   --&gt; src/display_list.rs:139:32</tspan>
+    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/display_list.rs:139:32</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>    |</tspan>
+    <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>139 |                           if let DisplayLine::Source {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139 |</tspan><tspan>                           if let DisplayLine::Source {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>    |  ________________________________^</tspan>
+    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">________________________________^</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>140 | |                             ref mut inline_marks,</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                             ref mut inline_marks,</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>141 | |                         } = body[body_idx]</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                         } = body[body_idx]</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>    | |_________________________^ missing fields `lineno`, `content`</tspan>
+    <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_________________________^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">missing fields `lineno`, `content`</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>    |</tspan>
+    <tspan x="10px" y="172px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml
index 671b534..9d8c30f 100644
--- a/tests/fixtures/color/ann_multiline.toml
+++ b/tests/fixtures/color/ann_multiline.toml
@@ -16,3 +16,6 @@ fold = false
 label = "missing fields `lineno`, `content`"
 level = "Error"
 range = [31, 128]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg
index 49c2c4b..97948a4 100644
--- a/tests/fixtures/color/ann_multiline2.svg
+++ b/tests/fixtures/color/ann_multiline2.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,21 +19,21 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error[E####]: spacing error found</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E####]</tspan><tspan>: </tspan><tspan class="bold">spacing error found</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  --&gt; foo.txt:26:12</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> foo.txt:26:12</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>   |</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>26 | This is an example</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan> This is an example</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   |            ^^^^^^^ this should not be on separate lines</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>            </tspan><tspan class="fg-bright-red bold">^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">this should not be on separate lines</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>27 | of an edge case of an annotation overflowing</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> of an edge case of an annotation overflowing</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>28 | to exactly one character on next line.</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> to exactly one character on next line.</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>   |</tspan>
+    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml
index afb3aa9..259d94b 100644
--- a/tests/fixtures/color/ann_multiline2.toml
+++ b/tests/fixtures/color/ann_multiline2.toml
@@ -16,3 +16,6 @@ fold = false
 label = "this should not be on separate lines"
 level = "Error"
 range = [11, 19]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg
index c8900d0..bb12aec 100644
--- a/tests/fixtures/color/ann_removed_nl.svg
+++ b/tests/fixtures/color/ann_removed_nl.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,17 +19,17 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:5</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:5</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  |</tspan>
+    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>1 | asdf</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  |     ^</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-red bold">^</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  |</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml
index b681c29..36f74ef 100644
--- a/tests/fixtures/color/ann_removed_nl.toml
+++ b/tests/fixtures/color/ann_removed_nl.toml
@@ -10,3 +10,6 @@ origin = "Cargo.toml"
 label = ""
 level = "Error"
 range = [4, 5]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg
index 0840805..e5646e6 100644
--- a/tests/fixtures/color/ensure-emoji-highlight-width.svg
+++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,17 +19,17 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> --&gt; &lt;file&gt;:7:1</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> &lt;file&gt;:7:1</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  |</tspan>
+    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>7 | "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  |</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml
index 7af05ff..52168b4 100644
--- a/tests/fixtures/color/ensure-emoji-highlight-width.toml
+++ b/tests/fixtures/color/ensure-emoji-highlight-width.toml
@@ -13,3 +13,6 @@ origin = "<file>"
 label = ""
 level = "Error"
 range = [0, 35]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg
index f82fe25..6a89c4f 100644
--- a/tests/fixtures/color/fold_ann_multiline.svg
+++ b/tests/fixtures/color/fold_ann_multiline.svg
@@ -2,10 +2,14 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
+    .fg-yellow { fill: #AA5500 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,29 +20,29 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  --&gt; src/format.rs:51:6</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs:51:6</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>   |</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>51 |   ) -&gt; Option&lt;String&gt; {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan>   ) -&gt; Option&lt;String&gt; {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   |        -------------- expected `std::option::Option&lt;std::string::String&gt;` because of return type</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>        </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `std::option::Option&lt;std::string::String&gt;` because of return type</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>52 | /     for ann in annotations {</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan>     for ann in annotations {</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>53 | |         match (ann.range.0, ann.range.1) {</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>...  |</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">...</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>71 | |         }</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         }</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan>72 | |     }</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>   | |_____^ expected enum `std::option::Option`, found ()</tspan>
+    <tspan x="10px" y="208px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan>
 </tspan>
-    <tspan x="10px" y="226px"><tspan>   |</tspan>
+    <tspan x="10px" y="226px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml
index 09fc7d4..80edfb5 100644
--- a/tests/fixtures/color/fold_ann_multiline.toml
+++ b/tests/fixtures/color/fold_ann_multiline.toml
@@ -39,3 +39,6 @@ range = [5, 19]
 label = "expected enum `std::option::Option`, found ()"
 level = "Error"
 range = [22, 766]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg
index 13a0834..23f1b64 100644
--- a/tests/fixtures/color/fold_bad_origin_line.svg
+++ b/tests/fixtures/color/fold_bad_origin_line.svg
@@ -2,10 +2,14 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
+    .fg-yellow { fill: #AA5500 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,17 +20,17 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> --&gt; path/to/error.rs:3:1</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> path/to/error.rs:3:1</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  |</tspan>
+    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>3 | invalid syntax</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3 |</tspan><tspan> invalid syntax</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  | -------------- error here</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">error here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  |</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml
index 1e81a71..3e40137 100644
--- a/tests/fixtures/color/fold_bad_origin_line.toml
+++ b/tests/fixtures/color/fold_bad_origin_line.toml
@@ -15,3 +15,6 @@ fold = true
 label = "error here"
 level = "Warning"
 range = [2,16]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg
index 72887a2..e69965e 100644
--- a/tests/fixtures/color/fold_leading.svg
+++ b/tests/fixtures/color/fold_leading.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,17 +19,17 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error[E0308]: invalid type: integer `20`, expected a bool</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a bool</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  --&gt; Cargo.toml:11:13</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:11:13</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>   |</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>11 | workspace = 20</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11 |</tspan><tspan> workspace = 20</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   |             ^^</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             </tspan><tspan class="fg-bright-red bold">^^</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   |</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml
index e3fc696..90c6c8c 100644
--- a/tests/fixtures/color/fold_leading.toml
+++ b/tests/fixtures/color/fold_leading.toml
@@ -24,3 +24,6 @@ fold = true
 label = ""
 level = "Error"
 range = [132, 134]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg
index 15c9850..41bf7c7 100644
--- a/tests/fixtures/color/fold_trailing.svg
+++ b/tests/fixtures/color/fold_trailing.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,17 +19,17 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error[E0308]: invalid type: integer `20`, expected a lints table</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a lints table</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:9</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:9</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  |</tspan>
+    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>1 | lints = 20</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> lints = 20</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  |         ^^</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-bright-red bold">^^</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  |</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml
index 8ee4c05..10b2240 100644
--- a/tests/fixtures/color/fold_trailing.toml
+++ b/tests/fixtures/color/fold_trailing.toml
@@ -23,3 +23,6 @@ fold = true
 label = ""
 level = "Error"
 range = [8, 10]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg
index af22d82..80b891e 100644
--- a/tests/fixtures/color/issue_9.svg
+++ b/tests/fixtures/color/issue_9.svg
@@ -2,10 +2,14 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
+    .fg-yellow { fill: #AA5500 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,29 +20,29 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> --&gt; /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  |</tspan>
+    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>4 | let x = vec![1];</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> let x = vec![1];</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  |     - move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  |</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>7 | let y = x;</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> let y = x;</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>  |         - value moved here</tspan>
+    <tspan x="10px" y="154px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>  |</tspan>
+    <tspan x="10px" y="172px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan>9 | x;</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9 |</tspan><tspan> x;</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>  | ^ value used here after move</tspan>
+    <tspan x="10px" y="208px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
 </tspan>
-    <tspan x="10px" y="226px"><tspan>  |</tspan>
+    <tspan x="10px" y="226px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/issue_9.toml b/tests/fixtures/color/issue_9.toml
index 1f35243..9de1753 100644
--- a/tests/fixtures/color/issue_9.toml
+++ b/tests/fixtures/color/issue_9.toml
@@ -26,3 +26,6 @@ line_start = 9
 label = "value used here after move"
 level = "Error"
 range = [0, 1]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg
index 18bca93..84f4749 100644
--- a/tests/fixtures/color/multiple_annotations.svg
+++ b/tests/fixtures/color/multiple_annotations.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,35 +19,35 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>    |</tspan>
+    <tspan x="10px" y="46px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan> 96 | fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
+    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96 |</tspan><tspan> fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan> 97 |     if let Some(annotation) = main_annotation {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97 |</tspan><tspan>     if let Some(annotation) = main_annotation {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>    |                 ^^^^^^^^^^ Variable defined here</tspan>
+    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Variable defined here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan> 98 |         result.push(format_title_line(</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98 |</tspan><tspan>         result.push(format_title_line(</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan> 99 |             &amp;annotation.annotation_type,</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99 |</tspan><tspan>             &amp;annotation.annotation_type,</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>    |              ^^^^^^^^^^ Referenced here</tspan>
+    <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>              </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced here</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>100 |             None,</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">100 |</tspan><tspan>             None,</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan>101 |             &amp;annotation.label,</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">101 |</tspan><tspan>             &amp;annotation.label,</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>    |              ^^^^^^^^^^ Referenced again here</tspan>
+    <tspan x="10px" y="208px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>              </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced again here</tspan>
 </tspan>
-    <tspan x="10px" y="226px"><tspan>102 |         ));</tspan>
+    <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">102 |</tspan><tspan>         ));</tspan>
 </tspan>
-    <tspan x="10px" y="244px"><tspan>103 |     }</tspan>
+    <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">103 |</tspan><tspan>     }</tspan>
 </tspan>
-    <tspan x="10px" y="262px"><tspan>104 | }</tspan>
+    <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">104 |</tspan><tspan> }</tspan>
 </tspan>
-    <tspan x="10px" y="280px"><tspan>    |</tspan>
+    <tspan x="10px" y="280px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml
index 842b137..824c530 100644
--- a/tests/fixtures/color/multiple_annotations.toml
+++ b/tests/fixtures/color/multiple_annotations.toml
@@ -27,3 +27,6 @@ range = [184, 194]
 label = "Referenced again here"
 level = "Error"
 range = [243, 253]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg
index ae7b03c..7b92d23 100644
--- a/tests/fixtures/color/simple.svg
+++ b/tests/fixtures/color/simple.svg
@@ -2,10 +2,14 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
+    .fg-yellow { fill: #AA5500 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,23 +20,23 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>   --&gt; src/format_color.rs:171:9</tspan>
+    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format_color.rs:171:9</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>    |</tspan>
+    <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>169 |         })</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169 |</tspan><tspan>         })</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>    |           - expected one of `.`, `;`, `?`, or an operator here</tspan>
+    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>           </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected one of `.`, `;`, `?`, or an operator here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>170 |</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170 |</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>171 |         for line in &amp;self.body {</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">171 |</tspan><tspan>         for line in &amp;self.body {</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>    |         ^^^ unexpected token</tspan>
+    <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unexpected token</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>    |</tspan>
+    <tspan x="10px" y="172px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/simple.toml b/tests/fixtures/color/simple.toml
index 76b5bac..2e6969f 100644
--- a/tests/fixtures/color/simple.toml
+++ b/tests/fixtures/color/simple.toml
@@ -17,3 +17,6 @@ range = [20, 23]
 label = "expected one of `.`, `;`, `?`, or an operator here"
 level = "Warning"
 range = [10, 11]
+
+[renderer]
+color = true
diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg
index b1fd8a6..9da24fe 100644
--- a/tests/fixtures/color/strip_line.svg
+++ b/tests/fixtures/color/strip_line.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,17 +19,17 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  --&gt; $DIR/whitespace-trimming.rs:4:193</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>   |</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>LL | ...                   let _: () = 42;</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan>                   let _: () = 42;</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   |                                   ^^ expected (), found integer</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   |</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/strip_line.toml b/tests/fixtures/color/strip_line.toml
index 459cbe1..546c96a 100644
--- a/tests/fixtures/color/strip_line.toml
+++ b/tests/fixtures/color/strip_line.toml
@@ -14,5 +14,5 @@ level = "Error"
 range = [192, 194]
 
 [renderer]
-color = false
+color = true
 anonymized_line_numbers = true
diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg
index 15296a1..cbafc78 100644
--- a/tests/fixtures/color/strip_line_char.svg
+++ b/tests/fixtures/color/strip_line_char.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,17 +19,17 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  --&gt; $DIR/whitespace-trimming.rs:4:193</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>   |</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan>LL | ...                   let _: () = 42ñ</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan>                   let _: () = 42ñ</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   |                                   ^^ expected (), found integer</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   |</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml
index dedefd5..863abb3 100644
--- a/tests/fixtures/color/strip_line_char.toml
+++ b/tests/fixtures/color/strip_line_char.toml
@@ -14,5 +14,5 @@ level = "Error"
 range = [192, 194]
 
 [renderer]
-color = false
+color = true
 anonymized_line_numbers = true
diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg
index f1977dc..e4f8a85 100644
--- a/tests/fixtures/color/strip_line_non_ws.svg
+++ b/tests/fixtures/color/strip_line_non_ws.svg
@@ -2,10 +2,13 @@
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
     }
+    .bold { font-weight: bold; }
     tspan {
       font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
       white-space: pre;
@@ -16,21 +19,21 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  --&gt; $DIR/non-whitespace-trimming.rs:4:242</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>   |</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</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 class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () </tspan><tspan class="fg-bright-blue bold">...</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   |                                                  ^^   ^^ expected `()`, found integer</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                                  </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan>   </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected `()`, found integer</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   |                                                  |</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                                  </tspan><tspan class="fg-bright-red bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>   |                                                  expected due to this</tspan>
+    <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                                  </tspan><tspan class="fg-bright-red bold">expected due to this</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>   |</tspan>
+    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml
index 06ecad8..c6573ff 100644
--- a/tests/fixtures/color/strip_line_non_ws.toml
+++ b/tests/fixtures/color/strip_line_non_ws.toml
@@ -23,3 +23,4 @@ range = [236, 238]
 
 [renderer]
 anonymized_line_numbers = true
+color = true
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 3ddef79..a38a88e 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -151,6 +151,8 @@ pub struct RendererDef {
     anonymized_line_numbers: bool,
     #[serde(default)]
     term_width: Option<usize>,
+    #[serde(default)]
+    color: bool,
 }
 
 impl From<RendererDef> for Renderer {
@@ -158,8 +160,15 @@ impl From<RendererDef> for Renderer {
         let RendererDef {
             anonymized_line_numbers,
             term_width,
+            color,
         } = val;
-        Renderer::plain()
+
+        let renderer = if color {
+            Renderer::styled()
+        } else {
+            Renderer::plain()
+        };
+        renderer
             .anonymized_line_numbers(anonymized_line_numbers)
             .term_width(term_width.unwrap_or(DEFAULT_TERM_WIDTH))
     }

From a687aff3a742903c60037f19284c884d4de0bca8 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 21 Aug 2024 15:23:30 -0600
Subject: [PATCH 225/302] test: Use consistent colors when testing

---
 Cargo.lock | 1 +
 Cargo.toml | 1 +
 2 files changed, 2 insertions(+)

diff --git a/Cargo.lock b/Cargo.lock
index a686b02..226775f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,6 +21,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 name = "annotate-snippets"
 version = "0.11.4"
 dependencies = [
+ "annotate-snippets",
  "anstream 0.6.15",
  "anstyle",
  "criterion",
diff --git a/Cargo.toml b/Cargo.toml
index 62ab387..d323686 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,6 +28,7 @@ anstyle = "1.0.4"
 unicode-width = "0.1.11"
 
 [dev-dependencies]
+annotate-snippets = { path = ".", features = ["testing-colors"] }
 anstream = "0.6.13"
 criterion = "0.5.1"
 difference = "2.0.0"

From 40d22784b139254d08750d624cff386d37382a20 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 21 Aug 2024 15:36:10 -0600
Subject: [PATCH 226/302] test: Add module to fixture test name in output

---
 tests/fixtures/main.rs | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index 81d0246..07c69d8 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -14,7 +14,15 @@ fn main() {
 }
 
 fn setup(input_path: std::path::PathBuf) -> tryfn::Case {
-    let name = input_path.file_name().unwrap().to_str().unwrap().to_owned();
+    let parent = input_path
+        .parent()
+        .unwrap()
+        .file_name()
+        .unwrap()
+        .to_str()
+        .unwrap();
+    let file_name = input_path.file_name().unwrap().to_str().unwrap();
+    let name = format!("{}/{}", parent, file_name);
     let expected = Data::read_from(&input_path.with_extension("svg"), None);
     tryfn::Case {
         name,

From 7a28f01acfe8eb95f4e54127e1b66aa31cf2aeed Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 23 Aug 2024 19:02:02 -0500
Subject: [PATCH 227/302] docs(contrib): Fix tpo

---
 CONTRIBUTING.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 87d9134..b0318b8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -45,7 +45,7 @@ We ask that commits are atomic, meaning they are complete and have a single resp
 PRs should tell a cohesive story, with test and refactor commits that keep the
 fix or feature commits simple and clear.
 
-Specifically, we would encouage
+Specifically, we would encourage
 - File renames be isolated into their own commit
 - Add tests in a commit before their feature or fix, showing the current behavior.
   The diff for the feature/fix commit will then show how the behavior changed,

From 37cf1085bc6aa53e18a37f0aa97be51afa6e7f14 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Sep 2024 01:02:47 +0000
Subject: [PATCH 228/302] chore(deps): Update EmbarkStudios/cargo-deny-action
 action to v2

---
 .github/workflows/audit.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index 07c70ee..a94be15 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -47,7 +47,7 @@ jobs:
           - bans licenses sources
     steps:
     - uses: actions/checkout@v4
-    - uses: EmbarkStudios/cargo-deny-action@v1
+    - uses: EmbarkStudios/cargo-deny-action@v2
       with:
         command: check ${{ matrix.checks }}
         rust-version: stable

From 55eccd917e2eadb92d12c7181a7081e4935fbb8f Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Sep 2024 02:08:46 +0000
Subject: [PATCH 229/302] chore(deps): Update EmbarkStudios/cargo-deny-action
 action to v2

---
 .github/workflows/audit.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index 35b3da8..2da233d 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -47,7 +47,7 @@ jobs:
           - bans licenses sources
     steps:
     - uses: actions/checkout@v4
-    - uses: EmbarkStudios/cargo-deny-action@v1
+    - uses: EmbarkStudios/cargo-deny-action@v2
       with:
         command: check ${{ matrix.checks }}
         rust-version: stable

From e5f47698285d84e9e00fc942e1350d670f1ff2cf Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 9 Sep 2024 10:09:09 -0500
Subject: [PATCH 230/302] chore(ci): Exclude dev-dependencies from MSRV

It would be nice to run tests but divan is getting in the way
---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b643aba..f07c714 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -64,7 +64,7 @@ jobs:
     - uses: Swatinem/rust-cache@v2
     - uses: taiki-e/install-action@cargo-hack
     - name: Default features
-      run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets
+      run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --lib --bins
   lockfile:
     runs-on: ubuntu-latest
     steps:

From 4b7a702a9cec57633af564fbf04f88dcc0ee6592 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 9 Sep 2024 09:38:27 -0500
Subject: [PATCH 231/302] bench: Switch to divan

---
 Cargo.lock        | 353 +++++++++++-----------------------------------
 Cargo.toml        |   2 +-
 benches/simple.rs |  22 +--
 3 files changed, 88 insertions(+), 289 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 1e3073f..ffaa43e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,20 +11,14 @@ dependencies = [
  "memchr",
 ]
 
-[[package]]
-name = "anes"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
-
 [[package]]
 name = "annotate-snippets"
 version = "0.11.2"
 dependencies = [
  "anstream 0.6.14",
  "anstyle",
- "criterion",
  "difference",
+ "divan",
  "glob",
  "serde",
  "snapbox",
@@ -130,10 +124,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "autocfg"
-version = "1.3.0"
+name = "bitflags"
+version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bstr"
@@ -145,51 +139,12 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "bumpalo"
-version = "3.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
-
-[[package]]
-name = "cast"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
-
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
-[[package]]
-name = "ciborium"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
-dependencies = [
- "ciborium-io",
- "ciborium-ll",
- "serde",
-]
-
-[[package]]
-name = "ciborium-io"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
-
-[[package]]
-name = "ciborium-ll"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
-dependencies = [
- "ciborium-io",
- "half",
-]
-
 [[package]]
 name = "clap"
 version = "4.3.24"
@@ -211,6 +166,7 @@ dependencies = [
  "anstyle",
  "clap_lex",
  "strsim",
+ "terminal_size",
 ]
 
 [[package]]
@@ -238,83 +194,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
 
 [[package]]
-name = "criterion"
-version = "0.5.1"
+name = "condtype"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
-dependencies = [
- "anes",
- "cast",
- "ciborium",
- "clap",
- "criterion-plot",
- "is-terminal",
- "itertools",
- "num-traits",
- "once_cell",
- "oorandom",
- "plotters",
- "rayon",
- "regex",
- "serde",
- "serde_derive",
- "serde_json",
- "tinytemplate",
- "walkdir",
-]
+checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af"
 
 [[package]]
-name = "criterion-plot"
-version = "0.5.0"
+name = "difference"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
-dependencies = [
- "cast",
- "itertools",
-]
+checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
 
 [[package]]
-name = "crossbeam-deque"
-version = "0.8.5"
+name = "divan"
+version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+checksum = "a0d567df2c9c2870a43f3f2bd65aaeb18dbce1c18f217c3e564b4fbaeb3ee56c"
 dependencies = [
- "crossbeam-epoch",
- "crossbeam-utils",
+ "cfg-if",
+ "clap",
+ "condtype",
+ "divan-macros",
+ "libc",
+ "regex-lite",
 ]
 
 [[package]]
-name = "crossbeam-epoch"
-version = "0.9.18"
+name = "divan-macros"
+version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2"
 dependencies = [
- "crossbeam-utils",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
-name = "crossbeam-utils"
-version = "0.8.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
-
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
-[[package]]
-name = "difference"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
-
-[[package]]
-name = "either"
-version = "1.12.0"
+name = "errno"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
 
 [[package]]
 name = "escape8259"
@@ -356,15 +280,6 @@ dependencies = [
  "regex-syntax",
 ]
 
-[[package]]
-name = "half"
-version = "2.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
-dependencies = [
- "crunchy",
-]
-
 [[package]]
 name = "heck"
 version = "0.4.1"
@@ -403,6 +318,17 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "is-terminal"
 version = "0.4.12"
@@ -423,30 +349,12 @@ dependencies = [
  "is-terminal",
 ]
 
-[[package]]
-name = "itertools"
-version = "0.10.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
-dependencies = [
- "either",
-]
-
 [[package]]
 name = "itoa"
 version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
-[[package]]
-name = "js-sys"
-version = "0.3.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
-dependencies = [
- "wasm-bindgen",
-]
-
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -471,6 +379,12 @@ dependencies = [
  "threadpool",
 ]
 
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
 [[package]]
 name = "log"
 version = "0.4.21"
@@ -489,15 +403,6 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
 
-[[package]]
-name = "num-traits"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
-dependencies = [
- "autocfg",
-]
-
 [[package]]
 name = "num_cpus"
 version = "1.16.0"
@@ -514,12 +419,6 @@ version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
-[[package]]
-name = "oorandom"
-version = "11.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
-
 [[package]]
 name = "os_pipe"
 version = "1.2.0"
@@ -530,34 +429,6 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
-[[package]]
-name = "plotters"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3"
-dependencies = [
- "num-traits",
- "plotters-backend",
- "plotters-svg",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
-name = "plotters-backend"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7"
-
-[[package]]
-name = "plotters-svg"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705"
-dependencies = [
- "plotters-backend",
-]
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.85"
@@ -576,26 +447,6 @@ dependencies = [
  "proc-macro2",
 ]
 
-[[package]]
-name = "rayon"
-version = "1.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
-dependencies = [
- "either",
- "rayon-core",
-]
-
-[[package]]
-name = "rayon-core"
-version = "1.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
-dependencies = [
- "crossbeam-deque",
- "crossbeam-utils",
-]
-
 [[package]]
 name = "regex"
 version = "1.10.4"
@@ -619,12 +470,32 @@ dependencies = [
  "regex-syntax",
 ]
 
+[[package]]
+name = "regex-lite"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
+
 [[package]]
 name = "regex-syntax"
 version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
+[[package]]
+name = "rustix"
+version = "0.37.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "rustversion"
 version = "1.0.17"
@@ -738,6 +609,16 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "terminal_size"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
+dependencies = [
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "thread_local"
 version = "1.1.8"
@@ -757,16 +638,6 @@ dependencies = [
  "num_cpus",
 ]
 
-[[package]]
-name = "tinytemplate"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
-dependencies = [
- "serde",
- "serde_json",
-]
-
 [[package]]
 name = "toml"
 version = "0.5.11"
@@ -830,70 +701,6 @@ dependencies = [
  "winapi-util",
 ]
 
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
-dependencies = [
- "cfg-if",
- "wasm-bindgen-macro",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
-dependencies = [
- "bumpalo",
- "log",
- "once_cell",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
-
-[[package]]
-name = "web-sys"
-version = "0.3.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
 [[package]]
 name = "winapi-util"
 version = "0.1.8"
diff --git a/Cargo.toml b/Cargo.toml
index 12fadf6..c29de95 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,8 +29,8 @@ unicode-width = "0.1.11"
 
 [dev-dependencies]
 anstream = "0.6.13"
-criterion = "0.5.1"
 difference = "2.0.0"
+divan = "0.1.14"
 glob = "0.3.1"
 serde = { version = "1.0.199", features = ["derive"] }
 snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] }
diff --git a/benches/simple.rs b/benches/simple.rs
index 723793e..d5926c2 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -1,12 +1,7 @@
-#![allow(clippy::unit_arg)]
-#[macro_use]
-extern crate criterion;
-
-use criterion::{black_box, Criterion};
-
 use annotate_snippets::{Level, Renderer, Snippet};
 
-fn create_snippet(renderer: Renderer) {
+#[divan::bench]
+fn create_and_render() -> String {
     let source = r#") -> Option<String> {
     for ann in annotations {
         match (ann.range.0, ann.range.1) {
@@ -45,14 +40,11 @@ fn create_snippet(renderer: Renderer) {
             ),
     );
 
-    let _result = renderer.render(message).to_string();
+    let renderer = Renderer::plain();
+    let rendered = renderer.render(message).to_string();
+    rendered
 }
 
-pub fn criterion_benchmark(c: &mut Criterion) {
-    c.bench_function("format", |b| {
-        b.iter(|| black_box(create_snippet(Renderer::plain())));
-    });
+fn main() {
+    divan::main();
 }
-
-criterion_group!(benches, criterion_benchmark);
-criterion_main!(benches);

From da8cc5f2f535788afbbe07e6f7821d9ac91cc394 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 9 Sep 2024 09:42:49 -0500
Subject: [PATCH 232/302] bench: Generalize the binary name

---
 Cargo.toml                      | 2 +-
 benches/{simple.rs => bench.rs} | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
 rename benches/{simple.rs => bench.rs} (97%)

diff --git a/Cargo.toml b/Cargo.toml
index c29de95..2b72bd9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,7 +38,7 @@ toml = "0.5.11"
 tryfn = "0.2.1"
 
 [[bench]]
-name = "simple"
+name = "bench"
 harness = false
 
 [[test]]
diff --git a/benches/simple.rs b/benches/bench.rs
similarity index 97%
rename from benches/simple.rs
rename to benches/bench.rs
index d5926c2..eab7626 100644
--- a/benches/simple.rs
+++ b/benches/bench.rs
@@ -1,7 +1,7 @@
 use annotate_snippets::{Level, Renderer, Snippet};
 
 #[divan::bench]
-fn create_and_render() -> String {
+fn simple() -> String {
     let source = r#") -> Option<String> {
     for ann in annotations {
         match (ann.range.0, ann.range.1) {

From a4cca360fc493abf0ec183ef7203d3bd73ef0ab1 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 9 Sep 2024 09:56:16 -0500
Subject: [PATCH 233/302] bench: Check fold's performance

---
 benches/bench.rs | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/benches/bench.rs b/benches/bench.rs
index eab7626..9747954 100644
--- a/benches/bench.rs
+++ b/benches/bench.rs
@@ -45,6 +45,45 @@ fn simple() -> String {
     rendered
 }
 
+#[divan::bench(args=[0, 1, 10, 100, 1_000, 10_000, 100_000])]
+fn fold(bencher: divan::Bencher<'_, '_>, context: usize) {
+    bencher
+        .with_inputs(|| {
+            let line = "012345678901234567890123456789";
+            let mut input = String::new();
+            for _ in 1..=context {
+                input.push_str(line);
+                input.push('\n');
+            }
+            let span_start = input.len() + line.len();
+            let span = span_start..span_start;
+
+            input.push_str(line);
+            input.push('\n');
+            for _ in 1..=context {
+                input.push_str(line);
+                input.push('\n');
+            }
+            (input, span)
+        })
+        .bench_values(|(input, span)| {
+            let message = Level::Error.title("mismatched types").id("E0308").snippet(
+                Snippet::source(&input)
+                    .fold(true)
+                    .origin("src/format.rs")
+                    .annotation(
+                        Level::Warning
+                            .span(span)
+                            .label("expected `Option<String>` because of return type"),
+                    ),
+            );
+
+            let renderer = Renderer::plain();
+            let rendered = renderer.render(message).to_string();
+            rendered
+        });
+}
+
 fn main() {
     divan::main();
 }

From e8ce092df71abfbc5feab41b8458261e82d94ac2 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 9 Sep 2024 10:03:56 -0500
Subject: [PATCH 234/302] perf: Offer 'simd' feature for faster folding
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

```console
$ cargo bench && cargo bench -F simd
   Compiling annotate-snippets v0.11.2 (/home/epage/src/personal/annotate-snippets-rs)
    Finished `bench` profile [optimized] target(s) in 0.99s
     Running unittests src/lib.rs (target/release/deps/annotate_snippets-b51bb37991a7f496)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running benches/bench.rs (target/release/deps/bench-468ba612503afee1)
Timer precision: 18 ns
bench         fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ fold                     │               │               │               │         │
│  ├─ 0       1.911 µs      │ 19.44 µs      │ 1.943 µs      │ 2.146 µs      │ 100     │ 100
│  ├─ 1       1.916 µs      │ 3.158 µs      │ 1.973 µs      │ 1.982 µs      │ 100     │ 100
│  ├─ 10      2.121 µs      │ 6.05 µs       │ 2.225 µs      │ 2.281 µs      │ 100     │ 100
│  ├─ 100     3.706 µs      │ 7.007 µs      │ 3.83 µs       │ 3.876 µs      │ 100     │ 100
│  ├─ 1000    19.42 µs      │ 25.61 µs      │ 19.48 µs      │ 19.64 µs      │ 100     │ 100
│  ├─ 10000   111.2 µs      │ 204.2 µs      │ 127 µs        │ 133.6 µs      │ 100     │ 100
│  ╰─ 100000  1.094 ms      │ 1.747 ms      │ 1.137 ms      │ 1.158 ms      │ 100     │ 100
╰─ simple     10.14 µs      │ 40.27 µs      │ 10.5 µs       │ 11.01 µs      │ 100     │ 100

   Compiling annotate-snippets v0.11.2 (/home/epage/src/personal/annotate-snippets-rs)
    Finished `bench` profile [optimized] target(s) in 0.99s
     Running unittests src/lib.rs (target/release/deps/annotate_snippets-9d4024ac94675e6a)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running benches/bench.rs (target/release/deps/bench-d5470149969acbb8)
Timer precision: 13 ns
bench         fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ fold                     │               │               │               │         │
│  ├─ 0       1.164 µs      │ 13.91 µs      │ 1.208 µs      │ 1.408 µs      │ 100     │ 100
│  ├─ 1       1.188 µs      │ 4.289 µs      │ 1.234 µs      │ 1.277 µs      │ 100     │ 100
│  ├─ 10      1.259 µs      │ 3.822 µs      │ 1.319 µs      │ 1.419 µs      │ 100     │ 100
│  ├─ 100     1.312 µs      │ 2.732 µs      │ 1.412 µs      │ 1.519 µs      │ 100     │ 100
│  ├─ 1000    1.917 µs      │ 5.52 µs       │ 2 µs          │ 2.085 µs      │ 100     │ 100
│  ├─ 10000   7.195 µs      │ 29.55 µs      │ 7.325 µs      │ 7.638 µs      │ 100     │ 100
│  ╰─ 100000  59.08 µs      │ 403 µs        │ 61.1 µs       │ 65.52 µs      │ 100     │ 100
╰─ simple     9.92 µs       │ 19.09 µs      │ 10.33 µs      │ 10.91 µs      │ 100     │ 100
```
---
 Cargo.lock                   |  5 +++--
 Cargo.toml                   |  2 ++
 src/renderer/display_list.rs | 13 ++++++++++++-
 3 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index ffaa43e..02b8812 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -20,6 +20,7 @@ dependencies = [
  "difference",
  "divan",
  "glob",
+ "memchr",
  "serde",
  "snapbox",
  "toml",
@@ -393,9 +394,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
 
 [[package]]
 name = "memchr"
-version = "2.7.2"
+version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "normalize-line-endings"
diff --git a/Cargo.toml b/Cargo.toml
index 2b72bd9..d59fa9f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,7 @@ maintenance = { status = "actively-developed" }
 
 [dependencies]
 anstyle = "1.0.4"
+memchr = { version = "2.7.4", optional = true }
 unicode-width = "0.1.11"
 
 [dev-dependencies]
@@ -47,6 +48,7 @@ harness = false
 
 [features]
 default = []
+simd = ["memchr"]
 testing-colors = []
 
 [lints.rust]
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index d94a660..b5fef26 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -893,7 +893,7 @@ fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_>
     if let Some(before_new_start) = snippet.source[0..ann_start].rfind('\n') {
         let new_start = before_new_start + 1;
 
-        let line_offset = snippet.source[..new_start].lines().count();
+        let line_offset = newline_count(&snippet.source[..new_start]);
         snippet.line_start += line_offset;
 
         snippet.source = &snippet.source[new_start..];
@@ -919,6 +919,17 @@ fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_>
     snippet
 }
 
+fn newline_count(body: &str) -> usize {
+    #[cfg(feature = "simd")]
+    {
+        memchr::memchr_iter(b'\n', body.as_bytes()).count()
+    }
+    #[cfg(not(feature = "simd"))]
+    {
+        body.lines().count()
+    }
+}
+
 fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
     const INNER_CONTEXT: usize = 1;
     const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1;

From 6e193aa09aed80118df4e1317b8eed057bad6f0b Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 26 Sep 2024 20:59:12 -0500
Subject: [PATCH 235/302] chore: Ensure pre-commit gets non-system Python

This is needed with the ubuntu-24.04 images so that `setup-python` will
install a version of Python that the pre-commit action can install into.

See pre-commit/action#210 for more of an analysis of this.
---
 .github/workflows/pre-commit.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 1b000ab..7b55a3d 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -24,4 +24,6 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - uses: actions/setup-python@v5
+      with:
+        python-version: '3.x'
     - uses: pre-commit/action@v3.0.1

From 67ea82154dac3ebaae1394a9cd4bc4e14f759307 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 27 Sep 2024 10:27:59 -0500
Subject: [PATCH 236/302] style: Use inline format args

---
 src/renderer/display_list.rs | 13 +++++--------
 tests/fixtures/main.rs       |  2 +-
 2 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index fedf268..1823e61 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -198,7 +198,7 @@ impl<'a> DisplaySet<'a> {
             self.format_label(line_offset, &annotation.label, stylesheet, buffer)
         } else {
             let id = match &annotation.id {
-                Some(id) => format!("[{}]", id),
+                Some(id) => format!("[{id}]"),
                 None => String::new(),
             };
             buffer.append(
@@ -290,12 +290,12 @@ impl<'a> DisplaySet<'a> {
             } => {
                 let lineno_color = stylesheet.line_no();
                 if anonymized_line_numbers && lineno.is_some() {
-                    let num = format!("{:>width$} |", ANONYMIZED_LINE_NUM, width = lineno_width);
+                    let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$} |");
                     buffer.puts(line_offset, 0, &num, *lineno_color);
                 } else {
                     match lineno {
                         Some(n) => {
-                            let num = format!("{:>width$} |", n, width = lineno_width);
+                            let num = format!("{n:>lineno_width$} |");
                             buffer.puts(line_offset, 0, &num, *lineno_color);
                         }
                         None => {
@@ -645,7 +645,7 @@ impl<'a> DisplaySet<'a> {
                             } else if formatted_len != 0 {
                                 formatted_len += 2;
                                 let id = match &annotation.annotation.id {
-                                    Some(id) => format!("[{}]", id),
+                                    Some(id) => format!("[{id}]"),
                                     None => String::new(),
                                 };
                                 buffer.puts(
@@ -1292,10 +1292,7 @@ fn format_body(
             None
         }
     }) {
-        panic!(
-            "SourceAnnotation range `{:?}` is beyond the end of buffer `{}`",
-            bigger, source_len
-        )
+        panic!("SourceAnnotation range `{bigger:?}` is beyond the end of buffer `{source_len}`")
     }
 
     let mut body = vec![];
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index 07c69d8..bf37e73 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -22,7 +22,7 @@ fn setup(input_path: std::path::PathBuf) -> tryfn::Case {
         .to_str()
         .unwrap();
     let file_name = input_path.file_name().unwrap().to_str().unwrap();
-    let name = format!("{}/{}", parent, file_name);
+    let name = format!("{parent}/{file_name}");
     let expected = Data::read_from(&input_path.with_extension("svg"), None);
     tryfn::Case {
         name,

From 71039b9430af149e71189b62423b3c508beeb4c7 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 01:02:07 +0000
Subject: [PATCH 237/302] chore(deps): Update compatible (dev)

---
 Cargo.lock | 75 ++++++++++++++++++++++++++++++------------------------
 1 file changed, 42 insertions(+), 33 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 244fffd..7476825 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -521,18 +521,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.204"
+version = "1.0.210"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
+checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.204"
+version = "1.0.210"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
+checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -558,9 +558,9 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
 
 [[package]]
 name = "snapbox"
-version = "0.6.16"
+version = "0.6.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "027c936207f85d10d015e21faf5c676c7e08c453ed371adf55c0874c443ca77a"
+checksum = "840b73eb3148bc3cbc10ebe00ec9bc6d96033e658d022c4adcbf3f35596fd64a"
 dependencies = [
  "anstream 0.6.15",
  "anstyle",
@@ -573,7 +573,7 @@ dependencies = [
  "similar",
  "snapbox-macros",
  "wait-timeout",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -727,7 +727,16 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -747,18 +756,18 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.5",
- "windows_aarch64_msvc 0.52.5",
- "windows_i686_gnu 0.52.5",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
  "windows_i686_gnullvm",
- "windows_i686_msvc 0.52.5",
- "windows_x86_64_gnu 0.52.5",
- "windows_x86_64_gnullvm 0.52.5",
- "windows_x86_64_msvc 0.52.5",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
 ]
 
 [[package]]
@@ -769,9 +778,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -781,9 +790,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -793,15 +802,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
 
 [[package]]
 name = "windows_i686_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -811,9 +820,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -823,9 +832,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -835,9 +844,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -847,6 +856,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

From 1fe2522b7b84554032dc8801e3a164b6caf59383 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 04:05:29 +0000
Subject: [PATCH 238/302] chore(deps): Update dependency STABLE to v1.81.0

---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 87baaab..0888a57 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -86,7 +86,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.80"  # STABLE
+        toolchain: "1.81.0"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -101,7 +101,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.80"  # STABLE
+        toolchain: "1.81.0"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -117,7 +117,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.80"  # STABLE
+        toolchain: "1.81.0"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From 218d7ff4ad0540fd4152d88940e9ff8819783295 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Thu, 10 Oct 2024 16:19:10 -0500
Subject: [PATCH 239/302] chore(ci): Fix CI

---
 .github/workflows/ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2964a8f..1e1e88a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -64,7 +64,7 @@ jobs:
     - uses: Swatinem/rust-cache@v2
     - uses: taiki-e/install-action@cargo-hack
     - name: Default features
-      run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets
+      run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --lib --bins
   minimal-versions:
     name: Minimal versions
     runs-on: ubuntu-latest
@@ -170,7 +170,7 @@ jobs:
     - name: Install cargo-tarpaulin
       run: cargo install cargo-tarpaulin
     - name: Gather coverage
-      run: cargo tarpaulin --output-dir coverage --out lcov
+      run: cargo tarpaulin --output-dir coverage --out lcov --timeout 120
     - name: Publish to Coveralls
       uses: coverallsapp/github-action@master
       with:

From 95657b3536062f9177f72b45158e7c7523b6a553 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 10 Oct 2024 15:43:44 -0600
Subject: [PATCH 240/302] chore: Bump MSRV to 1.66

---
 Cargo.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index 879ce00..870129c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,7 @@ resolver = "2"
 repository = "https://github.com/rust-lang/annotate-snippets-rs"
 license = "MIT OR Apache-2.0"
 edition = "2021"
-rust-version = "1.65.0"  # MSRV
+rust-version = "1.66.0"  # MSRV
 include = [
   "build.rs",
   "src/**/*",

From 928226ce921ee1048bba131218d8cecf5bbe1833 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 10 Oct 2024 15:44:58 -0600
Subject: [PATCH 241/302] chore: Update unicode-width

---
 Cargo.lock | 10 ++++++++--
 Cargo.toml |  2 +-
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 7476825..8218ad6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -26,7 +26,7 @@ dependencies = [
  "snapbox",
  "toml",
  "tryfn",
- "unicode-width",
+ "unicode-width 0.2.0",
 ]
 
 [[package]]
@@ -102,7 +102,7 @@ dependencies = [
  "anstyle",
  "anstyle-lossy",
  "html-escape",
- "unicode-width",
+ "unicode-width 0.1.13",
 ]
 
 [[package]]
@@ -672,6 +672,12 @@ version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
 
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
 [[package]]
 name = "utf8-width"
 version = "0.1.7"
diff --git a/Cargo.toml b/Cargo.toml
index 870129c..5b77b77 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -117,7 +117,7 @@ maintenance = { status = "actively-developed" }
 [dependencies]
 anstyle = "1.0.4"
 memchr = { version = "2.7.4", optional = true }
-unicode-width = "0.1.11"
+unicode-width = "0.2.0"
 
 [dev-dependencies]
 annotate-snippets = { path = ".", features = ["testing-colors"] }

From c08abcc0bcb2a1c39072de299dd7aa0c4bf2a4dc Mon Sep 17 00:00:00 2001
From: Karel Peeters <karel.peeters.leuven@gmail.com>
Date: Sun, 13 Oct 2024 23:44:29 +0200
Subject: [PATCH 242/302] fix: Fix double mistake in EndLine

EndLine::Crlf and EndLine::Lf had wrong lengths, and all usages of
them were also flipped. Both issues happened to cancel out. This
commit fixes both, and so this should be a no-op.
---
 src/renderer/display_list.rs | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 1823e61..e2f0d74 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -940,9 +940,20 @@ impl<'a> CursorLines<'a> {
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub(crate) enum EndLine {
-    Eof = 0,
-    Crlf = 1,
-    Lf = 2,
+    Eof,
+    Lf,
+    Crlf,
+}
+
+impl EndLine {
+    /// The number of characters this line ending occupies in bytes.
+    pub(crate) fn len(self) -> usize {
+        match self {
+            EndLine::Eof => 0,
+            EndLine::Lf => 1,
+            EndLine::Crlf => 2,
+        }
+    }
 }
 
 impl<'a> Iterator for CursorLines<'a> {
@@ -957,12 +968,12 @@ impl<'a> Iterator for CursorLines<'a> {
                 .map(|x| {
                     let ret = if 0 < x {
                         if self.0.as_bytes()[x - 1] == b'\r' {
-                            (&self.0[..x - 1], EndLine::Lf)
+                            (&self.0[..x - 1], EndLine::Crlf)
                         } else {
-                            (&self.0[..x], EndLine::Crlf)
+                            (&self.0[..x], EndLine::Lf)
                         }
                     } else {
-                        ("", EndLine::Crlf)
+                        ("", EndLine::Lf)
                     };
                     self.0 = &self.0[x + 1..];
                     ret
@@ -1138,7 +1149,7 @@ fn format_header<'a>(
                 ..
             } = item
             {
-                if main_range >= range.0 && main_range <= range.1 + *end_line as usize {
+                if main_range >= range.0 && main_range <= range.1 + end_line.len() {
                     let char_column = text[0..(main_range - range.0).min(text.len())]
                         .chars()
                         .count();
@@ -1366,7 +1377,7 @@ fn format_body(
     for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() {
         let line_length: usize = line.len();
         let line_range = (current_index, current_index + line_length);
-        let end_line_size = end_line as usize;
+        let end_line_size = end_line.len();
         body.push(DisplayLine::Source {
             lineno: Some(current_line),
             inline_marks: vec![],

From ee114bc6ec0fdbaaf25b82b6a48720e401b935ab Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 17 Oct 2024 16:08:49 -0600
Subject: [PATCH 243/302] test: Don't borrow when deserializing fixtures

---
 tests/fixtures/deserialize.rs | 99 ++++++++++-------------------------
 tests/fixtures/main.rs        |  6 ++-
 2 files changed, 31 insertions(+), 74 deletions(-)

diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index a38a88e..4dbf341 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -1,36 +1,30 @@
-use serde::{Deserialize, Deserializer, Serialize};
+use serde::Deserialize;
 use std::ops::Range;
 
 use annotate_snippets::renderer::DEFAULT_TERM_WIDTH;
 use annotate_snippets::{Annotation, Level, Message, Renderer, Snippet};
 
 #[derive(Deserialize)]
-pub(crate) struct Fixture<'a> {
+pub(crate) struct Fixture {
     #[serde(default)]
     pub(crate) renderer: RendererDef,
-    #[serde(borrow)]
-    pub(crate) message: MessageDef<'a>,
+    pub(crate) message: MessageDef,
 }
 
 #[derive(Deserialize)]
-pub struct MessageDef<'a> {
+pub struct MessageDef {
     #[serde(with = "LevelDef")]
     pub level: Level,
-    #[serde(borrow)]
-    pub title: &'a str,
+    pub title: String,
     #[serde(default)]
-    #[serde(borrow)]
-    pub id: Option<&'a str>,
+    pub id: Option<String>,
     #[serde(default)]
-    #[serde(borrow)]
-    pub footer: Vec<MessageDef<'a>>,
-    #[serde(deserialize_with = "deserialize_snippets")]
-    #[serde(borrow)]
-    pub snippets: Vec<Snippet<'a>>,
+    pub footer: Vec<MessageDef>,
+    pub snippets: Vec<SnippetDef>,
 }
 
-impl<'a> From<MessageDef<'a>> for Message<'a> {
-    fn from(val: MessageDef<'a>) -> Self {
+impl<'a> From<&'a MessageDef> for Message<'a> {
+    fn from(val: &'a MessageDef) -> Self {
         let MessageDef {
             level,
             title,
@@ -42,43 +36,24 @@ impl<'a> From<MessageDef<'a>> for Message<'a> {
         if let Some(id) = id {
             message = message.id(id);
         }
-        message = message.snippets(snippets);
-        message = message.footers(footer.into_iter().map(Into::into));
+        message = message.snippets(snippets.iter().map(Snippet::from));
+        message = message.footers(footer.iter().map(Into::into));
         message
     }
 }
 
-fn deserialize_snippets<'de, D>(deserializer: D) -> Result<Vec<Snippet<'de>>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    #[derive(Deserialize)]
-    struct Wrapper<'a>(
-        #[serde(with = "SnippetDef")]
-        #[serde(borrow)]
-        SnippetDef<'a>,
-    );
-
-    let v = Vec::deserialize(deserializer)?;
-    Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect())
-}
-
 #[derive(Deserialize)]
-pub struct SnippetDef<'a> {
-    #[serde(borrow)]
-    pub source: &'a str,
+pub struct SnippetDef {
+    pub source: String,
     pub line_start: usize,
-    #[serde(borrow)]
-    pub origin: Option<&'a str>,
-    #[serde(deserialize_with = "deserialize_annotations")]
-    #[serde(borrow)]
-    pub annotations: Vec<Annotation<'a>>,
+    pub origin: Option<String>,
+    pub annotations: Vec<AnnotationDef>,
     #[serde(default)]
     pub fold: bool,
 }
 
-impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
-    fn from(val: SnippetDef<'a>) -> Self {
+impl<'a> From<&'a SnippetDef> for Snippet<'a> {
+    fn from(val: &'a SnippetDef) -> Self {
         let SnippetDef {
             source,
             line_start,
@@ -86,56 +61,36 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
             annotations,
             fold,
         } = val;
-        let mut snippet = Snippet::source(source).line_start(line_start).fold(fold);
+        let mut snippet = Snippet::source(source).line_start(*line_start).fold(*fold);
         if let Some(origin) = origin {
             snippet = snippet.origin(origin);
         }
-        snippet = snippet.annotations(annotations);
+        snippet = snippet.annotations(annotations.iter().map(Into::into));
         snippet
     }
 }
 
-fn deserialize_annotations<'de, D>(deserializer: D) -> Result<Vec<Annotation<'de>>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    #[derive(Deserialize)]
-    struct Wrapper<'a>(#[serde(borrow)] AnnotationDef<'a>);
-
-    let v = Vec::deserialize(deserializer)?;
-    Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect())
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct AnnotationDef<'a> {
+#[derive(Deserialize)]
+pub struct AnnotationDef {
     pub range: Range<usize>,
-    #[serde(borrow)]
-    pub label: &'a str,
+    pub label: String,
     #[serde(with = "LevelDef")]
     pub level: Level,
 }
 
-impl<'a> From<AnnotationDef<'a>> for Annotation<'a> {
-    fn from(val: AnnotationDef<'a>) -> Self {
+impl<'a> From<&'a AnnotationDef> for Annotation<'a> {
+    fn from(val: &'a AnnotationDef) -> Self {
         let AnnotationDef {
             range,
             label,
             level,
         } = val;
-        level.span(range).label(label)
+        level.span(range.start..range.end).label(label)
     }
 }
 
-#[derive(Serialize, Deserialize)]
-pub(crate) struct LabelDef<'a> {
-    #[serde(with = "LevelDef")]
-    pub(crate) level: Level,
-    #[serde(borrow)]
-    pub(crate) label: &'a str,
-}
-
 #[allow(dead_code)]
-#[derive(Serialize, Deserialize)]
+#[derive(Deserialize)]
 #[serde(remote = "Level")]
 enum LevelDef {
     Error,
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index bf37e73..5ff1105 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -33,8 +33,10 @@ fn setup(input_path: std::path::PathBuf) -> tryfn::Case {
 
 fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> {
     let src = std::fs::read_to_string(input_path)?;
-    let (renderer, message): (Renderer, Message<'_>) =
-        toml::from_str(&src).map(|a: Fixture<'_>| (a.renderer.into(), a.message.into()))?;
+    let fixture: Fixture = toml::from_str(&src)?;
+    let renderer: Renderer = fixture.renderer.into();
+    let message: Message<'_> = (&fixture.message).into();
+
     let actual = renderer.render(message).to_string();
     Ok(Data::from(actual).coerce_to(DataFormat::TermSvg))
 }

From 58425bfc95cb3dc53f7295bdfe97d1276f4f52d7 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Thu, 17 Oct 2024 16:12:19 -0600
Subject: [PATCH 244/302] chore(deps): Update Rust crate toml to 0.8.0

---
 Cargo.lock | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 Cargo.toml |  2 +-
 2 files changed, 68 insertions(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 8218ad6..bc7c650 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -232,6 +232,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
 [[package]]
 name = "errno"
 version = "0.3.9"
@@ -282,6 +288,12 @@ dependencies = [
  "regex-syntax",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
+
 [[package]]
 name = "heck"
 version = "0.4.1"
@@ -320,6 +332,16 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "indexmap"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
 [[package]]
 name = "io-lifetimes"
 version = "1.0.11"
@@ -550,6 +572,15 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_spanned"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "similar"
 version = "2.5.0"
@@ -642,11 +673,36 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.5.11"
+version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
 dependencies = [
  "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
 ]
 
 [[package]]
@@ -865,3 +921,12 @@ name = "windows_x86_64_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+dependencies = [
+ "memchr",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 5b77b77..d66ef09 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -127,7 +127,7 @@ divan = "0.1.14"
 glob = "0.3.1"
 serde = { version = "1.0.199", features = ["derive"] }
 snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] }
-toml = "0.5.11"
+toml = "0.8.0"
 tryfn = "0.2.1"
 
 [[bench]]

From 15e503f616b8c17834d7dbcd86169c828780a9d8 Mon Sep 17 00:00:00 2001
From: Karel Peeters <karel.peeters.leuven@gmail.com>
Date: Mon, 14 Oct 2024 00:38:13 +0200
Subject: [PATCH 245/302] test: Add origin location test cases

---
 tests/formatter.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/tests/formatter.rs b/tests/formatter.rs
index 7f914de..ab2eb07 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -905,3 +905,53 @@ error: unused optional dependency
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
+
+#[test]
+fn origin_correct_start_line() {
+    let source = "aaa\nbbb\nccc\nddd\n";
+    let input = Level::Error.title("title").snippet(
+        Snippet::source(source)
+            .origin("origin.txt")
+            .fold(false)
+            .annotation(Level::Error.span(8..8 + 3).label("annotation")),
+    );
+
+    let expected = str![[r#"
+error: title
+ --> origin.txt:2:4
+  |
+1 | aaa
+2 | bbb
+3 | ccc
+  | ^^^ annotation
+4 | ddd
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn origin_correct_mid_line() {
+    let source = "aaa\nbbb\nccc\nddd\n";
+    let input = Level::Error.title("title").snippet(
+        Snippet::source(source)
+            .origin("origin.txt")
+            .fold(false)
+            .annotation(Level::Error.span(8 + 1..8 + 3).label("annotation")),
+    );
+
+    let expected = str![[r#"
+error: title
+ --> origin.txt:3:2
+  |
+1 | aaa
+2 | bbb
+3 | ccc
+  |  ^^ annotation
+4 | ddd
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}

From 695e4284411f9f8b16a60cb193ef1ad678ee0b43 Mon Sep 17 00:00:00 2001
From: Karel Peeters <karel.peeters.leuven@gmail.com>
Date: Mon, 14 Oct 2024 00:39:16 +0200
Subject: [PATCH 246/302] fix: Fix bug in origin location computation

---
 src/renderer/display_list.rs | 2 +-
 tests/formatter.rs           | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index e2f0d74..8884b0d 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1149,7 +1149,7 @@ fn format_header<'a>(
                 ..
             } = item
             {
-                if main_range >= range.0 && main_range <= range.1 + end_line.len() {
+                if main_range >= range.0 && main_range < range.1 + max(*end_line as usize, 1) {
                     let char_column = text[0..(main_range - range.0).min(text.len())]
                         .chars()
                         .count();
diff --git a/tests/formatter.rs b/tests/formatter.rs
index ab2eb07..6faab76 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -918,7 +918,7 @@ fn origin_correct_start_line() {
 
     let expected = str![[r#"
 error: title
- --> origin.txt:2:4
+ --> origin.txt:3:1
   |
 1 | aaa
 2 | bbb

From 8db276fc5ba20df6c0c2f8f0073cbcb2781f1648 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 21 Oct 2024 10:35:12 +0800
Subject: [PATCH 247/302] docs: Cross-reference source code view

---
 Cargo.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index 97c7ed7..0e9372d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -99,7 +99,7 @@ include.workspace = true
 
 [package.metadata.docs.rs]
 all-features = true
-rustdoc-args = ["--cfg", "docsrs"]
+rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
 
 [package.metadata.release]
 pre-release-replacements = [

From e121dd6ef9e11dfa818a813bbaffb12a16cd174e Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 21 Oct 2024 15:39:54 +0800
Subject: [PATCH 248/302] chore(ci): Fix STABLE updates

See rust-lang/cargo#14704
---
 .github/renovate.json5 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/renovate.json5 b/.github/renovate.json5
index c184420..7ab13b9 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -32,7 +32,7 @@
       matchManagers: [
         'custom.regex',
       ],
-      matchPackageNames: [
+      matchDepNames: [
         'STABLE',
       ],
       extractVersion: '^(?<version>\\d+\\.\\d+)',  // Drop the patch version

From 64e1a8ddca879700b8a8139d28cab60f6305e35b Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Nov 2024 00:38:11 +0000
Subject: [PATCH 249/302] chore(deps): Update compatible (dev)

---
 Cargo.lock | 40 ++++++++++++++++++++--------------------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index bc7c650..61af3f7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -16,7 +16,7 @@ name = "annotate-snippets"
 version = "0.11.4"
 dependencies = [
  "annotate-snippets",
- "anstream 0.6.15",
+ "anstream 0.6.17",
  "anstyle",
  "difference",
  "divan",
@@ -46,14 +46,14 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.15"
+version = "0.6.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338"
 dependencies = [
  "anstyle",
  "anstyle-parse",
  "anstyle-query",
- "anstyle-wincon 3.0.3",
+ "anstyle-wincon 3.0.6",
  "colorchoice",
  "is_terminal_polyfill",
  "utf8parse",
@@ -98,7 +98,7 @@ version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa"
 dependencies = [
- "anstream 0.6.15",
+ "anstream 0.6.17",
  "anstyle",
  "anstyle-lossy",
  "html-escape",
@@ -117,12 +117,12 @@ dependencies = [
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.3"
+version = "3.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
+checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
 dependencies = [
  "anstyle",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -259,9 +259,9 @@ dependencies = [
 
 [[package]]
 name = "escargot"
-version = "0.5.11"
+version = "0.5.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "650eb5f6eeda986377996e9ed570cbc20cc16d30440696f82f129c863e4e3e83"
+checksum = "05a3ac187a16b5382fef8c69fd1bad123c67b7cf3932240a2d43dcdd32cded88"
 dependencies = [
  "log",
  "once_cell",
@@ -543,18 +543,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.210"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.210"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -589,11 +589,11 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
 
 [[package]]
 name = "snapbox"
-version = "0.6.17"
+version = "0.6.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "840b73eb3148bc3cbc10ebe00ec9bc6d96033e658d022c4adcbf3f35596fd64a"
+checksum = "881f1849454828a68363dd288b7a0a071e55e2a4356d2c38b567db18a9be0d9f"
 dependencies = [
- "anstream 0.6.15",
+ "anstream 0.6.17",
  "anstyle",
  "anstyle-svg",
  "escargot",
@@ -613,7 +613,7 @@ version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af"
 dependencies = [
- "anstream 0.6.15",
+ "anstream 0.6.17",
 ]
 
 [[package]]
@@ -624,9 +624,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
 [[package]]
 name = "syn"
-version = "2.0.66"
+version = "2.0.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
+checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c"
 dependencies = [
  "proc-macro2",
  "quote",

From 3a79f3a9ee9c9a1cde43331d0fc8305ad71bf5b4 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Nov 2024 03:24:47 +0000
Subject: [PATCH 250/302] chore(deps): Update dependency STABLE to v1.82.0

---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8867b99..cc7fa01 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -104,7 +104,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.81.0"  # STABLE
+        toolchain: "1.82.0"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -119,7 +119,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.81.0"  # STABLE
+        toolchain: "1.82.0"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -135,7 +135,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.81.0"  # STABLE
+        toolchain: "1.82.0"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From afa23ae258a1a6ec9f2268cd0b57c20a88dea3be Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Fri, 1 Nov 2024 15:36:16 -0500
Subject: [PATCH 251/302] style: Ignore large Err variants

---
 Cargo.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Cargo.toml b/Cargo.toml
index 0e9372d..7e38ce3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -70,6 +70,7 @@ rc_mutex = "warn"
 redundant_feature_names = "warn"
 ref_option_ref = "warn"
 rest_pat_in_fully_bound_structs = "warn"
+result_large_err = "allow"
 same_functions_in_if_condition = "warn"
 self_named_module_files = "warn"
 semicolon_if_nothing_returned = "warn"

From 006f98fb3a3e4d4a3054c9fc0ea33906a3e42d44 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 18 Nov 2024 13:48:49 -0600
Subject: [PATCH 252/302] chore(ci): Report deprecations in the review

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d49017e..9fb9591 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -144,7 +144,7 @@ jobs:
       run: cargo install sarif-fmt --locked
     - name: Check
       run: >
-        cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated
+        cargo clippy --workspace --all-features --all-targets --message-format=json
         | clippy-sarif
         | tee clippy-results.sarif
         | sarif-fmt

From 810013723912ca4faf8fbc87fc2ed25b2a531516 Mon Sep 17 00:00:00 2001
From: futreall <86553580+futreall@users.noreply.github.com>
Date: Fri, 29 Nov 2024 16:16:58 +0200
Subject: [PATCH 253/302] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b0318b8..e9fca37 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -49,7 +49,7 @@ Specifically, we would encourage
 - File renames be isolated into their own commit
 - Add tests in a commit before their feature or fix, showing the current behavior.
   The diff for the feature/fix commit will then show how the behavior changed,
-  making it clearer to reviewrs and the community and showing people that the
+  making it clearer to reviewers and the community and showing people that the
   test is verifying the expected state.
   - e.g. [clap#5520](https://github.com/clap-rs/clap/pull/5520)
 

From 9625bc071db30ce53ef960099ac16c868619a3a9 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 00:32:15 +0000
Subject: [PATCH 254/302] chore(deps): Update compatible (dev) (#162)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Cargo.lock | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 61af3f7..524efa4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -16,7 +16,7 @@ name = "annotate-snippets"
 version = "0.11.4"
 dependencies = [
  "annotate-snippets",
- "anstream 0.6.17",
+ "anstream 0.6.18",
  "anstyle",
  "difference",
  "divan",
@@ -46,9 +46,9 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.17"
+version = "0.6.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -98,7 +98,7 @@ version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa"
 dependencies = [
- "anstream 0.6.17",
+ "anstream 0.6.18",
  "anstyle",
  "anstyle-lossy",
  "html-escape",
@@ -209,9 +209,9 @@ checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
 
 [[package]]
 name = "divan"
-version = "0.1.14"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0d567df2c9c2870a43f3f2bd65aaeb18dbce1c18f217c3e564b4fbaeb3ee56c"
+checksum = "ccc40f214f0d9e897cfc72e2edfa5c225d3252f758c537f11ac0a80371c073a6"
 dependencies = [
  "cfg-if",
  "clap",
@@ -223,9 +223,9 @@ dependencies = [
 
 [[package]]
 name = "divan-macros"
-version = "0.1.14"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2"
+checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -543,18 +543,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.214"
+version = "1.0.215"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
+checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.214"
+version = "1.0.215"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
+checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -589,11 +589,11 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
 
 [[package]]
 name = "snapbox"
-version = "0.6.19"
+version = "0.6.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "881f1849454828a68363dd288b7a0a071e55e2a4356d2c38b567db18a9be0d9f"
+checksum = "1373ce406dfad473059bbc31d807715642182bbc952a811952b58d1c9e41dcfa"
 dependencies = [
- "anstream 0.6.17",
+ "anstream 0.6.18",
  "anstyle",
  "anstyle-svg",
  "escargot",
@@ -613,7 +613,7 @@ version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af"
 dependencies = [
- "anstream 0.6.17",
+ "anstream 0.6.18",
 ]
 
 [[package]]

From 7bd4180a4218c46925ca2f9e30a28a10655e65cd Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Mon, 2 Dec 2024 11:04:06 -0600
Subject: [PATCH 255/302] style: Make clippy happy

---
 src/renderer/display_list.rs | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 8884b0d..c2cb82b 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -55,13 +55,13 @@ pub(crate) struct DisplayList<'a> {
     pub(crate) anonymized_line_numbers: bool,
 }
 
-impl<'a> PartialEq for DisplayList<'a> {
+impl PartialEq for DisplayList<'_> {
     fn eq(&self, other: &Self) -> bool {
         self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
     }
 }
 
-impl<'a> fmt::Debug for DisplayList<'a> {
+impl fmt::Debug for DisplayList<'_> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_struct("DisplayList")
             .field("body", &self.body)
@@ -70,7 +70,7 @@ impl<'a> fmt::Debug for DisplayList<'a> {
     }
 }
 
-impl<'a> Display for DisplayList<'a> {
+impl Display for DisplayList<'_> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         let lineno_width = self.body.iter().fold(0, |max, set| {
             set.display_lines.iter().fold(max, |max, line| match line {
@@ -156,7 +156,7 @@ pub(crate) struct DisplaySet<'a> {
     pub(crate) margin: Margin,
 }
 
-impl<'a> DisplaySet<'a> {
+impl DisplaySet<'_> {
     fn format_label(
         &self,
         line_offset: usize,
@@ -791,7 +791,7 @@ pub(crate) struct DisplaySourceAnnotation<'a> {
     pub(crate) annotation_part: DisplayAnnotationPart,
 }
 
-impl<'a> DisplaySourceAnnotation<'a> {
+impl DisplaySourceAnnotation<'_> {
     fn has_label(&self) -> bool {
         !self
             .annotation
@@ -932,7 +932,7 @@ pub(crate) enum DisplayHeaderType {
 
 struct CursorLines<'a>(&'a str);
 
-impl<'a> CursorLines<'a> {
+impl CursorLines<'_> {
     fn new(src: &str) -> CursorLines<'_> {
         CursorLines(src)
     }

From 949ac19b7864a600a2c88d7468b01c9156b300bc Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 2 Dec 2024 17:10:06 +0000
Subject: [PATCH 256/302] chore(deps): Update Rust Stable to v1.83

---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 82108a7..14218fe 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -104,7 +104,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.82.0"  # STABLE
+        toolchain: "1.83"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -119,7 +119,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.82.0"  # STABLE
+        toolchain: "1.83"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -135,7 +135,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.82.0"  # STABLE
+        toolchain: "1.83"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From 30118b02feac986ec73566577796ff27a96a7596 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Mon, 9 Dec 2024 11:12:21 -0700
Subject: [PATCH 257/302] chore: Make master the allowed release branch

---
 release.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/release.toml b/release.toml
index 160b061..f74b710 100644
--- a/release.toml
+++ b/release.toml
@@ -1,2 +1,2 @@
 dependent-version = "fix"
-allow-branch = ["main"]
+allow-branch = ["master"]

From 286cce0b3ffe0caabeed6dd29dc48704654cf413 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Mon, 9 Dec 2024 11:19:01 -0700
Subject: [PATCH 258/302] docs: Update changelog

---
 CHANGELOG.md | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c7d2d2..8e42b16 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+### Added
+
+- `rustc`'s multiline annotation special case [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133)
+  - This special case happens when:
+    - The start of a multiline annotation is at the start of the line disregarding any leading whitespace
+    - No other multiline annotations overlap it
+- `simd` feature for faster folding [#146](https://github.com/rust-lang/annotate-snippets-rs/pull/146)
+
+### Changed
+
+- Multiline annotations with matching spans get merged [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133)
+- Multiple annotations on one line are no longer rendered on separate lines [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133)
+
+### Fixed
+
+- Overlapping multiline annotations are now correctly rendered [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133)
+- Origin position is now correctly calculated when an annotation starts at the beginning of the line [#154](https://github.com/rust-lang/annotate-snippets-rs/pull/154)
+
 ## [0.11.4] - 2024-06-15
 
 ### Fixes

From 72dd8c7b9210bace1be990e3e3018fab46fd8291 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Mon, 9 Dec 2024 11:20:24 -0700
Subject: [PATCH 259/302] chore: Release annotate-snippets version 0.11.5

---
 CHANGELOG.md | 5 ++++-
 Cargo.lock   | 2 +-
 Cargo.toml   | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e42b16..ed078cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
+## [0.11.5] - 2024-12-09
+
 ### Added
 
 - `rustc`'s multiline annotation special case [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133)
@@ -162,7 +164,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Update the syntax to Rust 2018 idioms. (#4)
 
 <!-- next-url -->
-[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.4...HEAD
+[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.5...HEAD
+[0.11.5]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.4...0.11.5
 [0.11.4]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.3...0.11.4
 [0.11.3]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...0.11.3
 [0.11.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...0.11.2
diff --git a/Cargo.lock b/Cargo.lock
index 524efa4..b2561b2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13,7 +13,7 @@ dependencies = [
 
 [[package]]
 name = "annotate-snippets"
-version = "0.11.4"
+version = "0.11.5"
 dependencies = [
  "annotate-snippets",
  "anstream 0.6.18",
diff --git a/Cargo.toml b/Cargo.toml
index 0b03d94..f30c64f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -88,7 +88,7 @@ zero_sized_map_values = "warn"
 
 [package]
 name = "annotate-snippets"
-version = "0.11.4"
+version = "0.11.5"
 description = "Library for building code annotations"
 categories = []
 keywords = ["code", "analysis", "ascii", "errors", "debug"]

From 7132bf31ce6fc004911ceed9caab1c02dda23b24 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 1 Jan 2025 02:28:36 +0000
Subject: [PATCH 260/302] chore(deps): Update compatible (dev) (#168)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Cargo.lock | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index b2561b2..acadd7b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -209,9 +209,9 @@ checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
 
 [[package]]
 name = "divan"
-version = "0.1.16"
+version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccc40f214f0d9e897cfc72e2edfa5c225d3252f758c537f11ac0a80371c073a6"
+checksum = "e0583193020b29b03682d8d33bb53a5b0f50df6daacece12ca99b904cfdcb8c4"
 dependencies = [
  "cfg-if",
  "clap",
@@ -223,9 +223,9 @@ dependencies = [
 
 [[package]]
 name = "divan-macros"
-version = "0.1.16"
+version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e"
+checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -271,9 +271,9 @@ dependencies = [
 
 [[package]]
 name = "glob"
-version = "0.3.1"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
 
 [[package]]
 name = "globset"
@@ -543,18 +543,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.215"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.215"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -589,9 +589,9 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
 
 [[package]]
 name = "snapbox"
-version = "0.6.20"
+version = "0.6.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1373ce406dfad473059bbc31d807715642182bbc952a811952b58d1c9e41dcfa"
+checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b"
 dependencies = [
  "anstream 0.6.18",
  "anstyle",

From 32dc46465e7b76295a3dc29c3e89b3e3f9f936aa Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 9 Jan 2025 18:23:37 +0000
Subject: [PATCH 261/302] chore(deps): Update Rust Stable to v1.84 (#173)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 14218fe..2073635 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -104,7 +104,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.83"  # STABLE
+        toolchain: "1.84"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -119,7 +119,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.83"  # STABLE
+        toolchain: "1.84"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -135,7 +135,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.83"  # STABLE
+        toolchain: "1.84"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From fa2f56ad2a19018eba2f18af494e8b4fc6ec1cf7 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 12 Feb 2025 09:34:02 -0600
Subject: [PATCH 262/302] fix: Remove trailing pipe

---
 examples/expected_type.svg                    |  6 +--
 examples/footer.svg                           | 10 ++--
 examples/format.svg                           |  6 +--
 examples/multislice.svg                       | 14 ++----
 src/renderer/display_list.rs                  | 20 --------
 tests/fixtures/color/ann_eof.svg              |  4 +-
 tests/fixtures/color/ann_insertion.svg        |  4 +-
 tests/fixtures/color/ann_multiline.svg        |  4 +-
 tests/fixtures/color/ann_multiline2.svg       |  4 +-
 tests/fixtures/color/ann_removed_nl.svg       |  4 +-
 .../color/ensure-emoji-highlight-width.svg    |  4 +-
 tests/fixtures/color/fold_ann_multiline.svg   |  4 +-
 tests/fixtures/color/fold_bad_origin_line.svg |  4 +-
 tests/fixtures/color/fold_leading.svg         |  4 +-
 tests/fixtures/color/fold_trailing.svg        |  4 +-
 tests/fixtures/color/issue_9.svg              | 16 ++-----
 tests/fixtures/color/multiple_annotations.svg |  4 +-
 tests/fixtures/color/simple.svg               |  4 +-
 tests/fixtures/color/strip_line.svg           |  4 +-
 tests/fixtures/color/strip_line_char.svg      |  4 +-
 tests/fixtures/color/strip_line_non_ws.svg    |  4 +-
 tests/formatter.rs                            | 48 ++++---------------
 tests/rustc_tests.rs                          | 22 ---------
 23 files changed, 42 insertions(+), 160 deletions(-)

diff --git a/examples/expected_type.svg b/examples/expected_type.svg
index ed19ef3..d5de44f 100644
--- a/examples/expected_type.svg
+++ b/examples/expected_type.svg
@@ -1,4 +1,4 @@
-<svg width="860px" height="218px" xmlns="http://www.w3.org/2000/svg">
+<svg width="860px" height="200px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -37,9 +37,7 @@
 </tspan>
     <tspan x="10px" y="172px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                         </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
-</tspan>
-    <tspan x="10px" y="208px">
+    <tspan x="10px" y="190px">
 </tspan>
   </text>
 
diff --git a/examples/footer.svg b/examples/footer.svg
index 76e7d77..ab9e4df 100644
--- a/examples/footer.svg
+++ b/examples/footer.svg
@@ -1,4 +1,4 @@
-<svg width="844px" height="182px" xmlns="http://www.w3.org/2000/svg">
+<svg width="844px" height="164px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -30,13 +30,11 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                      </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
+    <tspan x="10px" y="136px"><tspan>              found type: `__&amp;__snippet::Annotation`</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>              found type: `__&amp;__snippet::Annotation`</tspan>
-</tspan>
-    <tspan x="10px" y="172px">
+    <tspan x="10px" y="154px">
 </tspan>
   </text>
 
diff --git a/examples/format.svg b/examples/format.svg
index ac196c0..0e05457 100644
--- a/examples/format.svg
+++ b/examples/format.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="542px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="524px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -74,9 +74,7 @@
 </tspan>
     <tspan x="10px" y="496px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan>
 </tspan>
-    <tspan x="10px" y="514px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
-</tspan>
-    <tspan x="10px" y="532px">
+    <tspan x="10px" y="514px">
 </tspan>
   </text>
 
diff --git a/examples/multislice.svg b/examples/multislice.svg
index 216a359..92ff9df 100644
--- a/examples/multislice.svg
+++ b/examples/multislice.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="200px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -27,17 +27,13 @@
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51 |</tspan><tspan> Foo</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan>
+    <tspan x="10px" y="118px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan>
-</tspan>
-    <tspan x="10px" y="172px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
-</tspan>
-    <tspan x="10px" y="190px">
+    <tspan x="10px" y="154px">
 </tspan>
   </text>
 
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index c2cb82b..460f985 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -20,7 +20,6 @@
 //! 152 | |       return "test";
 //! 153 | |   }
 //!     | |___^ error: expected `String`, for `&str`.
-//!     |
 //! ```
 //!
 //! The first two lines of the example above are `Raw` lines, while the rest
@@ -1013,7 +1012,6 @@ fn format_message(
         sets.push(format_snippet(
             snippet,
             idx == 0,
-            !footer.is_empty(),
             term_width,
             anonymized_line_numbers,
         ));
@@ -1092,7 +1090,6 @@ fn format_label(
 fn format_snippet(
     snippet: snippet::Snippet<'_>,
     is_first: bool,
-    has_footer: bool,
     term_width: usize,
     anonymized_line_numbers: bool,
 ) -> DisplaySet<'_> {
@@ -1102,7 +1099,6 @@ fn format_snippet(
     let mut body = format_body(
         snippet,
         need_empty_header,
-        has_footer,
         term_width,
         anonymized_line_numbers,
     );
@@ -1290,7 +1286,6 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
 fn format_body(
     snippet: snippet::Snippet<'_>,
     need_empty_header: bool,
-    has_footer: bool,
     term_width: usize,
     anonymized_line_numbers: bool,
 ) -> DisplaySet<'_> {
@@ -1605,21 +1600,6 @@ fn format_body(
         );
     }
 
-    if has_footer {
-        body.push(DisplayLine::Source {
-            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![],
-        });
-    }
     let max_line_num_len = if anonymized_line_numbers {
         ANONYMIZED_LINE_NUM.len()
     } else {
diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg
index bb12aec..b0fb8b6 100644
--- a/tests/fixtures/color/ann_eof.svg
+++ b/tests/fixtures/color/ann_eof.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,8 +28,6 @@
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-red bold">^</tspan>
-</tspan>
-    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg
index 1f4b6a2..35d65a0 100644
--- a/tests/fixtures/color/ann_insertion.svg
+++ b/tests/fixtures/color/ann_insertion.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,8 +28,6 @@
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asf</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>   </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">'d' belongs here</tspan>
-</tspan>
-    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg
index 9130691..949eddc 100644
--- a/tests/fixtures/color/ann_multiline.svg
+++ b/tests/fixtures/color/ann_multiline.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -34,8 +34,6 @@
     <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                         } = body[body_idx]</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_________________________^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">missing fields `lineno`, `content`</tspan>
-</tspan>
-    <tspan x="10px" y="172px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg
index 97948a4..064826a 100644
--- a/tests/fixtures/color/ann_multiline2.svg
+++ b/tests/fixtures/color/ann_multiline2.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -32,8 +32,6 @@
     <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> of an edge case of an annotation overflowing</tspan>
 </tspan>
     <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> to exactly one character on next line.</tspan>
-</tspan>
-    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg
index bb12aec..b0fb8b6 100644
--- a/tests/fixtures/color/ann_removed_nl.svg
+++ b/tests/fixtures/color/ann_removed_nl.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,8 +28,6 @@
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-red bold">^</tspan>
-</tspan>
-    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg
index e5646e6..077bca2 100644
--- a/tests/fixtures/color/ensure-emoji-highlight-width.svg
+++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg
@@ -1,4 +1,4 @@
-<svg width="1356px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="1356px" height="110px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,8 +28,6 @@
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan>
-</tspan>
-    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg
index 6a89c4f..39323c5 100644
--- a/tests/fixtures/color/fold_ann_multiline.svg
+++ b/tests/fixtures/color/fold_ann_multiline.svg
@@ -1,4 +1,4 @@
-<svg width="869px" height="236px" xmlns="http://www.w3.org/2000/svg">
+<svg width="869px" height="218px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -41,8 +41,6 @@
     <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
 </tspan>
     <tspan x="10px" y="208px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan>
-</tspan>
-    <tspan x="10px" y="226px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg
index 23f1b64..c7fb916 100644
--- a/tests/fixtures/color/fold_bad_origin_line.svg
+++ b/tests/fixtures/color/fold_bad_origin_line.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -29,8 +29,6 @@
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3 |</tspan><tspan> invalid syntax</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">error here</tspan>
-</tspan>
-    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg
index e69965e..0ff7d15 100644
--- a/tests/fixtures/color/fold_leading.svg
+++ b/tests/fixtures/color/fold_leading.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,8 +28,6 @@
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11 |</tspan><tspan> workspace = 20</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             </tspan><tspan class="fg-bright-red bold">^^</tspan>
-</tspan>
-    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg
index 41bf7c7..ca9de40 100644
--- a/tests/fixtures/color/fold_trailing.svg
+++ b/tests/fixtures/color/fold_trailing.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,8 +28,6 @@
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> lints = 20</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-bright-red bold">^^</tspan>
-</tspan>
-    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg
index 80b891e..05e421e 100644
--- a/tests/fixtures/color/issue_9.svg
+++ b/tests/fixtures/color/issue_9.svg
@@ -1,4 +1,4 @@
-<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg">
+<svg width="911px" height="182px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -30,19 +30,13 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> let y = x;</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> let y = x;</tspan>
+    <tspan x="10px" y="136px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">9 |</tspan><tspan> x;</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
-</tspan>
-    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9 |</tspan><tspan> x;</tspan>
-</tspan>
-    <tspan x="10px" y="208px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
-</tspan>
-    <tspan x="10px" y="226px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="172px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg
index 84f4749..fc6fe68 100644
--- a/tests/fixtures/color/multiple_annotations.svg
+++ b/tests/fixtures/color/multiple_annotations.svg
@@ -1,4 +1,4 @@
-<svg width="768px" height="290px" xmlns="http://www.w3.org/2000/svg">
+<svg width="768px" height="272px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -46,8 +46,6 @@
     <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">103 |</tspan><tspan>     }</tspan>
 </tspan>
     <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">104 |</tspan><tspan> }</tspan>
-</tspan>
-    <tspan x="10px" y="280px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg
index 7b92d23..210cf34 100644
--- a/tests/fixtures/color/simple.svg
+++ b/tests/fixtures/color/simple.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -35,8 +35,6 @@
     <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">171 |</tspan><tspan>         for line in &amp;self.body {</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unexpected token</tspan>
-</tspan>
-    <tspan x="10px" y="172px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg
index 9da24fe..f86250a 100644
--- a/tests/fixtures/color/strip_line.svg
+++ b/tests/fixtures/color/strip_line.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,8 +28,6 @@
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan>                   let _: () = 42;</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan>
-</tspan>
-    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg
index cbafc78..ed9ba73 100644
--- a/tests/fixtures/color/strip_line_char.svg
+++ b/tests/fixtures/color/strip_line_char.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,8 +28,6 @@
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan>                   let _: () = 42ñ</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan>
-</tspan>
-    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg
index e4f8a85..251dfca 100644
--- a/tests/fixtures/color/strip_line_non_ws.svg
+++ b/tests/fixtures/color/strip_line_non_ws.svg
@@ -1,4 +1,4 @@
-<svg width="1196px" height="164px" 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 }
@@ -32,8 +32,6 @@
     <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                                  </tspan><tspan class="fg-bright-red bold">|</tspan>
 </tspan>
     <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                                  </tspan><tspan class="fg-bright-red bold">expected due to this</tspan>
-</tspan>
-    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
   </text>
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 6faab76..7aa37d2 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -16,7 +16,6 @@ error: oops
   |
 2 | Second oops line
   |        ^^^^ oops
-  |
 "#]];
 
     let renderer = Renderer::plain();
@@ -37,7 +36,6 @@ error
   |
 1 | こんにちは、世界
   |             ^^^^ world
-  |
 "#]];
 
     let renderer = Renderer::plain();
@@ -60,7 +58,6 @@ error
   |  _____^
 2 | | ございます
   | |______^ Good morning
-  |
 "#]];
 
     let renderer = Renderer::plain();
@@ -84,7 +81,6 @@ error
   | ^^^^^^ Sushi1
 2 | 食べたい🍣
   |     ---- note: Sushi2
-  |
 "#]];
 
     let renderer = Renderer::plain();
@@ -105,7 +101,6 @@ error
   |
 1 | こんにちは、新しいWorld!
   |             ^^^^^^^^^^^ New world
-  |
 "#]];
 
     let renderer = Renderer::plain();
@@ -133,7 +128,6 @@ error
      |
 5402 | This is line 1
 5403 | This is line 2
-     |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -152,11 +146,9 @@ error
     --> file1.rs
      |
 5402 | This is slice 1
-     |
     ::: file2.rs
      |
    2 | This is slice 2
-     |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -180,7 +172,6 @@ error
 5402 | This is line 1
 5403 | This is line 2
      |        -- info: Test annotation
-     |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -224,7 +215,6 @@ error
    |
 56 | This is an example
 57 | of content lines
-   |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -243,7 +233,6 @@ error
   |
 1 | tests
   | ----- help: Example string
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -266,7 +255,6 @@ error
   | |
   | help: Example string
   | help: Second line
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -281,7 +269,6 @@ fn test_only_source() {
 error
 --> file.rs
  |
- |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -300,7 +287,6 @@ LL | This is an example
 LL | of content lines
 LL |
 LL | abc
-   |
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(true);
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -323,7 +309,6 @@ error: dummy
 4 | / bar
 5 | | baz
   | |___^
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -349,7 +334,6 @@ error
 3 | / a"
 4 | | // ...
   | |_______^
-  |
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -371,7 +355,7 @@ error
 3 | a
   | ^
 4 | b
-  |"#]];
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -392,7 +376,7 @@ error
 3 | a
   | ^
 4 | b
-  |"#]];
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -413,7 +397,6 @@ error
   |   ^^
 2 | にちは
 3 | 世界
-  |
 "#]];
 
     let renderer = Renderer::plain();
@@ -436,7 +419,7 @@ error
 3 | a
   |  ^
 4 | b
-  |"#]];
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -457,7 +440,7 @@ error
 3 | a
   |  ^
 4 | b
-  |"#]];
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -478,7 +461,7 @@ error
 3 | a
   |  ^
 4 | b
-  |"#]];
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -499,7 +482,7 @@ error
 3 | a
   |  ^
 4 | b
-  |"#]];
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -520,7 +503,6 @@ error
   |     ^
 2 | にちは
 3 | 世界
-  |
 "#]];
 
     let renderer = Renderer::plain();
@@ -544,7 +526,7 @@ error
   |  __^
 4 | | b
   | |_^
-  |"#]];
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -566,7 +548,7 @@ error
   |  __^
 4 | | b
   | |_^
-  |"#]];
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -588,7 +570,7 @@ error
   |  __^
 4 | | b
   | |_^
-  |"#]];
+"#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
@@ -610,7 +592,6 @@ error
 2 | | にちは
   | |__^
 3 |   世界
-  |
 "#]];
 
     let renderer = Renderer::plain();
@@ -635,7 +616,6 @@ error
 4 | | b
   | |__^
 5 |   c
-  |
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -659,7 +639,6 @@ error
 4 | | b
   | |__^
 5 |   c
-  |
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -683,7 +662,6 @@ error
 4 | | b
   | |__^
 5 |   c
-  |
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -706,7 +684,6 @@ error
   |  __^
 4 | | b
   | |__^
-  |
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -729,7 +706,6 @@ error
   |  ___^
 4 | | に
   | |___^
-  |
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -761,7 +737,6 @@ error: unused optional dependency
   | ^^^                        --------------- info: This should also be long but not too long
   | |
   | I need this to be really long so I can test overlaps
-  |
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -799,7 +774,6 @@ error: unused optional dependency
 6 | | so is this
 7 | | bar = { version = "0.1.0", optional = true }
   | |__________________________________________^ I need this to be really long so I can test overlaps
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -845,7 +819,6 @@ error: unused optional dependency
   | ||_________________________^________________^ I need this to be really long so I can test overlaps
   | |__________________________|
   |                            I need this to be really long so I can test overlaps
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -900,7 +873,6 @@ error: unused optional dependency
   |   |                         I need this to be really long so I can test overlaps
 8 |   | this is another line
   |   |____^ I need this to be really long so I can test overlaps
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -925,7 +897,6 @@ error: title
 3 | ccc
   | ^^^ annotation
 4 | ddd
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -950,7 +921,6 @@ error: title
 3 | ccc
   |  ^^ annotation
 4 | ddd
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index 54b7321..db17bf4 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -28,7 +28,6 @@ error: foo
   |  __________^
 3 | | }
   | |_^ test
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -59,7 +58,6 @@ error: foo
 4 | |
 5 | |   }
   | |___^ test
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -98,7 +96,6 @@ error: foo
   | ||____^__- `Y` is a good letter too
   | |_____|
   |       `X` is a good letter
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -135,7 +132,6 @@ error: foo
   | ||____-__^ `X` is a good letter
   |  |____|
   |       `Y` is a good letter too
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -175,7 +171,6 @@ error: foo
   | ||____^ `X` is a good letter
 6 |  |   X3 Y3 Z3
   |  |____- `Y` is a good letter too
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -217,7 +212,6 @@ error: foo
   | ||_____|__|
   | |______|  `Y` is a good letter too
   |        `X` is a good letter
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -259,7 +253,6 @@ error: foo
   |      `X` is a good letter
   |      `Y` is a good letter too
   |      `Z` label
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -304,7 +297,6 @@ error: foo
   |  |
 6 |  |   X3 Y3 Z3
   |  |_______- `Z`
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -343,7 +335,6 @@ error: foo
   |  ______-
 6 | |   X3 Y3 Z3
   | |__________- `Y` is a good letter too
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -384,7 +375,6 @@ error: foo
 5 |  |   X2 Y2 Z2
 6 |  |   X3 Y3 Z3
   |  |__________- `Y` is a good letter too
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -412,7 +402,6 @@ error: foo
   |
 3 |   a { b { c } d }
   |   ----^^^^-^^-- `a` is a good letter
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -439,7 +428,6 @@ error: foo
   |
 3 |   a { b { c } d }
   |   ^^^^-------^^ `a` is a good letter
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -469,7 +457,6 @@ error: foo
   |   ----^^^^-^^--
   |       |
   |       `b` is a good letter
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -498,7 +485,6 @@ error: foo
   |   ^^^^-------^^
   |       |
   |       `b` is a good letter
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -527,7 +513,6 @@ error: foo
   |   ^^^^----
   |   |
   |   `a` is a good letter
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -554,7 +539,6 @@ error: foo
   |
 3 |   a { b { c } d }
   |   ^^^^-------^^
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -582,7 +566,6 @@ error: foo
   |
 3 |   a { b { c } d }
   |   ----^^^^-^^--
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -612,7 +595,6 @@ error: foo
   |   |   |
   |   |   `b` is a good letter
   |   `a` is a good letter
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -638,7 +620,6 @@ error: foo
   |
 3 |   a { b { c } d }
   |   ^^^^^^^^^^^^^ `a` is a good letter
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -664,7 +645,6 @@ error: foo
   |
 3 |   a { b { c } d }
   |   ^^^^^^^^^^^^^
-  |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -717,7 +697,6 @@ error: foo
 15 |  |   X2 Y2 Z2
 16 |  |   X3 Y3 Z3
    |  |__________- `Y` is a good letter too
-   |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
@@ -776,7 +755,6 @@ error: foo
 15 | |  10
 16 | |    X3 Y3 Z3
    | |________^ `Y` is a good letter
-   |
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);

From 41825aadd7c66663cc320206637d333fb4651ba7 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 12 Feb 2025 09:37:49 -0600
Subject: [PATCH 263/302] style: Make clippy happy

---
 src/renderer/display_list.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 460f985..48fbdd0 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -324,7 +324,7 @@ impl DisplaySet<'_> {
                     }
 
                     let text = normalize_whitespace(text);
-                    let line_len = text.as_bytes().len();
+                    let line_len = text.len();
                     let left = self.margin.left(line_len);
                     let right = self.margin.right(line_len);
 

From 5ff020156ae6a49c522f993d6db282340a34b226 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 20 Feb 2025 18:46:46 +0000
Subject: [PATCH 264/302] chore(deps): Update Rust Stable to v1.85 (#180)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2073635..83a404c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -104,7 +104,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.84"  # STABLE
+        toolchain: "1.85"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -119,7 +119,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.84"  # STABLE
+        toolchain: "1.85"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -135,7 +135,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.84"  # STABLE
+        toolchain: "1.85"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From 5ff8d2c99b632deab5073f9fbf48b8f5650b6a92 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 1 Mar 2025 01:26:04 +0000
Subject: [PATCH 265/302] chore(deps): Update compatible (dev) (#181)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Cargo.lock | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index acadd7b..a3b2f06 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -543,18 +543,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.217"
+version = "1.0.218"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
+checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.217"
+version = "1.0.218"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
+checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -673,9 +673,9 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.8.19"
+version = "0.8.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
+checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
 dependencies = [
  "serde",
  "serde_spanned",
@@ -694,9 +694,9 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.22.22"
+version = "0.22.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
+checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
 dependencies = [
  "indexmap",
  "serde",
@@ -924,9 +924,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winnow"
-version = "0.6.20"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
 dependencies = [
  "memchr",
 ]

From a20d3264785d5700c5aa6d076d6f5f310b9afd8b Mon Sep 17 00:00:00 2001
From: Dhruv Manilawala <dhruvmanila@gmail.com>
Date: Mon, 3 Mar 2025 13:06:11 +0530
Subject: [PATCH 266/302] chore: Fix typos

---
 src/renderer/display_list.rs | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 48fbdd0..53184f2 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1230,25 +1230,25 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
     const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1;
 
     let mut lines = vec![];
-    let mut unhighlighed_lines = vec![];
+    let mut unhighlighted_lines = vec![];
     for line in body {
         match &line {
             DisplayLine::Source { annotations, .. } => {
                 if annotations.is_empty() {
-                    unhighlighed_lines.push(line);
+                    unhighlighted_lines.push(line);
                 } else {
                     if lines.is_empty() {
-                        // Ignore leading unhighlighed lines
-                        unhighlighed_lines.clear();
+                        // Ignore leading unhighlighted lines
+                        unhighlighted_lines.clear();
                     }
-                    match unhighlighed_lines.len() {
+                    match unhighlighted_lines.len() {
                         0 => {}
                         n if n <= INNER_UNFOLD_SIZE => {
                             // Rather than render `...`, don't fold
-                            lines.append(&mut unhighlighed_lines);
+                            lines.append(&mut unhighlighted_lines);
                         }
                         _ => {
-                            lines.extend(unhighlighed_lines.drain(..INNER_CONTEXT));
+                            lines.extend(unhighlighted_lines.drain(..INNER_CONTEXT));
                             let inline_marks = lines
                                 .last()
                                 .and_then(|line| {
@@ -1266,16 +1266,16 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
                             lines.push(DisplayLine::Fold {
                                 inline_marks: inline_marks.clone(),
                             });
-                            unhighlighed_lines
-                                .drain(..unhighlighed_lines.len().saturating_sub(INNER_CONTEXT));
-                            lines.append(&mut unhighlighed_lines);
+                            unhighlighted_lines
+                                .drain(..unhighlighted_lines.len().saturating_sub(INNER_CONTEXT));
+                            lines.append(&mut unhighlighted_lines);
                         }
                     }
                     lines.push(line);
                 }
             }
             _ => {
-                unhighlighed_lines.push(line);
+                unhighlighted_lines.push(line);
             }
         }
     }

From eb151419e93ec776f50795b0a5f81e050f7d2dc2 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Apr 2025 01:36:45 +0000
Subject: [PATCH 267/302] chore(deps): Update Rust crate serde to v1.0.219
 (#193)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Cargo.lock | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index a3b2f06..c8c4d67 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -543,18 +543,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.218"
+version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.218"
+version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
 dependencies = [
  "proc-macro2",
  "quote",

From 5e302a9f188fd003521f0f74dac6a7b401dc3600 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 3 Apr 2025 12:17:10 +0000
Subject: [PATCH 268/302] chore(deps): Update Rust Stable to v1.86 (#194)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 83a404c..aa6d8bb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -104,7 +104,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.85"  # STABLE
+        toolchain: "1.86"  # STABLE
     - uses: Swatinem/rust-cache@v2
     - name: Check documentation
       env:
@@ -119,7 +119,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.85"  # STABLE
+        toolchain: "1.86"  # STABLE
         components: rustfmt
     - uses: Swatinem/rust-cache@v2
     - name: Check formatting
@@ -135,7 +135,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: "1.85"  # STABLE
+        toolchain: "1.86"  # STABLE
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools

From a2ce03e1b5672eb8477805a474e31ebada1cb749 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 15 Apr 2025 04:26:32 -0600
Subject: [PATCH 269/302] test: Add more tests from rustc

---
 tests/rustc_tests.rs | 947 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 947 insertions(+)

diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index db17bf4..f10f479 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -759,3 +759,950 @@ error: foo
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
+
+#[test]
+fn issue_91334() {
+    let source = r#"// Regression test for the ICE described in issue #91334.
+
+//@ error-pattern: this file contains an unclosed delimiter
+
+#![feature(coroutines)]
+
+fn f(){||yield(((){),
+"#;
+    let input = Level::Error
+        .title("this file contains an unclosed delimiter")
+        .snippet(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("$DIR/issue-91334.rs")
+                .fold(true)
+                .annotation(Level::Warning.span(151..152).label("unclosed delimiter"))
+                .annotation(Level::Warning.span(159..160).label("unclosed delimiter"))
+                .annotation(
+                    Level::Warning
+                        .span(164..164)
+                        .label("missing open `(` for this delimiter"),
+                )
+                .annotation(Level::Error.span(167..167)),
+        );
+    let expected = str![[r#"
+error: this file contains an unclosed delimiter
+  --> $DIR/issue-91334.rs:7:7
+   |
+LL | fn f(){||yield(((){),
+   |       -       -    - ^
+   |       |       |    |
+   |       |       |    missing open `(` for this delimiter
+   |       |       unclosed delimiter
+   |       unclosed delimiter
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn issue_114529_illegal_break_with_value() {
+    // tests/ui/typeck/issue-114529-illegal-break-with-value.rs
+    let source = r#"// Regression test for issue #114529
+// Tests that we do not ICE during const eval for a
+// break-with-value in contexts where it is illegal
+
+#[allow(while_true)]
+fn main() {
+    [(); {
+        while true {
+            break 9; //~ ERROR `break` with value from a `while` loop
+        };
+        51
+    }];
+
+    [(); {
+        while let Some(v) = Some(9) {
+            break v; //~ ERROR `break` with value from a `while` loop
+        };
+        51
+    }];
+
+    while true {
+        break (|| { //~ ERROR `break` with value from a `while` loop
+            let local = 9;
+        });
+    }
+}
+"#;
+    let input = Level::Error
+        .title("`break` with value from a `while` loop")
+        .id("E0571")
+        .snippet(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("$DIR/issue-114529-illegal-break-with-value.rs")
+                .fold(true)
+                .annotation(
+                    Level::Error
+                        .span(483..581)
+                        .label("can only break with a value inside `loop` or breakable block"),
+                )
+                .annotation(
+                    Level::Warning
+                        .span(462..472)
+                        .label("you can't `break` with a value in a `while` loop"),
+                ),
+        )
+        .footer(
+            Level::Help
+                .title("use `break` on its own without a value inside this `while` loop")
+                .snippet(
+                    Snippet::source(source)
+                        .line_start(1)
+                        .origin("$DIR/issue-114529-illegal-break-with-value.rs")
+                        .fold(true)
+                        .annotation(Level::Help.span(483..581).label("break")),
+                ),
+        );
+    let expected = str![[r#"
+error[E0571]: `break` with value from a `while` loop
+  --> $DIR/issue-114529-illegal-break-with-value.rs:22:9
+   |
+LL |       while true {
+   |       ---------- you can't `break` with a value in a `while` loop
+LL | /         break (|| { //~ ERROR `break` with value from a `while` loop
+LL | |             let local = 9;
+LL | |         });
+   | |__________^ can only break with a value inside `loop` or breakable block
+help: use `break` on its own without a value inside this `while` loop
+  --> $DIR/issue-114529-illegal-break-with-value.rs:22:9
+   |
+LL | /         break (|| { //~ ERROR `break` with value from a `while` loop
+LL | |             let local = 9;
+LL | |         });
+   | |__________- help: break
+"#]];
+
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn primitive_reprs_should_have_correct_length() {
+    // tests/ui/transmutability/enums/repr/primitive_reprs_should_have_correct_length.rs
+    let source = r#"//! An enum with a primitive repr should have exactly the size of that primitive.
+
+#![crate_type = "lib"]
+#![feature(transmutability)]
+#![allow(dead_code)]
+
+mod assert {
+    use std::mem::{Assume, TransmuteFrom};
+
+    pub fn is_transmutable<Src, Dst>()
+    where
+        Dst: TransmuteFrom<Src, {
+            Assume {
+                alignment: true,
+                lifetimes: true,
+                safety: true,
+                validity: true,
+            }
+        }>
+    {}
+}
+
+#[repr(C)]
+struct Zst;
+
+#[derive(Clone, Copy)]
+#[repr(i8)] enum V0i8 { V }
+#[repr(u8)] enum V0u8 { V }
+#[repr(i16)] enum V0i16 { V }
+#[repr(u16)] enum V0u16 { V }
+#[repr(i32)] enum V0i32 { V }
+#[repr(u32)] enum V0u32 { V }
+#[repr(i64)] enum V0i64 { V }
+#[repr(u64)] enum V0u64 { V }
+#[repr(isize)] enum V0isize { V }
+#[repr(usize)] enum V0usize { V }
+
+fn n8() {
+    type Smaller = Zst;
+    type Analog = u8;
+    type Larger = u16;
+
+    fn i_should_have_correct_length() {
+        type Current = V0i8;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+
+    fn u_should_have_correct_length() {
+        type Current = V0u8;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+}
+
+fn n16() {
+    type Smaller = u8;
+    type Analog = u16;
+    type Larger = u32;
+
+    fn i_should_have_correct_length() {
+        type Current = V0i16;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+
+    fn u_should_have_correct_length() {
+        type Current = V0u16;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+}
+
+fn n32() {
+    type Smaller = u16;
+    type Analog = u32;
+    type Larger = u64;
+
+    fn i_should_have_correct_length() {
+        type Current = V0i32;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+
+    fn u_should_have_correct_length() {
+        type Current = V0u32;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+}
+
+fn n64() {
+    type Smaller = u32;
+    type Analog = u64;
+    type Larger = u128;
+
+    fn i_should_have_correct_length() {
+        type Current = V0i64;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+
+    fn u_should_have_correct_length() {
+        type Current = V0u64;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+}
+
+fn nsize() {
+    type Smaller = u8;
+    type Analog = usize;
+    type Larger = [usize; 2];
+
+    fn i_should_have_correct_length() {
+        type Current = V0isize;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+
+    fn u_should_have_correct_length() {
+        type Current = V0usize;
+
+        assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted
+        assert::is_transmutable::<Current, Analog>();
+        assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+    }
+}
+"#;
+    let input = Level::Error
+        .title("`V0usize` cannot be safely transmuted into `[usize; 2]`")
+        .id("E0277")
+        .snippet(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("$DIR/primitive_reprs_should_have_correct_length.rs")
+                .fold(true)
+                .annotation(
+                    Level::Error
+                        .span(4375..4381)
+                        .label("the size of `V0usize` is smaller than the size of `[usize; 2]`"),
+                ),
+        )
+        .footer(
+            Level::Note
+                .title("required by a bound in `is_transmutable`")
+                .snippet(
+                    Snippet::source(source)
+                        .line_start(1)
+                        .origin("$DIR/primitive_reprs_should_have_correct_length.rs")
+                        .fold(true)
+                        .annotation(
+                            Level::Note
+                                .span(225..240)
+                                .label("required by a bound in this function"),
+                        )
+                        .annotation(
+                            Level::Error
+                                .span(276..470)
+                                .label("required by this bound in `is_transmutable`"),
+                        ),
+                ),
+        );
+    let expected = str![[r#"
+error[E0277]: `V0usize` cannot be safely transmuted into `[usize; 2]`
+  --> $DIR/primitive_reprs_should_have_correct_length.rs:144:44
+   |
+LL |           assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+   |                                              ^^^^^^ the size of `V0usize` is smaller than the size of `[usize; 2]`
+note: required by a bound in `is_transmutable`
+  --> $DIR/primitive_reprs_should_have_correct_length.rs:10:12
+   |
+LL |       pub fn is_transmutable<Src, Dst>()
+   |              --------------- note: required by a bound in this function
+LL |       where
+LL |           Dst: TransmuteFrom<Src, {
+   |  ______________^
+LL | |             Assume {
+...  |
+LL | |             }
+LL | |         }>
+   | |__________^ required by this bound in `is_transmutable`
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn align_fail() {
+    // tests/ui/transmutability/alignment/align-fail.rs
+    let source = r#"//@ check-fail
+#![feature(transmutability)]
+
+mod assert {
+    use std::mem::{Assume, TransmuteFrom};
+
+    pub fn is_maybe_transmutable<Src, Dst>()
+    where
+        Dst: TransmuteFrom<Src, {
+            Assume {
+                alignment: false,
+                lifetimes: true,
+                safety: true,
+                validity: true,
+            }
+        }>
+    {}
+}
+
+fn main() {
+    assert::is_maybe_transmutable::<&'static [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`
+}
+"#;
+    let input = Level::Error
+        .title("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`").id("E0277")
+        .snippet(
+                Snippet::source(source)
+                    .line_start(1)
+                    .fold(true)
+                    .origin("$DIR/align-fail.rs")
+                    .annotation(
+                       Level::Error
+                            .span(442..459)
+                            .label("the minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2)")
+                    ),
+        );
+    let expected = str![[r#"
+error[E0277]: `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`
+  --> $DIR/align-fail.rs:21:55
+   |
+LL | ...ic [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`
+   |                ^^^^^^^^^^^^^^^^^ the minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2)
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn missing_semicolon() {
+    // tests/ui/suggestions/missing-semicolon.rs
+    let source = r#"//@ run-rustfix
+#![allow(dead_code, unused_variables, path_statements)]
+fn a() {
+    let x = 5;
+    let y = x //~ ERROR expected function
+    () //~ ERROR expected `;`, found `}`
+}
+
+fn b() {
+    let x = 5;
+    let y = x //~ ERROR expected function
+    ();
+}
+fn c() {
+    let x = 5;
+    x //~ ERROR expected function
+    ()
+}
+fn d() { // ok
+    let x = || ();
+    x
+    ()
+}
+fn e() { // ok
+    let x = || ();
+    x
+    ();
+}
+fn f()
+ {
+    let y = 5 //~ ERROR expected function
+    () //~ ERROR expected `;`, found `}`
+}
+fn g() {
+    5 //~ ERROR expected function
+    ();
+}
+fn main() {}
+"#;
+    let input =
+        Level::Error
+            .title("expected function, found `{integer}`")
+            .id("E0618")
+            .snippet(
+                Snippet::source(source)
+                    .line_start(1)
+                    .origin("$DIR/missing-semicolon.rs")
+                    .fold(true)
+                    .annotation(
+                        Level::Warning
+                            .span(108..144)
+                            .label("call expression requires function"),
+                    )
+                    .annotation(
+                        Level::Warning
+                            .span(89..90)
+                            .label("`x` has type `{integer}`"),
+                    )
+                    .annotation(Level::Warning.span(109..109).label(
+                        "help: consider using a semicolon here to finish the statement: `;`",
+                    ))
+                    .annotation(Level::Error.span(108..109)),
+            );
+    let expected = str![[r#"
+error[E0618]: expected function, found `{integer}`
+  --> $DIR/missing-semicolon.rs:5:13
+   |
+LL |       let x = 5;
+   |           - `x` has type `{integer}`
+LL |       let y = x //~ ERROR expected function
+   |               ^- help: consider using a semicolon here to finish the statement: `;`
+   |  _____________|
+   | |
+LL | |     () //~ ERROR expected `;`, found `}`
+   | |______- call expression requires function
+"#]];
+
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn nested_macro_rules() {
+    // tests/ui/proc-macro/nested-macro-rules.rs
+    let source = r#"//@ run-pass
+//@ aux-build:nested-macro-rules.rs
+//@ proc-macro: test-macros.rs
+//@ compile-flags: -Z span-debug -Z macro-backtrace
+//@ edition:2018
+
+#![no_std] // Don't load unnecessary hygiene information from std
+#![warn(non_local_definitions)]
+
+extern crate std;
+
+extern crate nested_macro_rules;
+extern crate test_macros;
+
+use test_macros::{print_bang, print_attr};
+
+use nested_macro_rules::FirstStruct;
+struct SecondStruct;
+
+fn main() {
+    nested_macro_rules::inner_macro!(print_bang, print_attr);
+
+    nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct);
+    //~^ WARN non-local `macro_rules!` definition
+    inner_macro!(print_bang, print_attr);
+}
+"#;
+
+    let aux_source = r#"pub struct FirstStruct;
+
+#[macro_export]
+macro_rules! outer_macro {
+    ($name:ident, $attr_struct_name:ident) => {
+        #[macro_export]
+        macro_rules! inner_macro {
+            ($bang_macro:ident, $attr_macro:ident) => {
+                $bang_macro!($name);
+                #[$attr_macro] struct $attr_struct_name {}
+            }
+        }
+    }
+}
+
+outer_macro!(FirstStruct, FirstAttrStruct);
+"#;
+    let input = Level::Warning
+        .title("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module")
+        .snippet(
+            Snippet::source(aux_source)
+                .line_start(1)
+                .origin("$DIR/auxiliary/nested-macro-rules.rs")
+                .fold(true)
+                .annotation(
+                    Level::Warning
+                        .span(41..65)
+                        .label("in this expansion of `nested_macro_rules::outer_macro!`"),
+                )
+                .annotation(Level::Error.span(148..350)),
+        )
+        .snippet(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("$DIR/nested-macro-rules.rs")
+                .fold(true)
+                .annotation(
+                    Level::Warning
+                        .span(510..574)
+                        .label("in this macro invocation"),
+                ),
+        )
+        .footer(Level::Help.title("remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`"))
+        .footer(Level::Note.title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute"))
+        .footer(
+            Level::Note.title("the lint level is defined here").snippet(
+                Snippet::source(source)
+                    .line_start(1)
+                    .origin("$DIR/nested-macro-rules.rs")
+                    .fold(true)
+                    .annotation(Level::Error.span(224..245)),
+            ),
+        );
+    let expected = str![[r#"
+warning: non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module
+  --> $DIR/auxiliary/nested-macro-rules.rs:4:1
+   |
+LL |   macro_rules! outer_macro {
+   |   ------------------------ in this expansion of `nested_macro_rules::outer_macro!`
+...
+LL | /         macro_rules! inner_macro {
+LL | |             ($bang_macro:ident, $attr_macro:ident) => {
+...  |
+LL | |             }
+LL | |         }
+   | |_________^
+  ::: $DIR/nested-macro-rules.rs:23:5
+   |
+LL |       nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct);
+   |       ---------------------------------------------------------------- in this macro invocation
+   = help: remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`
+   = note: a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute
+note: the lint level is defined here
+  --> $DIR/nested-macro-rules.rs:8:9
+   |
+LL |   #![warn(non_local_definitions)]
+   |           ^^^^^^^^^^^^^^^^^^^^^
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn method_on_ambiguous_numeric_type() {
+    // tests/ui/methods/method-on-ambiguous-numeric-type.rs
+    let source = r#"//@ aux-build:macro-in-other-crate.rs
+
+#[macro_use] extern crate macro_in_other_crate;
+
+macro_rules! local_mac {
+    ($ident:ident) => { let $ident = 42; }
+}
+macro_rules! local_mac_tt {
+    ($tt:tt) => { let $tt = 42; }
+}
+
+fn main() {
+    let x = 2.0.neg();
+    //~^ ERROR can't call method `neg` on ambiguous numeric type `{float}`
+
+    let y = 2.0;
+    let x = y.neg();
+    //~^ ERROR can't call method `neg` on ambiguous numeric type `{float}`
+    println!("{:?}", x);
+
+    for i in 0..100 {
+        println!("{}", i.pow(2));
+        //~^ ERROR can't call method `pow` on ambiguous numeric type `{integer}`
+    }
+
+    local_mac!(local_bar);
+    local_bar.pow(2);
+    //~^ ERROR can't call method `pow` on ambiguous numeric type `{integer}`
+
+    local_mac_tt!(local_bar_tt);
+    local_bar_tt.pow(2);
+    //~^ ERROR can't call method `pow` on ambiguous numeric type `{integer}`
+}
+
+fn qux() {
+    mac!(bar);
+    bar.pow(2);
+    //~^ ERROR can't call method `pow` on ambiguous numeric type `{integer}`
+}
+"#;
+
+    let aux_source = r#"#[macro_export]
+macro_rules! mac {
+    ($ident:ident) => { let $ident = 42; }
+}
+
+#[macro_export]
+macro_rules! inline {
+    () => ()
+}
+"#;
+    let input = Level::Error
+        .title("can't call method `pow` on ambiguous numeric type `{integer}`")
+        .id("E0689")
+        .snippet(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("$DIR/method-on-ambiguous-numeric-type.rs")
+                .fold(true)
+                .annotation(Level::Error.span(916..919)),
+        )
+        .footer(
+            Level::Help
+                .title("you must specify a type for this binding, like `i32`")
+                .snippet(
+                    Snippet::source(aux_source)
+                        .line_start(1)
+                        .origin("$DIR/auxiliary/macro-in-other-crate.rs")
+                        .fold(true)
+                        .annotation(Level::Help.span(69..69).label(": i32")),
+                ),
+        );
+    let expected = str![[r#"
+error[E0689]: can't call method `pow` on ambiguous numeric type `{integer}`
+  --> $DIR/method-on-ambiguous-numeric-type.rs:37:9
+   |
+LL |     bar.pow(2);
+   |         ^^^
+help: you must specify a type for this binding, like `i32`
+  --> $DIR/auxiliary/macro-in-other-crate.rs:3:35
+   |
+LL |     ($ident:ident) => { let $ident = 42; }
+   |                                   - help: : i32
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn issue_42234_unknown_receiver_type() {
+    // tests/ui/span/issue-42234-unknown-receiver-type.rs
+    let source = r#"//@ revisions: full generic_arg
+#![cfg_attr(generic_arg, feature(generic_arg_infer))]
+
+// When the type of a method call's receiver is unknown, the span should point
+// to the receiver (and not the entire call, as was previously the case before
+// the fix of which this tests).
+
+fn shines_a_beacon_through_the_darkness() {
+    let x: Option<_> = None; //~ ERROR type annotations needed
+    x.unwrap().method_that_could_exist_on_some_type();
+}
+
+fn courier_to_des_moines_and_points_west(data: &[u32]) -> String {
+    data.iter()
+        .sum::<_>() //~ ERROR type annotations needed
+        .to_string()
+}
+
+fn main() {}
+"#;
+
+    let input = Level::Error
+        .title("type annotations needed")
+        .id("E0282")
+        .snippet(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("$DIR/issue-42234-unknown-receiver-type.rs")
+                .fold(true)
+                .annotation(Level::Error.span(536..539).label(
+                    "cannot infer type of the type parameter `S` declared on the method `sum`",
+                )),
+        );
+    let expected = str![[r#"
+error[E0282]: type annotations needed
+  --> $DIR/issue-42234-unknown-receiver-type.rs:15:10
+   |
+LL |         .sum::<_>() //~ ERROR type annotations needed
+   |          ^^^ cannot infer type of the type parameter `S` declared on the method `sum`
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn pattern_usefulness_empty_match() {
+    // tests/ui/pattern/usefulness/empty-match.rs
+    let source = r##"//@ revisions: normal exhaustive_patterns
+//
+// This tests a match with no arms on various types.
+#![feature(never_type)]
+#![cfg_attr(exhaustive_patterns, feature(exhaustive_patterns))]
+#![deny(unreachable_patterns)]
+
+fn nonempty<const N: usize>(arrayN_of_empty: [!; N]) {
+    macro_rules! match_no_arms {
+        ($e:expr) => {
+            match $e {}
+        };
+    }
+    macro_rules! match_guarded_arm {
+        ($e:expr) => {
+            match $e {
+                _ if false => {}
+            }
+        };
+    }
+
+    struct NonEmptyStruct1;
+    struct NonEmptyStruct2(bool);
+    union NonEmptyUnion1 {
+        foo: (),
+    }
+    union NonEmptyUnion2 {
+        foo: (),
+        bar: !,
+    }
+    enum NonEmptyEnum1 {
+        Foo(bool),
+    }
+    enum NonEmptyEnum2 {
+        Foo(bool),
+        Bar,
+    }
+    enum NonEmptyEnum5 {
+        V1,
+        V2,
+        V3,
+        V4,
+        V5,
+    }
+    let array0_of_empty: [!; 0] = [];
+
+    match_no_arms!(0u8); //~ ERROR type `u8` is non-empty
+    match_no_arms!(0i8); //~ ERROR type `i8` is non-empty
+    match_no_arms!(0usize); //~ ERROR type `usize` is non-empty
+    match_no_arms!(0isize); //~ ERROR type `isize` is non-empty
+    match_no_arms!(NonEmptyStruct1); //~ ERROR type `NonEmptyStruct1` is non-empty
+    match_no_arms!(NonEmptyStruct2(true)); //~ ERROR type `NonEmptyStruct2` is non-empty
+    match_no_arms!((NonEmptyUnion1 { foo: () })); //~ ERROR type `NonEmptyUnion1` is non-empty
+    match_no_arms!((NonEmptyUnion2 { foo: () })); //~ ERROR type `NonEmptyUnion2` is non-empty
+    match_no_arms!(NonEmptyEnum1::Foo(true)); //~ ERROR `NonEmptyEnum1::Foo(_)` not covered
+    match_no_arms!(NonEmptyEnum2::Foo(true)); //~ ERROR `NonEmptyEnum2::Foo(_)` and `NonEmptyEnum2::Bar` not covered
+    match_no_arms!(NonEmptyEnum5::V1); //~ ERROR `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered
+    match_no_arms!(array0_of_empty); //~ ERROR type `[!; 0]` is non-empty
+    match_no_arms!(arrayN_of_empty); //~ ERROR type `[!; N]` is non-empty
+
+    match_guarded_arm!(0u8); //~ ERROR `0_u8..=u8::MAX` not covered
+    match_guarded_arm!(0i8); //~ ERROR `i8::MIN..=i8::MAX` not covered
+    match_guarded_arm!(0usize); //~ ERROR `0_usize..` not covered
+    match_guarded_arm!(0isize); //~ ERROR `_` not covered
+    match_guarded_arm!(NonEmptyStruct1); //~ ERROR `NonEmptyStruct1` not covered
+    match_guarded_arm!(NonEmptyStruct2(true)); //~ ERROR `NonEmptyStruct2(_)` not covered
+    match_guarded_arm!((NonEmptyUnion1 { foo: () })); //~ ERROR `NonEmptyUnion1 { .. }` not covered
+    match_guarded_arm!((NonEmptyUnion2 { foo: () })); //~ ERROR `NonEmptyUnion2 { .. }` not covered
+    match_guarded_arm!(NonEmptyEnum1::Foo(true)); //~ ERROR `NonEmptyEnum1::Foo(_)` not covered
+    match_guarded_arm!(NonEmptyEnum2::Foo(true)); //~ ERROR `NonEmptyEnum2::Foo(_)` and `NonEmptyEnum2::Bar` not covered
+    match_guarded_arm!(NonEmptyEnum5::V1); //~ ERROR `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered
+    match_guarded_arm!(array0_of_empty); //~ ERROR `[]` not covered
+    match_guarded_arm!(arrayN_of_empty); //~ ERROR `[]` not covered
+}
+
+fn main() {}
+"##;
+
+    let input = Level::Error
+        .title(
+            "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered"
+        )
+        .id("E0004")
+        .snippet(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("$DIR/empty-match.rs")
+                .fold(true)
+                .annotation(
+                   Level::Error
+                        .span(2911..2928)
+                        .label("patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered")
+                ),
+        )
+        .footer(Level::Note.title("`NonEmptyEnum5` defined here")
+            .snippet(
+                Snippet::source(source)
+                    .line_start(1)
+                    .origin("$DIR/empty-match.rs")
+                    .fold(true)
+                    .annotation(Level::Error.span(818..831))
+                    .annotation(Level::Warning.span(842..844).label("not covered"))
+                    .annotation(Level::Warning.span(854..856).label("not covered"))
+                    .annotation(Level::Warning.span(866..868).label("not covered"))
+                    .annotation(Level::Warning.span(878..880).label("not covered"))
+                    .annotation(Level::Warning.span(890..892).label("not covered"))
+            ))
+        .footer(Level::Note.title("the matched value is of type `NonEmptyEnum5`"))
+        .footer(Level::Note.title("match arms with guards don't count towards exhaustivity"))
+        .footer(
+            Level::Help
+                .title("ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms")
+                .snippet(
+                    Snippet::source(source)
+                        .line_start(1)
+                        .origin("$DIR/empty-match.rs")
+                        .fold(true)
+                        .annotation(Level::Help.span(485..485).label(",\n                _ => todo!()"))
+                )
+        );
+    let expected = str![[r#"
+error[E0004]: non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered
+  --> $DIR/empty-match.rs:71:24
+   |
+LL |     match_guarded_arm!(NonEmptyEnum5::V1); //~ ERROR `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered
+   |                        ^^^^^^^^^^^^^^^^^ patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered
+note: `NonEmptyEnum5` defined here
+  --> $DIR/empty-match.rs:38:10
+   |
+LL |     enum NonEmptyEnum5 {
+   |          ^^^^^^^^^^^^^
+LL |         V1,
+   |         -- not covered
+LL |         V2,
+   |         -- not covered
+LL |         V3,
+   |         -- not covered
+LL |         V4,
+   |         -- not covered
+LL |         V5,
+   |         -- not covered
+   = note: the matched value is of type `NonEmptyEnum5`
+   = note: match arms with guards don't count towards exhaustivity
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms
+  --> $DIR/empty-match.rs:17:33
+   |
+LL |                 _ if false => {}
+   |                                 - help: ,
+                _ => todo!()
+"#]];
+    let renderer = Renderer::plain()
+        .anonymized_line_numbers(true)
+        .term_width(annotate_snippets::renderer::DEFAULT_TERM_WIDTH + 4);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}
+
+#[test]
+fn object_fail() {
+    // tests/ui/traits/alias/object-fail.rs
+    let source = r#"#![feature(trait_alias)]
+
+trait EqAlias = Eq;
+trait IteratorAlias = Iterator;
+
+fn main() {
+    let _: &dyn EqAlias = &123;
+    //~^ ERROR the trait alias `EqAlias` is not dyn compatible [E0038]
+    let _: &dyn IteratorAlias = &vec![123].into_iter();
+    //~^ ERROR must be specified
+}
+"#;
+    let input = Level::Error
+        .title("the trait alias `EqAlias` is not dyn compatible")
+        .id("E0038")
+        .snippet(
+                Snippet::source(source)
+                    .line_start(1)
+                    .origin("$DIR/object-fail.rs")
+                    .fold(true)
+                    .annotation(
+                        Level::Error
+                            .span(107..114)
+                            .label("`EqAlias` is not dyn compatible"),
+                    ),
+
+        )
+        .footer(
+                    Level::Note
+                        .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>")
+                .snippet(
+                    Snippet::source("")
+                        .line_start(334)
+                        .origin("$SRC_DIR/core/src/cmp.rs")
+
+                )
+                .snippet(
+                    Snippet::source(source)
+                        .line_start(1)
+                        .origin("$DIR/object-fail.rs")
+                        .fold(true)
+                        .annotation(
+                            Level::Warning
+                                .span(32..39)
+                                .label("this trait is not dyn compatible..."),
+                        ),
+                ),
+        );
+    let expected = str![[r#"
+error[E0038]: the trait alias `EqAlias` is not dyn compatible
+  --> $DIR/object-fail.rs:7:17
+   |
+LL |     let _: &dyn EqAlias = &123;
+   |                 ^^^^^^^ `EqAlias` is not dyn compatible
+note: for a trait to be dyn compatible it needs to allow building a vtable
+for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
+  --> $SRC_DIR/core/src/cmp.rs
+   |
+  ::: $DIR/object-fail.rs:3:7
+   |
+LL | trait EqAlias = Eq;
+   |       ------- this trait is not dyn compatible...
+"#]];
+
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}

From ebae628b5fbd7f315756b5797fab17f1960b71da Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 1 Apr 2025 15:54:51 -0600
Subject: [PATCH 270/302] feat: Style `: ` for titles

---
 examples/expected_type.svg                          |  2 +-
 examples/footer.svg                                 |  2 +-
 examples/format.svg                                 |  2 +-
 examples/multislice.svg                             |  2 +-
 src/renderer/display_list.rs                        | 13 ++++++++-----
 tests/fixtures/color/ann_eof.svg                    |  2 +-
 tests/fixtures/color/ann_insertion.svg              |  2 +-
 tests/fixtures/color/ann_multiline.svg              |  2 +-
 tests/fixtures/color/ann_multiline2.svg             |  2 +-
 tests/fixtures/color/ann_removed_nl.svg             |  2 +-
 .../fixtures/color/ensure-emoji-highlight-width.svg |  2 +-
 tests/fixtures/color/fold_ann_multiline.svg         |  2 +-
 tests/fixtures/color/fold_leading.svg               |  2 +-
 tests/fixtures/color/fold_trailing.svg              |  2 +-
 tests/fixtures/color/issue_9.svg                    |  2 +-
 tests/fixtures/color/simple.svg                     |  2 +-
 tests/fixtures/color/strip_line.svg                 |  2 +-
 tests/fixtures/color/strip_line_char.svg            |  2 +-
 tests/fixtures/color/strip_line_non_ws.svg          |  2 +-
 19 files changed, 26 insertions(+), 23 deletions(-)

diff --git a/examples/expected_type.svg b/examples/expected_type.svg
index d5de44f..334be47 100644
--- a/examples/expected_type.svg
+++ b/examples/expected_type.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected type, found `22`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected type, found `22`</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> examples/footer.rs:29:25</tspan>
 </tspan>
diff --git a/examples/footer.svg b/examples/footer.svg
index ab9e4df..b16d224 100644
--- a/examples/footer.svg
+++ b/examples/footer.svg
@@ -20,7 +20,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/multislice.rs:13:22</tspan>
 </tspan>
diff --git a/examples/format.svg b/examples/format.svg
index 0e05457..5a0eba2 100644
--- a/examples/format.svg
+++ b/examples/format.svg
@@ -20,7 +20,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs:51:6</tspan>
 </tspan>
diff --git a/examples/multislice.svg b/examples/multislice.svg
index 92ff9df..25fe27c 100644
--- a/examples/multislice.svg
+++ b/examples/multislice.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs</tspan>
 </tspan>
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 53184f2..9bbe9bf 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -160,14 +160,18 @@ impl DisplaySet<'_> {
         &self,
         line_offset: usize,
         label: &[DisplayTextFragment<'_>],
+        needs_colon: bool,
         stylesheet: &Stylesheet,
         buffer: &mut StyledBuffer,
     ) -> fmt::Result {
-        for fragment in label {
+        for (i, fragment) in label.iter().enumerate() {
             let style = match fragment.style {
                 DisplayTextStyle::Regular => stylesheet.none(),
                 DisplayTextStyle::Emphasis => stylesheet.emphasis(),
             };
+            if i == 0 && needs_colon {
+                buffer.append(line_offset, ": ", *style);
+            }
             buffer.append(line_offset, fragment.content, *style);
         }
         Ok(())
@@ -191,10 +195,10 @@ impl DisplaySet<'_> {
             for _ in 0..formatted_len + 2 {
                 buffer.append(line_offset, " ", Style::new());
             }
-            return self.format_label(line_offset, &annotation.label, stylesheet, buffer);
+            return self.format_label(line_offset, &annotation.label, false, stylesheet, buffer);
         }
         if formatted_len == 0 {
-            self.format_label(line_offset, &annotation.label, stylesheet, buffer)
+            self.format_label(line_offset, &annotation.label, false, stylesheet, buffer)
         } else {
             let id = match &annotation.id {
                 Some(id) => format!("[{id}]"),
@@ -207,8 +211,7 @@ impl DisplaySet<'_> {
             );
 
             if !is_annotation_empty(annotation) {
-                buffer.append(line_offset, ": ", stylesheet.none);
-                self.format_label(line_offset, &annotation.label, stylesheet, buffer)?;
+                self.format_label(line_offset, &annotation.label, true, stylesheet, buffer)?;
             }
             Ok(())
         }
diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg
index b0fb8b6..f559f27 100644
--- a/tests/fixtures/color/ann_eof.svg
+++ b/tests/fixtures/color/ann_eof.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:5</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg
index 35d65a0..dbdac8a 100644
--- a/tests/fixtures/color/ann_insertion.svg
+++ b/tests/fixtures/color/ann_insertion.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:3</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg
index 949eddc..4a01d03 100644
--- a/tests/fixtures/color/ann_multiline.svg
+++ b/tests/fixtures/color/ann_multiline.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0027]</tspan><tspan>: </tspan><tspan class="bold">pattern does not mention fields `lineno`, `content`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0027]</tspan><tspan class="bold">: pattern does not mention fields `lineno`, `content`</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/display_list.rs:139:32</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg
index 064826a..13e1b3f 100644
--- a/tests/fixtures/color/ann_multiline2.svg
+++ b/tests/fixtures/color/ann_multiline2.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E####]</tspan><tspan>: </tspan><tspan class="bold">spacing error found</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E####]</tspan><tspan class="bold">: spacing error found</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> foo.txt:26:12</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg
index b0fb8b6..f559f27 100644
--- a/tests/fixtures/color/ann_removed_nl.svg
+++ b/tests/fixtures/color/ann_removed_nl.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:5</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg
index 077bca2..2b569e6 100644
--- a/tests/fixtures/color/ensure-emoji-highlight-width.svg
+++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> &lt;file&gt;:7:1</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg
index 39323c5..4034e74 100644
--- a/tests/fixtures/color/fold_ann_multiline.svg
+++ b/tests/fixtures/color/fold_ann_multiline.svg
@@ -20,7 +20,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs:51:6</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg
index 0ff7d15..355dbd4 100644
--- a/tests/fixtures/color/fold_leading.svg
+++ b/tests/fixtures/color/fold_leading.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a bool</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:11:13</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg
index ca9de40..522363e 100644
--- a/tests/fixtures/color/fold_trailing.svg
+++ b/tests/fixtures/color/fold_trailing.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a lints table</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a lints table</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:9</tspan>
 </tspan>
diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg
index 05e421e..aa353b4 100644
--- a/tests/fixtures/color/issue_9.svg
+++ b/tests/fixtures/color/issue_9.svg
@@ -20,7 +20,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
 </tspan>
diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg
index 210cf34..328c754 100644
--- a/tests/fixtures/color/simple.svg
+++ b/tests/fixtures/color/simple.svg
@@ -20,7 +20,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format_color.rs:171:9</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg
index f86250a..487a887 100644
--- a/tests/fixtures/color/strip_line.svg
+++ b/tests/fixtures/color/strip_line.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg
index ed9ba73..6b60867 100644
--- a/tests/fixtures/color/strip_line_char.svg
+++ b/tests/fixtures/color/strip_line_char.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg
index 251dfca..e5d69fd 100644
--- a/tests/fixtures/color/strip_line_non_ws.svg
+++ b/tests/fixtures/color/strip_line_non_ws.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan>
 </tspan>

From f2832d253e80c6875046784d289be01d6b69a17c Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 1 Apr 2025 15:54:51 -0600
Subject: [PATCH 271/302] fix: Don't style ' ' before '|'

---
 examples/expected_type.svg                    |  8 ++--
 examples/footer.svg                           |  2 +-
 examples/format.svg                           | 44 +++++++++----------
 examples/multislice.svg                       |  4 +-
 src/renderer/display_list.rs                  | 16 +++----
 tests/fixtures/color/ann_eof.svg              |  2 +-
 tests/fixtures/color/ann_insertion.svg        |  2 +-
 tests/fixtures/color/ann_multiline.svg        |  6 +--
 tests/fixtures/color/ann_multiline2.svg       |  6 +--
 tests/fixtures/color/ann_removed_nl.svg       |  2 +-
 .../color/ensure-emoji-highlight-width.svg    |  2 +-
 tests/fixtures/color/fold_ann_multiline.svg   | 10 ++---
 tests/fixtures/color/fold_bad_origin_line.svg |  2 +-
 tests/fixtures/color/fold_leading.svg         |  2 +-
 tests/fixtures/color/fold_trailing.svg        |  2 +-
 tests/fixtures/color/issue_9.svg              |  6 +--
 tests/fixtures/color/multiple_annotations.svg | 18 ++++----
 tests/fixtures/color/simple.svg               |  6 +--
 tests/fixtures/color/strip_line.svg           |  2 +-
 tests/fixtures/color/strip_line_char.svg      |  2 +-
 tests/fixtures/color/strip_line_non_ws.svg    |  2 +-
 21 files changed, 71 insertions(+), 75 deletions(-)

diff --git a/examples/expected_type.svg b/examples/expected_type.svg
index 334be47..c3da76b 100644
--- a/examples/expected_type.svg
+++ b/examples/expected_type.svg
@@ -25,15 +25,15 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan>                 annotations: vec![SourceAnnotation {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 annotations: vec![SourceAnnotation {</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info: while parsing this struct</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan>                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan>                     ,</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                     ,</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
 </tspan>
     <tspan x="10px" y="172px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                         </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
diff --git a/examples/footer.svg b/examples/footer.svg
index b16d224..367a6d8 100644
--- a/examples/footer.svg
+++ b/examples/footer.svg
@@ -26,7 +26,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13 |</tspan><tspan>         slices: vec!["A",</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         slices: vec!["A",</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                      </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
diff --git a/examples/format.svg b/examples/format.svg
index 5a0eba2..9ecfc3d 100644
--- a/examples/format.svg
+++ b/examples/format.svg
@@ -26,51 +26,51 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan>   ) -&gt; Option&lt;String&gt; {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>   ) -&gt; Option&lt;String&gt; {</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>        </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option&lt;String&gt;` because of return type</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan>     for ann in annotations {</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan>     for ann in annotations {</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (None, None) =&gt; continue,</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (None, None) =&gt; continue,</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt; end_index =&gt; continue,</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt; end_index =&gt; continue,</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">56 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt;= start_index =&gt; {</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">56</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt;= start_index =&gt; {</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">57 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 let label = if let Some(ref label) = ann.label {</tspan>
+    <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">57</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 let label = if let Some(ref label) = ann.label {</tspan>
 </tspan>
-    <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">58 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     format!(" {}", label)</tspan>
+    <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">58</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     format!(" {}", label)</tspan>
 </tspan>
-    <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">59 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 } else {</tspan>
+    <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">59</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 } else {</tspan>
 </tspan>
-    <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">60 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     String::from("")</tspan>
+    <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">60</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     String::from("")</tspan>
 </tspan>
-    <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 };</tspan>
+    <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">61</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 };</tspan>
 </tspan>
-    <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan>
+    <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">62</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 return Some(format!(</tspan>
+    <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">63</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 return Some(format!(</tspan>
 </tspan>
-    <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">64 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "{}{}{}",</tspan>
+    <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">64</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "{}{}{}",</tspan>
 </tspan>
-    <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">65 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     " ".repeat(start - start_index),</tspan>
+    <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">65</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     " ".repeat(start - start_index),</tspan>
 </tspan>
-    <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">66 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "^".repeat(end - start),</tspan>
+    <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">66</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     "^".repeat(end - start),</tspan>
 </tspan>
-    <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">67 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     label</tspan>
+    <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">67</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                     label</tspan>
 </tspan>
-    <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">68 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 ));</tspan>
+    <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">68</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                 ));</tspan>
 </tspan>
-    <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">69 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             }</tspan>
+    <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">69</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             }</tspan>
 </tspan>
-    <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">70 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             _ =&gt; continue,</tspan>
+    <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">70</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             _ =&gt; continue,</tspan>
 </tspan>
-    <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         }</tspan>
+    <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">71</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         }</tspan>
 </tspan>
-    <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
+    <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
 </tspan>
     <tspan x="10px" y="496px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan>
 </tspan>
diff --git a/examples/multislice.svg b/examples/multislice.svg
index 25fe27c..6b325cb 100644
--- a/examples/multislice.svg
+++ b/examples/multislice.svg
@@ -25,13 +25,13 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51 |</tspan><tspan> Foo</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">129</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Faa</tspan>
 </tspan>
     <tspan x="10px" y="154px">
 </tspan>
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 9bbe9bf..7f85a31 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -292,19 +292,15 @@ impl DisplaySet<'_> {
             } => {
                 let lineno_color = stylesheet.line_no();
                 if anonymized_line_numbers && lineno.is_some() {
-                    let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$} |");
+                    let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$}");
                     buffer.puts(line_offset, 0, &num, *lineno_color);
                 } else {
-                    match lineno {
-                        Some(n) => {
-                            let num = format!("{n:>lineno_width$} |");
-                            buffer.puts(line_offset, 0, &num, *lineno_color);
-                        }
-                        None => {
-                            buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color);
-                        }
-                    };
+                    if let Some(n) = lineno {
+                        let num = format!("{n:>lineno_width$}");
+                        buffer.puts(line_offset, 0, &num, *lineno_color);
+                    }
                 }
+                buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color);
                 if let DisplaySourceLine::Content { text, .. } = line {
                     // The width of the line number, a space, pipe, and a space
                     // `123 | ` is `lineno_width + 3`.
diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg
index f559f27..c9a12c5 100644
--- a/tests/fixtures/color/ann_eof.svg
+++ b/tests/fixtures/color/ann_eof.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> asdf</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-red bold">^</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg
index dbdac8a..cd42fe4 100644
--- a/tests/fixtures/color/ann_insertion.svg
+++ b/tests/fixtures/color/ann_insertion.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asf</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> asf</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>   </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">'d' belongs here</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg
index 4a01d03..1e2d29e 100644
--- a/tests/fixtures/color/ann_multiline.svg
+++ b/tests/fixtures/color/ann_multiline.svg
@@ -25,13 +25,13 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139 |</tspan><tspan>                           if let DisplayLine::Source {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                           if let DisplayLine::Source {</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">________________________________^</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                             ref mut inline_marks,</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                             ref mut inline_marks,</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                         } = body[body_idx]</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                         } = body[body_idx]</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_________________________^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">missing fields `lineno`, `content`</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg
index 13e1b3f..bae12b8 100644
--- a/tests/fixtures/color/ann_multiline2.svg
+++ b/tests/fixtures/color/ann_multiline2.svg
@@ -25,13 +25,13 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan> This is an example</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> This is an example</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>            </tspan><tspan class="fg-bright-red bold">^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">this should not be on separate lines</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> of an edge case of an annotation overflowing</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> of an edge case of an annotation overflowing</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> to exactly one character on next line.</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> to exactly one character on next line.</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg
index f559f27..c9a12c5 100644
--- a/tests/fixtures/color/ann_removed_nl.svg
+++ b/tests/fixtures/color/ann_removed_nl.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> asdf</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-red bold">^</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg
index 2b569e6..b49915a 100644
--- a/tests/fixtures/color/ensure-emoji-highlight-width.svg
+++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg
index 4034e74..c3a220d 100644
--- a/tests/fixtures/color/fold_ann_multiline.svg
+++ b/tests/fixtures/color/fold_ann_multiline.svg
@@ -26,19 +26,19 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan>   ) -&gt; Option&lt;String&gt; {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>   ) -&gt; Option&lt;String&gt; {</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>        </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `std::option::Option&lt;std::string::String&gt;` because of return type</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan>     for ann in annotations {</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan>     for ann in annotations {</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">...</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         }</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">71</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         }</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
 </tspan>
     <tspan x="10px" y="208px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg
index c7fb916..eb70d5f 100644
--- a/tests/fixtures/color/fold_bad_origin_line.svg
+++ b/tests/fixtures/color/fold_bad_origin_line.svg
@@ -26,7 +26,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3 |</tspan><tspan> invalid syntax</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> invalid syntax</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">error here</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg
index 355dbd4..b6cc607 100644
--- a/tests/fixtures/color/fold_leading.svg
+++ b/tests/fixtures/color/fold_leading.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11 |</tspan><tspan> workspace = 20</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> workspace = 20</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             </tspan><tspan class="fg-bright-red bold">^^</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg
index 522363e..54b6d9c 100644
--- a/tests/fixtures/color/fold_trailing.svg
+++ b/tests/fixtures/color/fold_trailing.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> lints = 20</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> lints = 20</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-bright-red bold">^^</tspan>
 </tspan>
diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg
index aa353b4..f1f9044 100644
--- a/tests/fixtures/color/issue_9.svg
+++ b/tests/fixtures/color/issue_9.svg
@@ -26,15 +26,15 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> let x = vec![1];</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> let y = x;</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
 </tspan>
     <tspan x="10px" y="136px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">9 |</tspan><tspan> x;</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
 </tspan>
     <tspan x="10px" y="172px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
 </tspan>
diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg
index fc6fe68..71628bd 100644
--- a/tests/fixtures/color/multiple_annotations.svg
+++ b/tests/fixtures/color/multiple_annotations.svg
@@ -23,29 +23,29 @@
 </tspan>
     <tspan x="10px" y="46px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96 |</tspan><tspan> fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
+    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97 |</tspan><tspan>     if let Some(annotation) = main_annotation {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     if let Some(annotation) = main_annotation {</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Variable defined here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98 |</tspan><tspan>         result.push(format_title_line(</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         result.push(format_title_line(</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99 |</tspan><tspan>             &amp;annotation.annotation_type,</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             &amp;annotation.annotation_type,</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>              </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced here</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">100 |</tspan><tspan>             None,</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">100</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             None,</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">101 |</tspan><tspan>             &amp;annotation.label,</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">101</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             &amp;annotation.label,</tspan>
 </tspan>
     <tspan x="10px" y="208px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>              </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced again here</tspan>
 </tspan>
-    <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">102 |</tspan><tspan>         ));</tspan>
+    <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">102</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         ));</tspan>
 </tspan>
-    <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">103 |</tspan><tspan>     }</tspan>
+    <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">103</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     }</tspan>
 </tspan>
-    <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">104 |</tspan><tspan> }</tspan>
+    <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">104</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> }</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg
index 328c754..6707403 100644
--- a/tests/fixtures/color/simple.svg
+++ b/tests/fixtures/color/simple.svg
@@ -26,13 +26,13 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169 |</tspan><tspan>         })</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         })</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>           </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected one of `.`, `;`, `?`, or an operator here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170 |</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">171 |</tspan><tspan>         for line in &amp;self.body {</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">171</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         for line in &amp;self.body {</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unexpected token</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg
index 487a887..bcd77ae 100644
--- a/tests/fixtures/color/strip_line.svg
+++ b/tests/fixtures/color/strip_line.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan>                   let _: () = 42;</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan>                   let _: () = 42;</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg
index 6b60867..f6bfe69 100644
--- a/tests/fixtures/color/strip_line_char.svg
+++ b/tests/fixtures/color/strip_line_char.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan>                   let _: () = 42ñ</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan>                   let _: () = 42ñ</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg
index e5d69fd..4e5d33c 100644
--- a/tests/fixtures/color/strip_line_non_ws.svg
+++ b/tests/fixtures/color/strip_line_non_ws.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () </tspan><tspan class="fg-bright-blue bold">...</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () </tspan><tspan class="fg-bright-blue bold">...</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                                  </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan>   </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected `()`, found integer</tspan>
 </tspan>

From 93717717048a40aa3785cd8f9164665f855ceecc Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 1 Apr 2025 15:54:51 -0600
Subject: [PATCH 272/302] fix: Style ' ' after file sigil

---
 examples/expected_type.svg                            | 2 +-
 examples/footer.svg                                   | 2 +-
 examples/format.svg                                   | 2 +-
 examples/multislice.svg                               | 4 ++--
 src/renderer/display_list.rs                          | 4 ++--
 tests/fixtures/color/ann_eof.svg                      | 2 +-
 tests/fixtures/color/ann_insertion.svg                | 2 +-
 tests/fixtures/color/ann_multiline.svg                | 2 +-
 tests/fixtures/color/ann_multiline2.svg               | 2 +-
 tests/fixtures/color/ann_removed_nl.svg               | 2 +-
 tests/fixtures/color/ensure-emoji-highlight-width.svg | 2 +-
 tests/fixtures/color/fold_ann_multiline.svg           | 2 +-
 tests/fixtures/color/fold_bad_origin_line.svg         | 2 +-
 tests/fixtures/color/fold_leading.svg                 | 2 +-
 tests/fixtures/color/fold_trailing.svg                | 2 +-
 tests/fixtures/color/issue_9.svg                      | 2 +-
 tests/fixtures/color/simple.svg                       | 2 +-
 tests/fixtures/color/strip_line.svg                   | 2 +-
 tests/fixtures/color/strip_line_char.svg              | 2 +-
 tests/fixtures/color/strip_line_non_ws.svg            | 2 +-
 20 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/examples/expected_type.svg b/examples/expected_type.svg
index c3da76b..17a0a16 100644
--- a/examples/expected_type.svg
+++ b/examples/expected_type.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected type, found `22`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> examples/footer.rs:29:25</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>examples/footer.rs:29:25</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/examples/footer.svg b/examples/footer.svg
index 367a6d8..2f8eee7 100644
--- a/examples/footer.svg
+++ b/examples/footer.svg
@@ -22,7 +22,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/multislice.rs:13:22</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/multislice.rs:13:22</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/examples/format.svg b/examples/format.svg
index 9ecfc3d..f9bf80e 100644
--- a/examples/format.svg
+++ b/examples/format.svg
@@ -22,7 +22,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs:51:6</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format.rs:51:6</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/examples/multislice.svg b/examples/multislice.svg
index 6b325cb..bca0a56 100644
--- a/examples/multislice.svg
+++ b/examples/multislice.svg
@@ -21,13 +21,13 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs</tspan>
+    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format.rs</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 7f85a31..50a0fef 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -233,8 +233,8 @@ impl DisplaySet<'_> {
                 header_type,
             } => {
                 let header_sigil = match header_type {
-                    DisplayHeaderType::Initial => "-->",
-                    DisplayHeaderType::Continuation => ":::",
+                    DisplayHeaderType::Initial => "--> ",
+                    DisplayHeaderType::Continuation => "::: ",
                 };
                 let lineno_color = stylesheet.line_no();
                 buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color);
diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg
index c9a12c5..aeb4f8c 100644
--- a/tests/fixtures/color/ann_eof.svg
+++ b/tests/fixtures/color/ann_eof.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:5</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:1:5</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg
index cd42fe4..57c90a2 100644
--- a/tests/fixtures/color/ann_insertion.svg
+++ b/tests/fixtures/color/ann_insertion.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:3</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:1:3</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg
index 1e2d29e..3e813a0 100644
--- a/tests/fixtures/color/ann_multiline.svg
+++ b/tests/fixtures/color/ann_multiline.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0027]</tspan><tspan class="bold">: pattern does not mention fields `lineno`, `content`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/display_list.rs:139:32</tspan>
+    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/display_list.rs:139:32</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg
index bae12b8..24827f6 100644
--- a/tests/fixtures/color/ann_multiline2.svg
+++ b/tests/fixtures/color/ann_multiline2.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E####]</tspan><tspan class="bold">: spacing error found</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> foo.txt:26:12</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>foo.txt:26:12</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg
index c9a12c5..aeb4f8c 100644
--- a/tests/fixtures/color/ann_removed_nl.svg
+++ b/tests/fixtures/color/ann_removed_nl.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:5</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:1:5</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg
index b49915a..14624fb 100644
--- a/tests/fixtures/color/ensure-emoji-highlight-width.svg
+++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> &lt;file&gt;:7:1</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>&lt;file&gt;:7:1</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg
index c3a220d..b68a553 100644
--- a/tests/fixtures/color/fold_ann_multiline.svg
+++ b/tests/fixtures/color/fold_ann_multiline.svg
@@ -22,7 +22,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs:51:6</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format.rs:51:6</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg
index eb70d5f..4bd5f58 100644
--- a/tests/fixtures/color/fold_bad_origin_line.svg
+++ b/tests/fixtures/color/fold_bad_origin_line.svg
@@ -22,7 +22,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> path/to/error.rs:3:1</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>path/to/error.rs:3:1</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg
index b6cc607..23b31d4 100644
--- a/tests/fixtures/color/fold_leading.svg
+++ b/tests/fixtures/color/fold_leading.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:11:13</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:11:13</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg
index 54b6d9c..46071da 100644
--- a/tests/fixtures/color/fold_trailing.svg
+++ b/tests/fixtures/color/fold_trailing.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a lints table</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:9</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:1:9</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg
index f1f9044..35aa77b 100644
--- a/tests/fixtures/color/issue_9.svg
+++ b/tests/fixtures/color/issue_9.svg
@@ -22,7 +22,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg
index 6707403..d1d5d87 100644
--- a/tests/fixtures/color/simple.svg
+++ b/tests/fixtures/color/simple.svg
@@ -22,7 +22,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format_color.rs:171:9</tspan>
+    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format_color.rs:171:9</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg
index bcd77ae..14fbf9d 100644
--- a/tests/fixtures/color/strip_line.svg
+++ b/tests/fixtures/color/strip_line.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/whitespace-trimming.rs:4:193</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg
index f6bfe69..b37d4a9 100644
--- a/tests/fixtures/color/strip_line_char.svg
+++ b/tests/fixtures/color/strip_line_char.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/whitespace-trimming.rs:4:193</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg
index 4e5d33c..89047b3 100644
--- a/tests/fixtures/color/strip_line_non_ws.svg
+++ b/tests/fixtures/color/strip_line_non_ws.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/non-whitespace-trimming.rs:4:242</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>

From 96a2d7c13c341eba95a57d9a7dfc6ccd8a7b0c87 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 1 Apr 2025 17:20:37 -0600
Subject: [PATCH 273/302] fix: Add pipe to first snippet when not alone

---
 examples/multislice.svg          | 12 +++++++-----
 src/renderer/display_list.rs     | 14 ++++++++++++++
 tests/fixtures/color/issue_9.svg | 12 +++++++-----
 tests/formatter.rs               |  1 +
 tests/rustc_tests.rs             |  2 ++
 5 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/examples/multislice.svg b/examples/multislice.svg
index bca0a56..f0c1f65 100644
--- a/examples/multislice.svg
+++ b/examples/multislice.svg
@@ -1,4 +1,4 @@
-<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
+<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -27,13 +27,15 @@
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs</tspan>
+    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">129</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Faa</tspan>
+    <tspan x="10px" y="136px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="154px">
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Faa</tspan>
+</tspan>
+    <tspan x="10px" y="172px">
 </tspan>
   </text>
 
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 50a0fef..1a02883 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1006,11 +1006,13 @@ fn format_message(
         format_footer(level, id, title)
     };
 
+    let num_snippets = snippets.len();
     for (idx, snippet) in snippets.into_iter().enumerate() {
         let snippet = fold_prefix_suffix(snippet);
         sets.push(format_snippet(
             snippet,
             idx == 0,
+            idx == 0 && num_snippets > 1,
             term_width,
             anonymized_line_numbers,
         ));
@@ -1089,6 +1091,7 @@ fn format_label(
 fn format_snippet(
     snippet: snippet::Snippet<'_>,
     is_first: bool,
+    needs_trailing_pipe: bool,
     term_width: usize,
     anonymized_line_numbers: bool,
 ) -> DisplaySet<'_> {
@@ -1098,6 +1101,7 @@ fn format_snippet(
     let mut body = format_body(
         snippet,
         need_empty_header,
+        needs_trailing_pipe,
         term_width,
         anonymized_line_numbers,
     );
@@ -1285,6 +1289,7 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
 fn format_body(
     snippet: snippet::Snippet<'_>,
     need_empty_header: bool,
+    needs_trailing_pipe: bool,
     term_width: usize,
     anonymized_line_numbers: bool,
 ) -> DisplaySet<'_> {
@@ -1599,6 +1604,15 @@ fn format_body(
         );
     }
 
+    if needs_trailing_pipe {
+        body.push(DisplayLine::Source {
+            lineno: None,
+            inline_marks: vec![],
+            line: DisplaySourceLine::Empty,
+            annotations: vec![],
+        });
+    }
+
     let max_line_num_len = if anonymized_line_numbers {
         ANONYMIZED_LINE_NUM.len()
     } else {
diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg
index 35aa77b..6ba0199 100644
--- a/tests/fixtures/color/issue_9.svg
+++ b/tests/fixtures/color/issue_9.svg
@@ -1,4 +1,4 @@
-<svg width="911px" height="182px" xmlns="http://www.w3.org/2000/svg">
+<svg width="911px" height="200px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -30,13 +30,15 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
+    <tspan x="10px" y="154px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
+</tspan>
+    <tspan x="10px" y="190px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
 </tspan>
   </text>
 
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 7aa37d2..a4efbcc 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -146,6 +146,7 @@ error
     --> file1.rs
      |
 5402 | This is slice 1
+     |
     ::: file2.rs
      |
    2 | This is slice 2
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index f10f479..dab2e61 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -1323,6 +1323,7 @@ LL | |             ($bang_macro:ident, $attr_macro:ident) => {
 LL | |             }
 LL | |         }
    | |_________^
+   |
   ::: $DIR/nested-macro-rules.rs:23:5
    |
 LL |       nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct);
@@ -1697,6 +1698,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable
 for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
   --> $SRC_DIR/core/src/cmp.rs
    |
+   |
   ::: $DIR/object-fail.rs:3:7
    |
 LL | trait EqAlias = Eq;

From 936e9823da77466bfd2f85b346a1cfd55777945a Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 1 Apr 2025 17:35:00 -0600
Subject: [PATCH 274/302] fix: Left align line numbers

---
 examples/multislice.svg                       |  2 +-
 src/renderer/display_list.rs                  |  5 ++---
 tests/fixtures/color/multiple_annotations.svg |  8 ++++----
 tests/formatter.rs                            |  2 +-
 tests/rustc_tests.rs                          | 20 +++++++++----------
 5 files changed, 18 insertions(+), 19 deletions(-)

diff --git a/examples/multislice.svg b/examples/multislice.svg
index f0c1f65..bbb3fc9 100644
--- a/examples/multislice.svg
+++ b/examples/multislice.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 1a02883..08d5db3 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -292,11 +292,10 @@ impl DisplaySet<'_> {
             } => {
                 let lineno_color = stylesheet.line_no();
                 if anonymized_line_numbers && lineno.is_some() {
-                    let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$}");
-                    buffer.puts(line_offset, 0, &num, *lineno_color);
+                    buffer.puts(line_offset, 0, ANONYMIZED_LINE_NUM, *lineno_color);
                 } else {
                     if let Some(n) = lineno {
-                        let num = format!("{n:>lineno_width$}");
+                        let num = format!("{n}");
                         buffer.puts(line_offset, 0, &num, *lineno_color);
                     }
                 }
diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg
index 71628bd..26df4ae 100644
--- a/tests/fixtures/color/multiple_annotations.svg
+++ b/tests/fixtures/color/multiple_annotations.svg
@@ -23,15 +23,15 @@
 </tspan>
     <tspan x="10px" y="46px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
+    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">96</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     if let Some(annotation) = main_annotation {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">97</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     if let Some(annotation) = main_annotation {</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Variable defined here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         result.push(format_title_line(</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">98</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         result.push(format_title_line(</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             &amp;annotation.annotation_type,</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">99</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             &amp;annotation.annotation_type,</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>              </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced here</tspan>
 </tspan>
diff --git a/tests/formatter.rs b/tests/formatter.rs
index a4efbcc..d169421 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -149,7 +149,7 @@ error
      |
     ::: file2.rs
      |
-   2 | This is slice 2
+2    | This is slice 2
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index dab2e61..c2e72d3 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -686,13 +686,13 @@ fn foo() {
 error: foo
   --> test.rs:3:6
    |
- 3 |      X0 Y0 Z0
+3  |      X0 Y0 Z0
    |  _______^
- 4 | |    X1 Y1 Z1
+4  | |    X1 Y1 Z1
    | | ____^____-
    | ||____|
    |  |    `X` is a good letter
- 5 |  | 1
+5  |  | 1
 ...   |
 15 |  |   X2 Y2 Z2
 16 |  |   X3 Y3 Z3
@@ -738,15 +738,15 @@ fn foo() {
 error: foo
   --> test.rs:3:6
    |
- 3 |      X0 Y0 Z0
+3  |      X0 Y0 Z0
    |  _______^
- 4 | |  1
- 5 | |  2
- 6 | |  3
- 7 | |    X1 Y1 Z1
+4  | |  1
+5  | |  2
+6  | |  3
+7  | |    X1 Y1 Z1
    | | _________-
- 8 | || 4
- 9 | || 5
+8  | || 4
+9  | || 5
 10 | || 6
 11 | ||   X2 Y2 Z2
    | ||__________- `Z` is a good letter too

From 84d07af1e54aaaf08f014edae34a478829e7e9c9 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sun, 9 Feb 2025 08:54:15 -0700
Subject: [PATCH 275/302] feat: Better match rustc's internal renderer

---
 Cargo.lock                                    |   12 +-
 Cargo.toml                                    |    2 +
 benches/bench.rs                              |    4 +-
 examples/expected_type.svg                    |   16 +-
 examples/footer.svg                           |    3 +-
 src/renderer/display_list.rs                  | 1735 -----------------
 src/renderer/mod.rs                           | 1428 +++++++++++++-
 src/renderer/source_map.rs                    |  446 +++++
 src/renderer/styled_buffer.rs                 |   40 +-
 src/renderer/stylesheet.rs                    |   34 -
 src/snippet.rs                                |   85 +-
 tests/fixtures/color/ann_multiline.svg        |    2 +-
 tests/fixtures/color/fold_ann_multiline.svg   |   12 +-
 tests/fixtures/color/fold_bad_origin_line.svg |    2 +-
 tests/fixtures/color/fold_leading.svg         |    8 +-
 tests/fixtures/color/issue_9.svg              |   22 +-
 tests/fixtures/color/multiple_annotations.svg |    2 +-
 tests/fixtures/color/simple.svg               |    2 +-
 tests/fixtures/color/strip_line_non_ws.svg    |    2 +-
 tests/fixtures/color/strip_line_non_ws.toml   |    4 +-
 tests/fixtures/main.rs                        |    2 +-
 tests/formatter.rs                            |  182 +-
 tests/rustc_tests.rs                          |  116 +-
 23 files changed, 2179 insertions(+), 1982 deletions(-)
 delete mode 100644 src/renderer/display_list.rs
 create mode 100644 src/renderer/source_map.rs

diff --git a/Cargo.lock b/Cargo.lock
index c8c4d67..e8aebe5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,7 +21,9 @@ dependencies = [
  "difference",
  "divan",
  "glob",
+ "indexmap",
  "memchr",
+ "rustc-hash",
  "serde",
  "snapbox",
  "toml",
@@ -334,9 +336,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.6.0"
+version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
 dependencies = [
  "equivalent",
  "hashbrown",
@@ -506,6 +508,12 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
+[[package]]
+name = "rustc-hash"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
+
 [[package]]
 name = "rustix"
 version = "0.37.27"
diff --git a/Cargo.toml b/Cargo.toml
index f30c64f..95067ce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -117,7 +117,9 @@ maintenance = { status = "actively-developed" }
 
 [dependencies]
 anstyle = "1.0.4"
+indexmap = "2.7.0"
 memchr = { version = "2.7.4", optional = true }
+rustc-hash = "2.1.0"
 unicode-width = "0.2.0"
 
 [dev-dependencies]
diff --git a/benches/bench.rs b/benches/bench.rs
index 9747954..ed4e82c 100644
--- a/benches/bench.rs
+++ b/benches/bench.rs
@@ -41,7 +41,7 @@ fn simple() -> String {
     );
 
     let renderer = Renderer::plain();
-    let rendered = renderer.render(message).to_string();
+    let rendered = renderer.render(message);
     rendered
 }
 
@@ -79,7 +79,7 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) {
             );
 
             let renderer = Renderer::plain();
-            let rendered = renderer.render(message).to_string();
+            let rendered = renderer.render(message);
             rendered
         });
 }
diff --git a/examples/expected_type.svg b/examples/expected_type.svg
index 17a0a16..749fc3a 100644
--- a/examples/expected_type.svg
+++ b/examples/expected_type.svg
@@ -1,4 +1,4 @@
-<svg width="860px" height="200px" xmlns="http://www.w3.org/2000/svg">
+<svg width="860px" height="182px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -21,23 +21,21 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected type, found `22`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>examples/footer.rs:29:25</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>examples/footer.rs:26:35</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 annotations: vec![SourceAnnotation {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info: while parsing this struct</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                                   </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">while parsing this struct</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">...</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                     ,</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">29</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
+    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                         </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                         </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
-</tspan>
-    <tspan x="10px" y="190px">
+    <tspan x="10px" y="172px">
 </tspan>
   </text>
 
diff --git a/examples/footer.svg b/examples/footer.svg
index 2f8eee7..e3bb892 100644
--- a/examples/footer.svg
+++ b/examples/footer.svg
@@ -3,7 +3,6 @@
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
-    .fg-bright-green { fill: #55FF55 }
     .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
@@ -30,7 +29,7 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                      </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
 </tspan>
     <tspan x="10px" y="136px"><tspan>              found type: `__&amp;__snippet::Annotation`</tspan>
 </tspan>
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
deleted file mode 100644
index 08d5db3..0000000
--- a/src/renderer/display_list.rs
+++ /dev/null
@@ -1,1735 +0,0 @@
-//! `display_list` module stores the output model for the snippet.
-//!
-//! `DisplayList` is a central structure in the crate, which contains
-//! the structured list of lines to be displayed.
-//!
-//! It is made of two types of lines: `Source` and `Raw`. All `Source` lines
-//! are structured using four columns:
-//!
-//! ```text
-//!  /------------ (1) Line number column.
-//!  |  /--------- (2) Line number column delimiter.
-//!  |  | /------- (3) Inline marks column.
-//!  |  | |   /--- (4) Content column with the source and annotations for slices.
-//!  |  | |   |
-//! =============================================================================
-//! error[E0308]: mismatched types
-//!    --> src/format.rs:51:5
-//!     |
-//! 151 | /   fn test() -> String {
-//! 152 | |       return "test";
-//! 153 | |   }
-//!     | |___^ error: expected `String`, for `&str`.
-//! ```
-//!
-//! The first two lines of the example above are `Raw` lines, while the rest
-//! are `Source` lines.
-//!
-//! `DisplayList` does not store column alignment information, and those are
-//! only calculated by the implementation of `std::fmt::Display` using information such as
-//! styling.
-//!
-//! The above snippet has been built out of the following structure:
-use crate::snippet;
-use std::cmp::{max, min, Reverse};
-use std::collections::HashMap;
-use std::fmt::Display;
-use std::ops::Range;
-use std::{cmp, fmt};
-
-use crate::renderer::styled_buffer::StyledBuffer;
-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(crate) body: Vec<DisplaySet<'a>>,
-    pub(crate) stylesheet: &'a Stylesheet,
-    pub(crate) anonymized_line_numbers: bool,
-}
-
-impl PartialEq for DisplayList<'_> {
-    fn eq(&self, other: &Self) -> bool {
-        self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
-    }
-}
-
-impl fmt::Debug for DisplayList<'_> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("DisplayList")
-            .field("body", &self.body)
-            .field("anonymized_line_numbers", &self.anonymized_line_numbers)
-            .finish()
-    }
-}
-
-impl Display for DisplayList<'_> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        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 {
-            ANONYMIZED_LINE_NUM.len()
-        } else {
-            ((lineno_width as f64).log10().floor() as usize) + 1
-        };
-
-        let multiline_depth = self.body.iter().fold(0, |max, set| {
-            set.display_lines.iter().fold(max, |max2, line| match line {
-                DisplayLine::Source { annotations, .. } => cmp::max(
-                    annotations.iter().fold(max2, |max3, line| {
-                        cmp::max(
-                            match line.annotation_part {
-                                DisplayAnnotationPart::Standalone => 0,
-                                DisplayAnnotationPart::LabelContinuation => 0,
-                                DisplayAnnotationPart::MultilineStart(depth) => depth + 1,
-                                DisplayAnnotationPart::MultilineEnd(depth) => depth + 1,
-                            },
-                            max3,
-                        )
-                    }),
-                    max,
-                ),
-                _ => max2,
-            })
-        });
-        let mut buffer = StyledBuffer::new();
-        for set in self.body.iter() {
-            self.format_set(set, lineno_width, multiline_depth, &mut buffer)?;
-        }
-        write!(f, "{}", buffer.render(self.stylesheet)?)
-    }
-}
-
-impl<'a> DisplayList<'a> {
-    pub(crate) fn new(
-        message: snippet::Message<'a>,
-        stylesheet: &'a Stylesheet,
-        anonymized_line_numbers: bool,
-        term_width: usize,
-    ) -> DisplayList<'a> {
-        let body = format_message(message, term_width, anonymized_line_numbers, true);
-
-        Self {
-            body,
-            stylesheet,
-            anonymized_line_numbers,
-        }
-    }
-
-    fn format_set(
-        &self,
-        set: &DisplaySet<'_>,
-        lineno_width: usize,
-        multiline_depth: usize,
-        buffer: &mut StyledBuffer,
-    ) -> fmt::Result {
-        for line in &set.display_lines {
-            set.format_line(
-                line,
-                lineno_width,
-                multiline_depth,
-                self.stylesheet,
-                self.anonymized_line_numbers,
-                buffer,
-            )?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub(crate) struct DisplaySet<'a> {
-    pub(crate) display_lines: Vec<DisplayLine<'a>>,
-    pub(crate) margin: Margin,
-}
-
-impl DisplaySet<'_> {
-    fn format_label(
-        &self,
-        line_offset: usize,
-        label: &[DisplayTextFragment<'_>],
-        needs_colon: bool,
-        stylesheet: &Stylesheet,
-        buffer: &mut StyledBuffer,
-    ) -> fmt::Result {
-        for (i, fragment) in label.iter().enumerate() {
-            let style = match fragment.style {
-                DisplayTextStyle::Regular => stylesheet.none(),
-                DisplayTextStyle::Emphasis => stylesheet.emphasis(),
-            };
-            if i == 0 && needs_colon {
-                buffer.append(line_offset, ": ", *style);
-            }
-            buffer.append(line_offset, fragment.content, *style);
-        }
-        Ok(())
-    }
-    fn format_annotation(
-        &self,
-        line_offset: usize,
-        annotation: &Annotation<'_>,
-        continuation: bool,
-        stylesheet: &Stylesheet,
-        buffer: &mut StyledBuffer,
-    ) -> fmt::Result {
-        let color = get_annotation_style(&annotation.annotation_type, stylesheet);
-        let formatted_len = if let Some(id) = &annotation.id {
-            2 + id.len() + annotation_type_len(&annotation.annotation_type)
-        } else {
-            annotation_type_len(&annotation.annotation_type)
-        };
-
-        if continuation {
-            for _ in 0..formatted_len + 2 {
-                buffer.append(line_offset, " ", Style::new());
-            }
-            return self.format_label(line_offset, &annotation.label, false, stylesheet, buffer);
-        }
-        if formatted_len == 0 {
-            self.format_label(line_offset, &annotation.label, false, stylesheet, buffer)
-        } else {
-            let id = match &annotation.id {
-                Some(id) => format!("[{id}]"),
-                None => String::new(),
-            };
-            buffer.append(
-                line_offset,
-                &format!("{}{}", annotation_type_str(&annotation.annotation_type), id),
-                *color,
-            );
-
-            if !is_annotation_empty(annotation) {
-                self.format_label(line_offset, &annotation.label, true, stylesheet, buffer)?;
-            }
-            Ok(())
-        }
-    }
-
-    #[inline]
-    fn format_raw_line(
-        &self,
-        line_offset: usize,
-        line: &DisplayRawLine<'_>,
-        lineno_width: usize,
-        stylesheet: &Stylesheet,
-        buffer: &mut StyledBuffer,
-    ) -> fmt::Result {
-        match line {
-            DisplayRawLine::Origin {
-                path,
-                pos,
-                header_type,
-            } => {
-                let header_sigil = match header_type {
-                    DisplayHeaderType::Initial => "--> ",
-                    DisplayHeaderType::Continuation => "::: ",
-                };
-                let lineno_color = stylesheet.line_no();
-                buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color);
-                buffer.puts(line_offset, lineno_width + 4, path, stylesheet.none);
-                if let Some((col, row)) = pos {
-                    buffer.append(line_offset, ":", stylesheet.none);
-                    buffer.append(line_offset, col.to_string().as_str(), stylesheet.none);
-                    buffer.append(line_offset, ":", stylesheet.none);
-                    buffer.append(line_offset, row.to_string().as_str(), stylesheet.none);
-                }
-                Ok(())
-            }
-            DisplayRawLine::Annotation {
-                annotation,
-                source_aligned,
-                continuation,
-            } => {
-                if *source_aligned {
-                    if *continuation {
-                        for _ in 0..lineno_width + 3 {
-                            buffer.append(line_offset, " ", stylesheet.none);
-                        }
-                    } else {
-                        let lineno_color = stylesheet.line_no();
-                        for _ in 0..lineno_width + 1 {
-                            buffer.append(line_offset, " ", stylesheet.none);
-                        }
-                        buffer.append(line_offset, "=", *lineno_color);
-                        buffer.append(line_offset, " ", *lineno_color);
-                    }
-                }
-                self.format_annotation(line_offset, annotation, *continuation, stylesheet, buffer)
-            }
-        }
-    }
-
-    // Adapted from https://github.com/rust-lang/rust/blob/d371d17496f2ce3a56da76aa083f4ef157572c20/compiler/rustc_errors/src/emitter.rs#L706-L1211
-    #[inline]
-    fn format_line(
-        &self,
-        dl: &DisplayLine<'_>,
-        lineno_width: usize,
-        multiline_depth: usize,
-        stylesheet: &Stylesheet,
-        anonymized_line_numbers: bool,
-        buffer: &mut StyledBuffer,
-    ) -> fmt::Result {
-        let line_offset = buffer.num_lines();
-        match dl {
-            DisplayLine::Source {
-                lineno,
-                inline_marks,
-                line,
-                annotations,
-            } => {
-                let lineno_color = stylesheet.line_no();
-                if anonymized_line_numbers && lineno.is_some() {
-                    buffer.puts(line_offset, 0, ANONYMIZED_LINE_NUM, *lineno_color);
-                } else {
-                    if let Some(n) = lineno {
-                        let num = format!("{n}");
-                        buffer.puts(line_offset, 0, &num, *lineno_color);
-                    }
-                }
-                buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color);
-                if let DisplaySourceLine::Content { text, .. } = line {
-                    // The width of the line number, a space, pipe, and a space
-                    // `123 | ` is `lineno_width + 3`.
-                    let width_offset = lineno_width + 3;
-                    let code_offset = if multiline_depth == 0 {
-                        width_offset
-                    } else {
-                        width_offset + multiline_depth + 1
-                    };
-
-                    // Add any inline marks to the code line
-                    if !inline_marks.is_empty() || 0 < multiline_depth {
-                        format_inline_marks(
-                            line_offset,
-                            inline_marks,
-                            lineno_width,
-                            stylesheet,
-                            buffer,
-                        )?;
-                    }
-
-                    let text = normalize_whitespace(text);
-                    let line_len = text.len();
-                    let left = self.margin.left(line_len);
-                    let right = self.margin.right(line_len);
-
-                    // 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();
-                    buffer.puts(line_offset, code_offset, &code, Style::new());
-                    if self.margin.was_cut_left() {
-                        // We have stripped some code/whitespace from the beginning, make it clear.
-                        buffer.puts(line_offset, code_offset, "...", *lineno_color);
-                    }
-                    if self.margin.was_cut_right(line_len) {
-                        buffer.puts(line_offset, code_offset + taken - 3, "...", *lineno_color);
-                    }
-
-                    let left: usize = text
-                        .chars()
-                        .take(left)
-                        .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
-                        .sum();
-
-                    let mut annotations = annotations.clone();
-                    annotations.sort_by_key(|a| Reverse(a.range.0));
-
-                    let mut annotations_positions = vec![];
-                    let mut line_len: usize = 0;
-                    let mut p = 0;
-                    for (i, annotation) in annotations.iter().enumerate() {
-                        for (j, next) in annotations.iter().enumerate() {
-                            // This label overlaps with another one and both take space (
-                            // they have text and are not multiline lines).
-                            if overlaps(next, annotation, 0)
-                                && annotation.has_label()
-                                && j > i
-                                && p == 0
-                            // We're currently on the first line, move the label one line down
-                            {
-                                // If we're overlapping with an un-labelled annotation with the same span
-                                // we can just merge them in the output
-                                if next.range.0 == annotation.range.0
-                                    && next.range.1 == annotation.range.1
-                                    && !next.has_label()
-                                {
-                                    continue;
-                                }
-
-                                // This annotation needs a new line in the output.
-                                p += 1;
-                                break;
-                            }
-                        }
-                        annotations_positions.push((p, annotation));
-                        for (j, next) in annotations.iter().enumerate() {
-                            if j > i {
-                                let l = next
-                                    .annotation
-                                    .label
-                                    .iter()
-                                    .map(|label| label.content)
-                                    .collect::<Vec<_>>()
-                                    .join("")
-                                    .len()
-                                    + 2;
-                                // Do not allow two labels to be in the same line if they
-                                // overlap including padding, to avoid situations like:
-                                //
-                                // fn foo(x: u32) {
-                                // -------^------
-                                // |      |
-                                // fn_spanx_span
-                                //
-                                // Both labels must have some text, otherwise they are not
-                                // overlapping. Do not add a new line if this annotation or
-                                // the next are vertical line placeholders. If either this
-                                // or the next annotation is multiline start/end, move it
-                                // to a new line so as not to overlap the horizontal lines.
-                                if (overlaps(next, annotation, l)
-                                    && annotation.has_label()
-                                    && next.has_label())
-                                    || (annotation.takes_space() && next.has_label())
-                                    || (annotation.has_label() && next.takes_space())
-                                    || (annotation.takes_space() && next.takes_space())
-                                    || (overlaps(next, annotation, l)
-                                        && next.range.1 <= annotation.range.1
-                                        && next.has_label()
-                                        && p == 0)
-                                // Avoid #42595.
-                                {
-                                    // This annotation needs a new line in the output.
-                                    p += 1;
-                                    break;
-                                }
-                            }
-                        }
-                        line_len = max(line_len, p);
-                    }
-
-                    if line_len != 0 {
-                        line_len += 1;
-                    }
-
-                    if annotations_positions.iter().all(|(_, ann)| {
-                        matches!(
-                            ann.annotation_part,
-                            DisplayAnnotationPart::MultilineStart(_)
-                        )
-                    }) {
-                        if let Some(max_pos) =
-                            annotations_positions.iter().map(|(pos, _)| *pos).max()
-                        {
-                            // Special case the following, so that we minimize overlapping multiline spans.
-                            //
-                            // 3 │       X0 Y0 Z0
-                            //   │ ┏━━━━━┛  │  │     < We are writing these lines
-                            //   │ ┃┌───────┘  │     < by reverting the "depth" of
-                            //   │ ┃│┌─────────┘     < their multilne spans.
-                            // 4 │ ┃││   X1 Y1 Z1
-                            // 5 │ ┃││   X2 Y2 Z2
-                            //   │ ┃│└────╿──│──┘ `Z` label
-                            //   │ ┃└─────│──┤
-                            //   │ ┗━━━━━━┥  `Y` is a good letter too
-                            //   ╰╴       `X` is a good letter
-                            for (pos, _) in &mut annotations_positions {
-                                *pos = max_pos - *pos;
-                            }
-                            // We know then that we don't need an additional line for the span label, saving us
-                            // one line of vertical space.
-                            line_len = line_len.saturating_sub(1);
-                        }
-                    }
-
-                    // This is a special case where we have a multiline
-                    // annotation that is at the start of the line disregarding
-                    // any leading whitespace, and no other multiline
-                    // annotations overlap it. In this case, we want to draw
-                    //
-                    // 2 |   fn foo() {
-                    //   |  _^
-                    // 3 | |
-                    // 4 | | }
-                    //   | |_^ test
-                    //
-                    // we simplify the output to:
-                    //
-                    // 2 | / fn foo() {
-                    // 3 | |
-                    // 4 | | }
-                    //   | |_^ test
-                    if multiline_depth == 1
-                        && annotations_positions.len() == 1
-                        && annotations_positions
-                            .first()
-                            .map_or(false, |(_, annotation)| {
-                                matches!(
-                                    annotation.annotation_part,
-                                    DisplayAnnotationPart::MultilineStart(_)
-                                ) && text
-                                    .chars()
-                                    .take(annotation.range.0)
-                                    .all(|c| c.is_whitespace())
-                            })
-                    {
-                        let (_, ann) = annotations_positions.remove(0);
-                        let style = get_annotation_style(&ann.annotation_type, stylesheet);
-                        buffer.putc(line_offset, 3 + lineno_width, '/', *style);
-                    }
-
-                    // Draw the column separator for any extra lines that were
-                    // created
-                    //
-                    // After this we will have:
-                    //
-                    // 2 |   fn foo() {
-                    //   |
-                    //   |
-                    //   |
-                    // 3 |
-                    // 4 |   }
-                    //   |
-                    if !annotations_positions.is_empty() {
-                        for pos in 0..=line_len {
-                            buffer.putc(
-                                line_offset + pos + 1,
-                                lineno_width + 1,
-                                '|',
-                                stylesheet.line_no,
-                            );
-                        }
-                    }
-
-                    // Write the horizontal lines for multiline annotations
-                    // (only the first and last lines need this).
-                    //
-                    // After this we will have:
-                    //
-                    // 2 |   fn foo() {
-                    //   |  __________
-                    //   |
-                    //   |
-                    // 3 |
-                    // 4 |   }
-                    //   |  _
-                    for &(pos, annotation) in &annotations_positions {
-                        let style = get_annotation_style(&annotation.annotation_type, stylesheet);
-                        let pos = pos + 1;
-                        match annotation.annotation_part {
-                            DisplayAnnotationPart::MultilineStart(depth)
-                            | DisplayAnnotationPart::MultilineEnd(depth) => {
-                                for col in width_offset + depth
-                                    ..(code_offset + annotation.range.0).saturating_sub(left)
-                                {
-                                    buffer.putc(line_offset + pos, col + 1, '_', *style);
-                                }
-                            }
-                            _ => {}
-                        }
-                    }
-
-                    // Write the vertical lines for labels that are on a different line as the underline.
-                    //
-                    // After this we will have:
-                    //
-                    // 2 |   fn foo() {
-                    //   |  __________
-                    //   | |    |
-                    //   | |
-                    // 3 | |
-                    // 4 | | }
-                    //   | |_
-                    for &(pos, annotation) in &annotations_positions {
-                        let style = get_annotation_style(&annotation.annotation_type, stylesheet);
-                        let pos = pos + 1;
-                        if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
-                            for p in line_offset + 2..=line_offset + pos {
-                                buffer.putc(
-                                    p,
-                                    (code_offset + annotation.range.0).saturating_sub(left),
-                                    '|',
-                                    *style,
-                                );
-                            }
-                        }
-                        match annotation.annotation_part {
-                            DisplayAnnotationPart::MultilineStart(depth) => {
-                                for p in line_offset + pos + 1..line_offset + line_len + 2 {
-                                    buffer.putc(p, width_offset + depth, '|', *style);
-                                }
-                            }
-                            DisplayAnnotationPart::MultilineEnd(depth) => {
-                                for p in line_offset..=line_offset + pos {
-                                    buffer.putc(p, width_offset + depth, '|', *style);
-                                }
-                            }
-                            _ => {}
-                        }
-                    }
-
-                    // Add in any inline marks for any extra lines that have
-                    // been created. Output should look like above.
-                    for inline_mark in inline_marks {
-                        let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type;
-                        let style = get_annotation_style(&inline_mark.annotation_type, stylesheet);
-                        if annotations_positions.is_empty() {
-                            buffer.putc(line_offset, width_offset + depth, '|', *style);
-                        } else {
-                            for p in line_offset..=line_offset + line_len + 1 {
-                                buffer.putc(p, width_offset + depth, '|', *style);
-                            }
-                        }
-                    }
-
-                    // Write the labels on the annotations that actually have a label.
-                    //
-                    // After this we will have:
-                    //
-                    // 2 |   fn foo() {
-                    //   |  __________
-                    //   |      |
-                    //   |      something about `foo`
-                    // 3 |
-                    // 4 |   }
-                    //   |  _  test
-                    for &(pos, annotation) in &annotations_positions {
-                        if !is_annotation_empty(&annotation.annotation) {
-                            let style =
-                                get_annotation_style(&annotation.annotation_type, stylesheet);
-                            let mut formatted_len = if let Some(id) = &annotation.annotation.id {
-                                2 + id.len()
-                                    + annotation_type_len(&annotation.annotation.annotation_type)
-                            } else {
-                                annotation_type_len(&annotation.annotation.annotation_type)
-                            };
-                            let (pos, col) = if pos == 0 {
-                                (pos + 1, (annotation.range.1 + 1).saturating_sub(left))
-                            } else {
-                                (pos + 2, annotation.range.0.saturating_sub(left))
-                            };
-                            if annotation.annotation_part
-                                == DisplayAnnotationPart::LabelContinuation
-                            {
-                                formatted_len = 0;
-                            } else if formatted_len != 0 {
-                                formatted_len += 2;
-                                let id = match &annotation.annotation.id {
-                                    Some(id) => format!("[{id}]"),
-                                    None => String::new(),
-                                };
-                                buffer.puts(
-                                    line_offset + pos,
-                                    col + code_offset,
-                                    &format!(
-                                        "{}{}: ",
-                                        annotation_type_str(&annotation.annotation_type),
-                                        id
-                                    ),
-                                    *style,
-                                );
-                            } else {
-                                formatted_len = 0;
-                            }
-                            let mut before = 0;
-                            for fragment in &annotation.annotation.label {
-                                let inner_col = before + formatted_len + col + code_offset;
-                                buffer.puts(line_offset + pos, inner_col, fragment.content, *style);
-                                before += fragment.content.len();
-                            }
-                        }
-                    }
-
-                    // Sort from biggest span to smallest span so that smaller spans are
-                    // represented in the output:
-                    //
-                    // x | fn foo()
-                    //   | ^^^---^^
-                    //   | |  |
-                    //   | |  something about `foo`
-                    //   | something about `fn foo()`
-                    annotations_positions.sort_by_key(|(_, ann)| {
-                        // Decreasing order. When annotations share the same length, prefer `Primary`.
-                        Reverse(ann.len())
-                    });
-
-                    // Write the underlines.
-                    //
-                    // After this we will have:
-                    //
-                    // 2 |   fn foo() {
-                    //   |  ____-_____^
-                    //   |      |
-                    //   |      something about `foo`
-                    // 3 |
-                    // 4 |   }
-                    //   |  _^  test
-                    for &(_, annotation) in &annotations_positions {
-                        let mark = match annotation.annotation_type {
-                            DisplayAnnotationType::Error => '^',
-                            DisplayAnnotationType::Warning => '-',
-                            DisplayAnnotationType::Info => '-',
-                            DisplayAnnotationType::Note => '-',
-                            DisplayAnnotationType::Help => '-',
-                            DisplayAnnotationType::None => ' ',
-                        };
-                        let style = get_annotation_style(&annotation.annotation_type, stylesheet);
-                        for p in annotation.range.0..annotation.range.1 {
-                            buffer.putc(
-                                line_offset + 1,
-                                (code_offset + p).saturating_sub(left),
-                                mark,
-                                *style,
-                            );
-                        }
-                    }
-                } else if !inline_marks.is_empty() {
-                    format_inline_marks(
-                        line_offset,
-                        inline_marks,
-                        lineno_width,
-                        stylesheet,
-                        buffer,
-                    )?;
-                }
-                Ok(())
-            }
-            DisplayLine::Fold { inline_marks } => {
-                buffer.puts(line_offset, 0, "...", *stylesheet.line_no());
-                if !inline_marks.is_empty() || 0 < multiline_depth {
-                    format_inline_marks(
-                        line_offset,
-                        inline_marks,
-                        lineno_width,
-                        stylesheet,
-                        buffer,
-                    )?;
-                }
-                Ok(())
-            }
-            DisplayLine::Raw(line) => {
-                self.format_raw_line(line_offset, line, lineno_width, stylesheet, buffer)
-            }
-        }
-    }
-}
-
-/// Inline annotation which can be used in either Raw or Source line.
-#[derive(Clone, Debug, PartialEq)]
-pub(crate) struct Annotation<'a> {
-    pub(crate) annotation_type: DisplayAnnotationType,
-    pub(crate) id: Option<&'a str>,
-    pub(crate) label: Vec<DisplayTextFragment<'a>>,
-}
-
-/// A single line used in `DisplayList`.
-#[derive(Debug, PartialEq)]
-pub(crate) enum DisplayLine<'a> {
-    /// A line with `lineno` portion of the slice.
-    Source {
-        lineno: Option<usize>,
-        inline_marks: Vec<DisplayMark>,
-        line: DisplaySourceLine<'a>,
-        annotations: Vec<DisplaySourceAnnotation<'a>>,
-    },
-
-    /// A line indicating a folded part of the slice.
-    Fold { inline_marks: Vec<DisplayMark> },
-
-    /// A line which is displayed outside of slices.
-    Raw(DisplayRawLine<'a>),
-}
-
-/// A source line.
-#[derive(Debug, PartialEq)]
-pub(crate) enum DisplaySourceLine<'a> {
-    /// A line with the content of the Snippet.
-    Content {
-        text: &'a str,
-        range: (usize, usize), // meta information for annotation placement.
-        end_line: EndLine,
-    },
-    /// An empty source line.
-    Empty,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub(crate) struct DisplaySourceAnnotation<'a> {
-    pub(crate) annotation: Annotation<'a>,
-    pub(crate) range: (usize, usize),
-    pub(crate) annotation_type: DisplayAnnotationType,
-    pub(crate) annotation_part: DisplayAnnotationPart,
-}
-
-impl DisplaySourceAnnotation<'_> {
-    fn has_label(&self) -> bool {
-        !self
-            .annotation
-            .label
-            .iter()
-            .all(|label| label.content.is_empty())
-    }
-
-    // Length of this annotation as displayed in the stderr output
-    fn len(&self) -> usize {
-        // Account for usize underflows
-        if self.range.1 > self.range.0 {
-            self.range.1 - self.range.0
-        } else {
-            self.range.0 - self.range.1
-        }
-    }
-
-    fn takes_space(&self) -> bool {
-        // Multiline annotations always have to keep vertical space.
-        matches!(
-            self.annotation_part,
-            DisplayAnnotationPart::MultilineStart(_) | DisplayAnnotationPart::MultilineEnd(_)
-        )
-    }
-}
-
-/// Raw line - a line which does not have the `lineno` part and is not considered
-/// a part of the snippet.
-#[derive(Debug, PartialEq)]
-pub(crate) enum DisplayRawLine<'a> {
-    /// A line which provides information about the location of the given
-    /// slice in the project structure.
-    Origin {
-        path: &'a str,
-        pos: Option<(usize, usize)>,
-        header_type: DisplayHeaderType,
-    },
-
-    /// An annotation line which is not part of any snippet.
-    Annotation {
-        annotation: Annotation<'a>,
-
-        /// If set to `true`, the annotation will be aligned to the
-        /// lineno delimiter of the snippet.
-        source_aligned: bool,
-        /// If set to `true`, only the label of the `Annotation` will be
-        /// displayed. It allows for a multiline annotation to be aligned
-        /// without displaying the meta information (`type` and `id`) to be
-        /// displayed on each line.
-        continuation: bool,
-    },
-}
-
-/// An inline text fragment which any label is composed of.
-#[derive(Clone, Debug, PartialEq)]
-pub(crate) struct DisplayTextFragment<'a> {
-    pub(crate) content: &'a str,
-    pub(crate) style: DisplayTextStyle,
-}
-
-/// A style for the `DisplayTextFragment` which can be visually formatted.
-///
-/// This information may be used to emphasis parts of the label.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub(crate) enum DisplayTextStyle {
-    Regular,
-    Emphasis,
-}
-
-/// An indicator of what part of the annotation a given `Annotation` is.
-#[derive(Debug, Clone, PartialEq)]
-pub(crate) enum DisplayAnnotationPart {
-    /// A standalone, single-line annotation.
-    Standalone,
-    /// A continuation of a multi-line label of an annotation.
-    LabelContinuation,
-    /// A line starting a multiline annotation.
-    MultilineStart(usize),
-    /// A line ending a multiline annotation.
-    MultilineEnd(usize),
-}
-
-/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
-#[derive(Debug, Clone, PartialEq)]
-pub(crate) struct DisplayMark {
-    pub(crate) mark_type: DisplayMarkType,
-    pub(crate) annotation_type: DisplayAnnotationType,
-}
-
-/// A type of the `DisplayMark`.
-#[derive(Debug, Clone, PartialEq)]
-pub(crate) enum DisplayMarkType {
-    /// A mark indicating a multiline annotation going through the current line.
-    AnnotationThrough(usize),
-}
-
-/// A type of the `Annotation` which may impact the sigils, style or text displayed.
-///
-/// There are several ways to uses this information when formatting the `DisplayList`:
-///
-/// * An annotation may display the name of the type like `error` or `info`.
-/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`.
-/// * `ColorStylesheet` may use different colors for different annotations.
-#[derive(Debug, Clone, PartialEq)]
-pub(crate) enum DisplayAnnotationType {
-    None,
-    Error,
-    Warning,
-    Info,
-    Note,
-    Help,
-}
-
-impl From<snippet::Level> for DisplayAnnotationType {
-    fn from(at: snippet::Level) -> Self {
-        match at {
-            snippet::Level::Error => DisplayAnnotationType::Error,
-            snippet::Level::Warning => DisplayAnnotationType::Warning,
-            snippet::Level::Info => DisplayAnnotationType::Info,
-            snippet::Level::Note => DisplayAnnotationType::Note,
-            snippet::Level::Help => DisplayAnnotationType::Help,
-        }
-    }
-}
-
-/// Information whether the header is the initial one or a consequitive one
-/// for multi-slice cases.
-// TODO: private
-#[derive(Debug, Clone, PartialEq)]
-pub(crate) enum DisplayHeaderType {
-    /// Initial header is the first header in the snippet.
-    Initial,
-
-    /// Continuation marks all headers of following slices in the snippet.
-    Continuation,
-}
-
-struct CursorLines<'a>(&'a str);
-
-impl CursorLines<'_> {
-    fn new(src: &str) -> CursorLines<'_> {
-        CursorLines(src)
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub(crate) enum EndLine {
-    Eof,
-    Lf,
-    Crlf,
-}
-
-impl EndLine {
-    /// The number of characters this line ending occupies in bytes.
-    pub(crate) fn len(self) -> usize {
-        match self {
-            EndLine::Eof => 0,
-            EndLine::Lf => 1,
-            EndLine::Crlf => 2,
-        }
-    }
-}
-
-impl<'a> Iterator for CursorLines<'a> {
-    type Item = (&'a str, EndLine);
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.0.is_empty() {
-            None
-        } else {
-            self.0
-                .find('\n')
-                .map(|x| {
-                    let ret = if 0 < x {
-                        if self.0.as_bytes()[x - 1] == b'\r' {
-                            (&self.0[..x - 1], EndLine::Crlf)
-                        } else {
-                            (&self.0[..x], EndLine::Lf)
-                        }
-                    } else {
-                        ("", EndLine::Lf)
-                    };
-                    self.0 = &self.0[x + 1..];
-                    ret
-                })
-                .or_else(|| {
-                    let ret = Some((self.0, EndLine::Eof));
-                    self.0 = "";
-                    ret
-                })
-        }
-    }
-}
-
-fn format_message(
-    message: snippet::Message<'_>,
-    term_width: usize,
-    anonymized_line_numbers: bool,
-    primary: bool,
-) -> Vec<DisplaySet<'_>> {
-    let snippet::Message {
-        level,
-        id,
-        title,
-        footer,
-        snippets,
-    } = message;
-
-    let mut sets = vec![];
-    let body = if !snippets.is_empty() || primary {
-        vec![format_title(level, id, title)]
-    } else {
-        format_footer(level, id, title)
-    };
-
-    let num_snippets = snippets.len();
-    for (idx, snippet) in snippets.into_iter().enumerate() {
-        let snippet = fold_prefix_suffix(snippet);
-        sets.push(format_snippet(
-            snippet,
-            idx == 0,
-            idx == 0 && num_snippets > 1,
-            term_width,
-            anonymized_line_numbers,
-        ));
-    }
-
-    if let Some(first) = sets.first_mut() {
-        for line in body {
-            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 {
-        sets.extend(format_message(
-            annotation,
-            term_width,
-            anonymized_line_numbers,
-            false,
-        ));
-    }
-
-    sets
-}
-
-fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> {
-    DisplayLine::Raw(DisplayRawLine::Annotation {
-        annotation: Annotation {
-            annotation_type: DisplayAnnotationType::from(level),
-            id,
-            label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
-        },
-        source_aligned: false,
-        continuation: false,
-    })
-}
-
-fn format_footer<'a>(
-    level: crate::Level,
-    id: Option<&'a str>,
-    label: &'a str,
-) -> Vec<DisplayLine<'a>> {
-    let mut result = vec![];
-    for (i, line) in label.lines().enumerate() {
-        result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
-            annotation: Annotation {
-                annotation_type: DisplayAnnotationType::from(level),
-                id,
-                label: format_label(Some(line), None),
-            },
-            source_aligned: true,
-            continuation: i != 0,
-        }));
-    }
-    result
-}
-
-fn format_label(
-    label: Option<&str>,
-    style: Option<DisplayTextStyle>,
-) -> Vec<DisplayTextFragment<'_>> {
-    let mut result = vec![];
-    if let Some(label) = label {
-        let element_style = style.unwrap_or(DisplayTextStyle::Regular);
-        result.push(DisplayTextFragment {
-            content: label,
-            style: element_style,
-        });
-    }
-    result
-}
-
-fn format_snippet(
-    snippet: snippet::Snippet<'_>,
-    is_first: bool,
-    needs_trailing_pipe: bool,
-    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 mut body = format_body(
-        snippet,
-        need_empty_header,
-        needs_trailing_pipe,
-        term_width,
-        anonymized_line_numbers,
-    );
-    let header = format_header(origin, main_range, &body.display_lines, is_first);
-
-    if let Some(header) = header {
-        body.display_lines.insert(0, header);
-    }
-
-    body
-}
-
-#[inline]
-// TODO: option_zip
-fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> {
-    a.and_then(|a| b.map(|b| (a, b)))
-}
-
-fn format_header<'a>(
-    origin: Option<&'a str>,
-    main_range: Option<usize>,
-    body: &[DisplayLine<'_>],
-    is_first: bool,
-) -> Option<DisplayLine<'a>> {
-    let display_header = if is_first {
-        DisplayHeaderType::Initial
-    } else {
-        DisplayHeaderType::Continuation
-    };
-
-    if let Some((main_range, path)) = zip_opt(main_range, origin) {
-        let mut col = 1;
-        let mut line_offset = 1;
-
-        for item in body {
-            if let DisplayLine::Source {
-                line:
-                    DisplaySourceLine::Content {
-                        text,
-                        range,
-                        end_line,
-                    },
-                lineno,
-                ..
-            } = item
-            {
-                if main_range >= range.0 && main_range < range.1 + max(*end_line as usize, 1) {
-                    let char_column = text[0..(main_range - range.0).min(text.len())]
-                        .chars()
-                        .count();
-                    col = char_column + 1;
-                    line_offset = lineno.unwrap_or(1);
-                    break;
-                }
-            }
-        }
-
-        return Some(DisplayLine::Raw(DisplayRawLine::Origin {
-            path,
-            pos: Some((line_offset, col)),
-            header_type: display_header,
-        }));
-    }
-
-    if let Some(path) = origin {
-        return Some(DisplayLine::Raw(DisplayRawLine::Origin {
-            path,
-            pos: None,
-            header_type: display_header,
-        }));
-    }
-
-    None
-}
-
-fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_> {
-    if !snippet.fold {
-        return snippet;
-    }
-
-    let ann_start = snippet
-        .annotations
-        .iter()
-        .map(|ann| ann.range.start)
-        .min()
-        .unwrap_or(0);
-    if let Some(before_new_start) = snippet.source[0..ann_start].rfind('\n') {
-        let new_start = before_new_start + 1;
-
-        let line_offset = newline_count(&snippet.source[..new_start]);
-        snippet.line_start += line_offset;
-
-        snippet.source = &snippet.source[new_start..];
-
-        for ann in &mut snippet.annotations {
-            let range_start = ann.range.start - new_start;
-            let range_end = ann.range.end - new_start;
-            ann.range = range_start..range_end;
-        }
-    }
-
-    let ann_end = snippet
-        .annotations
-        .iter()
-        .map(|ann| ann.range.end)
-        .max()
-        .unwrap_or(snippet.source.len());
-    if let Some(end_offset) = snippet.source[ann_end..].find('\n') {
-        let new_end = ann_end + end_offset;
-        snippet.source = &snippet.source[..new_end];
-    }
-
-    snippet
-}
-
-fn newline_count(body: &str) -> usize {
-    #[cfg(feature = "simd")]
-    {
-        memchr::memchr_iter(b'\n', body.as_bytes()).count()
-    }
-    #[cfg(not(feature = "simd"))]
-    {
-        body.lines().count()
-    }
-}
-
-fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
-    const INNER_CONTEXT: usize = 1;
-    const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1;
-
-    let mut lines = vec![];
-    let mut unhighlighted_lines = vec![];
-    for line in body {
-        match &line {
-            DisplayLine::Source { annotations, .. } => {
-                if annotations.is_empty() {
-                    unhighlighted_lines.push(line);
-                } else {
-                    if lines.is_empty() {
-                        // Ignore leading unhighlighted lines
-                        unhighlighted_lines.clear();
-                    }
-                    match unhighlighted_lines.len() {
-                        0 => {}
-                        n if n <= INNER_UNFOLD_SIZE => {
-                            // Rather than render `...`, don't fold
-                            lines.append(&mut unhighlighted_lines);
-                        }
-                        _ => {
-                            lines.extend(unhighlighted_lines.drain(..INNER_CONTEXT));
-                            let inline_marks = lines
-                                .last()
-                                .and_then(|line| {
-                                    if let DisplayLine::Source {
-                                        ref inline_marks, ..
-                                    } = line
-                                    {
-                                        let inline_marks = inline_marks.clone();
-                                        Some(inline_marks)
-                                    } else {
-                                        None
-                                    }
-                                })
-                                .unwrap_or_default();
-                            lines.push(DisplayLine::Fold {
-                                inline_marks: inline_marks.clone(),
-                            });
-                            unhighlighted_lines
-                                .drain(..unhighlighted_lines.len().saturating_sub(INNER_CONTEXT));
-                            lines.append(&mut unhighlighted_lines);
-                        }
-                    }
-                    lines.push(line);
-                }
-            }
-            _ => {
-                unhighlighted_lines.push(line);
-            }
-        }
-    }
-
-    lines
-}
-
-fn format_body(
-    snippet: snippet::Snippet<'_>,
-    need_empty_header: bool,
-    needs_trailing_pipe: bool,
-    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.
-        if source_len + 1 < x.range.end {
-            Some(&x.range)
-        } else {
-            None
-        }
-    }) {
-        panic!("SourceAnnotation range `{bigger:?}` is beyond the end of buffer `{source_len}`")
-    }
-
-    let mut body = vec![];
-    let mut current_line = snippet.line_start;
-    let mut current_index = 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 depth_map: HashMap<usize, usize> = HashMap::new();
-    let mut current_depth = 0;
-    let mut annotations = snippet.annotations;
-    let ranges = annotations
-        .iter()
-        .map(|a| a.range.clone())
-        .collect::<Vec<_>>();
-    // We want to merge multiline annotations that have the same range into one
-    // multiline annotation to save space. This is done by making any duplicate
-    // multiline annotations into a single-line annotation pointing at the end
-    // of the range.
-    //
-    // 3 |       X0 Y0 Z0
-    //   |  _____^
-    //   | | ____|
-    //   | || ___|
-    //   | |||
-    // 4 | |||   X1 Y1 Z1
-    // 5 | |||   X2 Y2 Z2
-    //   | |||    ^
-    //   | |||____|
-    //   |  ||____`X` is a good letter
-    //   |   |____`Y` is a good letter too
-    //   |        `Z` label
-    // Should be
-    // error: foo
-    //  --> test.rs:3:3
-    //   |
-    // 3 | /   X0 Y0 Z0
-    // 4 | |   X1 Y1 Z1
-    // 5 | |   X2 Y2 Z2
-    //   | |    ^
-    //   | |____|
-    //   |      `X` is a good letter
-    //   |      `Y` is a good letter too
-    //   |      `Z` label
-    //   |
-    ranges.iter().enumerate().for_each(|(r_idx, range)| {
-        annotations
-            .iter_mut()
-            .enumerate()
-            .skip(r_idx + 1)
-            .for_each(|(ann_idx, ann)| {
-                // Skip if the annotation's index matches the range index
-                if ann_idx != r_idx
-                    // We only want to merge multiline annotations
-                    && snippet.source[ann.range.clone()].lines().count() > 1
-                    // We only want to merge annotations that have the same range
-                    && ann.range.start == range.start
-                    && ann.range.end == range.end
-                {
-                    ann.range.start = ann.range.end.saturating_sub(1);
-                }
-            });
-    });
-    annotations.sort_by_key(|a| a.range.start);
-    let mut annotations = annotations.into_iter().enumerate().collect::<Vec<_>>();
-
-    for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() {
-        let line_length: usize = line.len();
-        let line_range = (current_index, current_index + line_length);
-        let end_line_size = end_line.len();
-        body.push(DisplayLine::Source {
-            lineno: Some(current_line),
-            inline_marks: vec![],
-            line: DisplaySourceLine::Content {
-                text: line,
-                range: line_range,
-                end_line,
-            },
-            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_size;
-
-        // It would be nice to use filter_drain here once it's stable.
-        annotations.retain(|(key, annotation)| {
-            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 {
-                // This handles if the annotation is on the next line. We add
-                // the `end_line_size` to account for annotating the line end.
-                Range { start, .. } if start > line_end_index + end_line_size => true,
-                // This handles the case where an annotation is contained
-                // within the current line including any line-end characters.
-                Range { start, end }
-                    if start >= line_start_index
-                        // We add at least one to `line_end_index` to allow
-                        // highlighting the end of a file
-                        && end <= line_end_index + max(end_line_size, 1) =>
-                {
-                    if let DisplayLine::Source {
-                        ref mut annotations,
-                        ..
-                    } = body[body_idx]
-                    {
-                        let annotation_start_col = line
-                            [0..(start - line_start_index).min(line_length)]
-                            .chars()
-                            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                            .sum::<usize>();
-                        let mut annotation_end_col = line
-                            [0..(end - line_start_index).min(line_length)]
-                            .chars()
-                            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                            .sum::<usize>();
-                        if annotation_start_col == annotation_end_col {
-                            // At least highlight something
-                            annotation_end_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: format_label(annotation.label, None),
-                            },
-                            range,
-                            annotation_type: DisplayAnnotationType::from(annotation.level),
-                            annotation_part: DisplayAnnotationPart::Standalone,
-                        });
-                    }
-                    false
-                }
-                // This handles the case where a multiline annotation starts
-                // somewhere on the current line, including any line-end chars
-                Range { start, end }
-                    if start >= line_start_index
-                        // The annotation can start on a line ending
-                        && start <= line_end_index + end_line_size.saturating_sub(1)
-                        && end > line_end_index =>
-                {
-                    if let DisplayLine::Source {
-                        ref mut annotations,
-                        ..
-                    } = body[body_idx]
-                    {
-                        let annotation_start_col = line
-                            [0..(start - line_start_index).min(line_length)]
-                            .chars()
-                            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                            .sum::<usize>();
-                        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![],
-                            },
-                            range,
-                            annotation_type: DisplayAnnotationType::from(annotation.level),
-                            annotation_part: DisplayAnnotationPart::MultilineStart(current_depth),
-                        });
-                        depth_map.insert(*key, current_depth);
-                        current_depth += 1;
-                    }
-                    true
-                }
-                // This handles the case where a multiline annotation starts
-                // somewhere before this line and ends after it as well
-                Range { start, end }
-                    if start < line_start_index && end > line_end_index + max(end_line_size, 1) =>
-                {
-                    if let DisplayLine::Source {
-                        ref mut inline_marks,
-                        ..
-                    } = body[body_idx]
-                    {
-                        let depth = depth_map.get(key).cloned().unwrap_or_default();
-                        inline_marks.push(DisplayMark {
-                            mark_type: DisplayMarkType::AnnotationThrough(depth),
-                            annotation_type: DisplayAnnotationType::from(annotation.level),
-                        });
-                    }
-                    true
-                }
-                // This handles the case where a multiline annotation ends
-                // somewhere on the current line, including any line-end chars
-                Range { start, end }
-                    if start < line_start_index
-                        && end >= line_start_index
-                        // We add at least one to `line_end_index` to allow
-                        // highlighting the end of a file
-                        && end <= line_end_index + max(end_line_size, 1) =>
-                {
-                    if let DisplayLine::Source {
-                        ref mut annotations,
-                        ..
-                    } = body[body_idx]
-                    {
-                        let end_mark = line[0..(end - line_start_index).min(line_length)]
-                            .chars()
-                            .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                            .sum::<usize>()
-                            .saturating_sub(1);
-                        // If the annotation ends on a line-end character, we
-                        // need to annotate one past the end of the line
-                        let (end_mark, end_plus_one) = if end > line_end_index
-                            // Special case for highlighting the end of a file
-                            || (end == line_end_index + 1 && end_line_size == 0)
-                        {
-                            (end_mark + 1, end_mark + 2)
-                        } else {
-                            (end_mark, 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);
-                        let depth = depth_map.remove(key).unwrap_or(0);
-                        annotations.push(DisplaySourceAnnotation {
-                            annotation: Annotation {
-                                annotation_type,
-                                id: None,
-                                label: format_label(annotation.label, None),
-                            },
-                            range,
-                            annotation_type: DisplayAnnotationType::from(annotation.level),
-                            annotation_part: DisplayAnnotationPart::MultilineEnd(depth),
-                        });
-                    }
-                    false
-                }
-                _ => true,
-            }
-        });
-        // Reset the depth counter, but only after we've processed all
-        // annotations for a given line.
-        let max = depth_map.len();
-        if current_depth > max {
-            current_depth = max;
-        }
-    }
-
-    if snippet.fold {
-        body = fold_body(body);
-    }
-
-    if need_empty_header {
-        body.insert(
-            0,
-            DisplayLine::Source {
-                lineno: None,
-                inline_marks: vec![],
-                line: DisplaySourceLine::Empty,
-                annotations: vec![],
-            },
-        );
-    }
-
-    if needs_trailing_pipe {
-        body.push(DisplayLine::Source {
-            lineno: None,
-            inline_marks: vec![],
-            line: DisplaySourceLine::Empty,
-            annotations: vec![],
-        });
-    }
-
-    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,
-    }
-}
-
-#[inline]
-fn annotation_type_str(annotation_type: &DisplayAnnotationType) -> &'static str {
-    match annotation_type {
-        DisplayAnnotationType::Error => ERROR_TXT,
-        DisplayAnnotationType::Help => HELP_TXT,
-        DisplayAnnotationType::Info => INFO_TXT,
-        DisplayAnnotationType::Note => NOTE_TXT,
-        DisplayAnnotationType::Warning => WARNING_TXT,
-        DisplayAnnotationType::None => "",
-    }
-}
-
-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
-        .label
-        .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_owned();
-    for (c, replacement) in OUTPUT_REPLACEMENTS {
-        s = s.replace(*c, replacement);
-    }
-    s
-}
-
-fn overlaps(
-    a1: &DisplaySourceAnnotation<'_>,
-    a2: &DisplaySourceAnnotation<'_>,
-    padding: usize,
-) -> bool {
-    (a2.range.0..a2.range.1).contains(&a1.range.0)
-        || (a1.range.0..a1.range.1 + padding).contains(&a2.range.0)
-}
-
-fn format_inline_marks(
-    line: usize,
-    inline_marks: &[DisplayMark],
-    lineno_width: usize,
-    stylesheet: &Stylesheet,
-    buf: &mut StyledBuffer,
-) -> fmt::Result {
-    for mark in inline_marks.iter() {
-        let annotation_style = get_annotation_style(&mark.annotation_type, stylesheet);
-        match mark.mark_type {
-            DisplayMarkType::AnnotationThrough(depth) => {
-                buf.putc(line, 3 + lineno_width + depth, '|', *annotation_style);
-            }
-        };
-    }
-    Ok(())
-}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index b9edcc6..5fa9c8e 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1,3 +1,5 @@
+// Most of this file is adapted from https://github.com/rust-lang/rust/blob/160905b6253f42967ed4aef4b98002944c7df24c/compiler/rustc_errors/src/emitter.rs
+
 //! The renderer for [`Message`]s
 //!
 //! # Example
@@ -10,20 +12,29 @@
 //!  let renderer = Renderer::styled();
 //!  println!("{}", renderer.render(snippet));
 
-mod display_list;
 mod margin;
+mod source_map;
 mod styled_buffer;
 pub(crate) mod stylesheet;
 
+use crate::renderer::source_map::{AnnotatedLineInfo, Loc, SourceMap};
+use crate::renderer::styled_buffer::StyledBuffer;
 use crate::snippet::Message;
+use crate::{Level, Snippet};
 pub use anstyle::*;
-use display_list::DisplayList;
+use indexmap::IndexMap;
 use margin::Margin;
-use std::fmt::Display;
+use rustc_hash::{FxHashMap, FxHasher};
+use std::borrow::Cow;
+use std::cmp::{max, min, Ordering, Reverse};
+use std::hash::BuildHasherDefault;
 use stylesheet::Stylesheet;
 
+const ANONYMIZED_LINE_NUM: &str = "LL";
 pub const DEFAULT_TERM_WIDTH: usize = 140;
 
+type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
+
 /// A renderer for [`Message`]s
 #[derive(Clone, Debug)]
 pub struct Renderer {
@@ -150,14 +161,1411 @@ impl Renderer {
         self.stylesheet.none = style;
         self
     }
+}
+
+impl Renderer {
+    pub fn render(&self, message: Message<'_>) -> String {
+        let mut buffer = StyledBuffer::new();
+        let max_line_num_len = if self.anonymized_line_numbers {
+            ANONYMIZED_LINE_NUM.len()
+        } else {
+            let n = message.max_line_number();
+            num_decimal_digits(n)
+        };
+
+        self.render_message(&mut buffer, message, max_line_num_len, false);
+
+        buffer.render(&self.stylesheet).unwrap()
+    }
+
+    fn render_message(
+        &self,
+        buffer: &mut StyledBuffer,
+        message: Message<'_>,
+        max_line_num_len: usize,
+        is_secondary: bool,
+    ) {
+        self.render_title(buffer, &message, max_line_num_len, is_secondary);
+
+        let primary_origin = message.snippets.first().and_then(|s| s.origin);
+
+        for snippet in message.snippets {
+            let source_map = SourceMap::new(snippet.source, snippet.line_start);
+            self.render_snippet_annotations(
+                buffer,
+                max_line_num_len,
+                &snippet,
+                primary_origin,
+                &source_map,
+            );
+        }
+
+        for footer in message.footer {
+            self.render_message(buffer, footer, max_line_num_len, true);
+        }
+    }
+
+    fn render_title(
+        &self,
+        buffer: &mut StyledBuffer,
+        message: &Message<'_>,
+        max_line_num_len: usize,
+        is_secondary: bool,
+    ) {
+        let line_offset = buffer.num_lines();
+        if !message.has_primary_spans() && !message.has_span_labels() && is_secondary {
+            // This is a secondary message with no span info
+            for _ in 0..max_line_num_len {
+                buffer.prepend(line_offset, " ", ElementStyle::NoStyle);
+            }
+            buffer.puts(
+                line_offset,
+                max_line_num_len + 1,
+                "= ",
+                ElementStyle::LineNumber,
+            );
+            buffer.append(
+                line_offset,
+                message.level.as_str(),
+                ElementStyle::MainHeaderMsg,
+            );
+            buffer.append(line_offset, ": ", ElementStyle::NoStyle);
+            self.msgs_to_buffer(buffer, message.title, max_line_num_len, "note", None);
+        } else {
+            let mut label_width = 0;
+
+            buffer.append(
+                line_offset,
+                message.level.as_str(),
+                ElementStyle::Level(message.level),
+            );
+            label_width += message.level.as_str().len();
+            if let Some(id) = message.id {
+                buffer.append(line_offset, "[", ElementStyle::Level(message.level));
+                buffer.append(line_offset, id, ElementStyle::Level(message.level));
+                buffer.append(line_offset, "]", ElementStyle::Level(message.level));
+                label_width += 2 + id.len();
+            }
+            let header_style = if is_secondary {
+                ElementStyle::HeaderMsg
+            } else {
+                ElementStyle::MainHeaderMsg
+            };
+            buffer.append(line_offset, ": ", header_style);
+            label_width += 2;
+            if !message.title.is_empty() {
+                for (line, text) in normalize_whitespace(message.title).lines().enumerate() {
+                    buffer.append(
+                        line_offset + line,
+                        &format!(
+                            "{}{}",
+                            if line == 0 {
+                                String::new()
+                            } else {
+                                " ".repeat(label_width)
+                            },
+                            text
+                        ),
+                        header_style,
+                    );
+                }
+            }
+        }
+    }
+
+    /// Adds a left margin to every line but the first, given a padding length and the label being
+    /// displayed, keeping the provided highlighting.
+    fn msgs_to_buffer(
+        &self,
+        buffer: &mut StyledBuffer,
+        title: &str,
+        padding: usize,
+        label: &str,
+        override_style: Option<ElementStyle>,
+    ) -> usize {
+        // The extra 5 ` ` is padding that's always needed to align to the `note: `:
+        //
+        //   error: message
+        //     --> file.rs:13:20
+        //      |
+        //   13 |     <CODE>
+        //      |      ^^^^
+        //      |
+        //      = note: multiline
+        //              message
+        //   ++^^^----xx
+        //    |  |   | |
+        //    |  |   | magic `2`
+        //    |  |   length of label
+        //    |  magic `3`
+        //    `max_line_num_len`
+        let padding = " ".repeat(padding + label.len() + 5);
+
+        let mut line_number = buffer.num_lines().saturating_sub(1);
+
+        // Provided the following diagnostic message:
+        //
+        //     let msgs = vec![
+        //       ("
+        //       ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle),
+        //       ("looks", Style::Highlight),
+        //       ("with\nvery ", Style::NoStyle),
+        //       ("weird", Style::Highlight),
+        //       (" formats\n", Style::NoStyle),
+        //       ("see?", Style::Highlight),
+        //     ];
+        //
+        // the expected output on a note is (* surround the highlighted text)
+        //
+        //        = note: highlighted multiline
+        //                string to
+        //                see how it *looks* with
+        //                very *weird* formats
+        //                see?
+        let style = if let Some(override_style) = override_style {
+            override_style
+        } else {
+            ElementStyle::NoStyle
+        };
+        let text = &normalize_whitespace(title);
+        let lines = text.split('\n').collect::<Vec<_>>();
+        if lines.len() > 1 {
+            for (i, line) in lines.iter().enumerate() {
+                if i != 0 {
+                    line_number += 1;
+                    buffer.append(line_number, &padding, ElementStyle::NoStyle);
+                }
+                buffer.append(line_number, line, style);
+            }
+        } else {
+            buffer.append(line_number, text, style);
+        }
+        line_number
+    }
+
+    fn render_snippet_annotations(
+        &self,
+        buffer: &mut StyledBuffer,
+        max_line_num_len: usize,
+        snippet: &Snippet<'_>,
+        primary_origin: Option<&str>,
+        sm: &SourceMap<'_>,
+    ) {
+        let annotated_lines = sm.annotated_lines(snippet.annotations.clone(), snippet.fold);
+        // print out the span location and spacer before we print the annotated source
+        // to do this, we need to know if this span will be primary
+        let is_primary = primary_origin == snippet.origin;
+
+        if is_primary {
+            if let Some(origin) = snippet.origin {
+                // remember where we are in the output buffer for easy reference
+                let buffer_msg_line_offset = buffer.num_lines();
+
+                buffer.prepend(
+                    buffer_msg_line_offset,
+                    self.file_start(),
+                    ElementStyle::LineNumber,
+                );
+                let loc = if let Some(first_line) =
+                    annotated_lines.iter().find(|l| !l.annotations.is_empty())
+                {
+                    let col = if let Some(first_annotation) = first_line.annotations.first() {
+                        format!(":{}", first_annotation.start.char + 1)
+                    } else {
+                        String::new()
+                    };
+                    format!("{}:{}{}", origin, first_line.line_index, col)
+                } else {
+                    origin.to_owned()
+                };
+                buffer.append(buffer_msg_line_offset, &loc, ElementStyle::LineAndColumn);
+                for _ in 0..max_line_num_len {
+                    buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle);
+                }
+            }
+        } else {
+            if let Some(origin) = snippet.origin {
+                // remember where we are in the output buffer for easy reference
+                let buffer_msg_line_offset = buffer.num_lines();
+
+                // Add spacing line, as shown:
+                //   --> $DIR/file:54:15
+                //    |
+                // LL |         code
+                //    |         ^^^^
+                //    | (<- It prints *this* line)
+                //   ::: $DIR/other_file.rs:15:5
+                //    |
+                // LL |     code
+                //    |     ----
+                self.draw_col_separator_no_space(
+                    buffer,
+                    buffer_msg_line_offset,
+                    max_line_num_len + 1,
+                );
+
+                // Then, the secondary file indicator
+                buffer.prepend(
+                    buffer_msg_line_offset + 1,
+                    self.secondary_file_start(),
+                    ElementStyle::LineNumber,
+                );
+                let loc = if let Some(first_line) =
+                    annotated_lines.iter().find(|l| !l.annotations.is_empty())
+                {
+                    let col = if let Some(first_annotation) = first_line.annotations.first() {
+                        format!(":{}", first_annotation.start.char + 1)
+                    } else {
+                        String::new()
+                    };
+                    format!("{}:{}{}", origin, first_line.line_index, col)
+                } else {
+                    origin.to_owned()
+                };
+                buffer.append(
+                    buffer_msg_line_offset + 1,
+                    &loc,
+                    ElementStyle::LineAndColumn,
+                );
+                for _ in 0..max_line_num_len {
+                    buffer.prepend(buffer_msg_line_offset + 1, " ", ElementStyle::NoStyle);
+                }
+            }
+        }
+
+        // Put in the spacer between the location and annotated source
+        let buffer_msg_line_offset = buffer.num_lines();
+        self.draw_col_separator_no_space(buffer, buffer_msg_line_offset, max_line_num_len + 1);
+
+        // Contains the vertical lines' positions for active multiline annotations
+        let mut multilines = FxIndexMap::default();
+
+        // Get the left-side margin to remove it
+        let mut whitespace_margin = usize::MAX;
+        for line_info in &annotated_lines {
+            // Whitespace can only be removed (aka considered leading)
+            // if the lexer considers it whitespace.
+            // non-rustc_lexer::is_whitespace() chars are reported as an
+            // error (ex. no-break-spaces \u{a0}), and thus can't be considered
+            // for removal during error reporting.
+            let leading_whitespace = line_info
+                .line
+                .chars()
+                .take_while(|c| c.is_whitespace())
+                .map(|c| {
+                    match c {
+                        // Tabs are displayed as 4 spaces
+                        '\t' => 4,
+                        _ => 1,
+                    }
+                })
+                .sum();
+            if line_info.line.chars().any(|c| !c.is_whitespace()) {
+                whitespace_margin = min(whitespace_margin, leading_whitespace);
+            }
+        }
+        if whitespace_margin == usize::MAX {
+            whitespace_margin = 0;
+        }
+
+        // Left-most column any visible span points at.
+        let mut span_left_margin = usize::MAX;
+        for line_info in &annotated_lines {
+            for ann in &line_info.annotations {
+                span_left_margin = min(span_left_margin, ann.start.display);
+                span_left_margin = min(span_left_margin, ann.end.display);
+            }
+        }
+        if span_left_margin == usize::MAX {
+            span_left_margin = 0;
+        }
+
+        // Right-most column any visible span points at.
+        let mut span_right_margin = 0;
+        let mut label_right_margin = 0;
+        let mut max_line_len = 0;
+        for line_info in &annotated_lines {
+            max_line_len = max(max_line_len, line_info.line.len());
+            for ann in &line_info.annotations {
+                span_right_margin = max(span_right_margin, ann.start.display);
+                span_right_margin = max(span_right_margin, ann.end.display);
+                // FIXME: account for labels not in the same line
+                let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
+                label_right_margin = max(label_right_margin, ann.end.display + label_right);
+            }
+        }
+        let multiline_depth = annotated_lines.iter().fold(0, |acc, line_info| {
+            line_info.annotations.iter().fold(acc, |acc2, ann| {
+                max(
+                    acc2,
+                    match ann.annotation_type {
+                        LineAnnotationType::Singleline => 0,
+                        LineAnnotationType::MultilineStart(depth) => depth,
+                        LineAnnotationType::MultilineEnd(depth) => depth,
+                        LineAnnotationType::MultilineLine(depth) => depth,
+                    },
+                )
+            })
+        });
+        let width_offset = 3 + max_line_num_len;
+        let code_offset = if multiline_depth == 0 {
+            width_offset
+        } else {
+            width_offset + multiline_depth + 1
+        };
+
+        let column_width = self.term_width.saturating_sub(code_offset);
+
+        let margin = Margin::new(
+            whitespace_margin,
+            span_left_margin,
+            span_right_margin,
+            label_right_margin,
+            column_width,
+            max_line_len,
+        );
+
+        // Next, output the annotate source for this file
+        for annotated_line_idx in 0..annotated_lines.len() {
+            let previous_buffer_line = buffer.num_lines();
+
+            let depths = self.render_source_line(
+                &annotated_lines[annotated_line_idx],
+                buffer,
+                width_offset,
+                code_offset,
+                margin,
+            );
+
+            let mut to_add = FxHashMap::default();
+
+            for (depth, style) in depths {
+                // FIXME(#120456) - is `swap_remove` correct?
+                if multilines.swap_remove(&depth).is_none() {
+                    to_add.insert(depth, style);
+                }
+            }
+
+            // Set the multiline annotation vertical lines to the left of
+            // the code in this line.
+            for (depth, style) in &multilines {
+                for line in previous_buffer_line..buffer.num_lines() {
+                    self.draw_multiline_line(buffer, line, width_offset, *depth, *style);
+                }
+            }
+            // check to see if we need to print out or elide lines that come between
+            // this annotated line and the next one.
+            if annotated_line_idx < (annotated_lines.len() - 1) {
+                let line_idx_delta = annotated_lines[annotated_line_idx + 1].line_index
+                    - annotated_lines[annotated_line_idx].line_index;
+                match line_idx_delta.cmp(&2) {
+                    Ordering::Greater => {
+                        let last_buffer_line_num = buffer.num_lines();
+                        buffer.puts(last_buffer_line_num, 0, "...", ElementStyle::LineNumber);
+
+                        // Set the multiline annotation vertical lines on `...` bridging line.
+                        for (depth, style) in &multilines {
+                            self.draw_multiline_line(
+                                buffer,
+                                last_buffer_line_num,
+                                width_offset,
+                                *depth,
+                                *style,
+                            );
+                        }
+                        if let Some(line) = annotated_lines.get(annotated_line_idx) {
+                            for ann in &line.annotations {
+                                if let LineAnnotationType::MultilineStart(pos) = ann.annotation_type
+                                {
+                                    // In the case where we have elided the entire start of the
+                                    // multispan because those lines were empty, we still need
+                                    // to draw the `|`s across the `...`.
+                                    self.draw_multiline_line(
+                                        buffer,
+                                        last_buffer_line_num,
+                                        width_offset,
+                                        pos,
+                                        ElementStyle::Level(ann.level),
+                                    );
+                                }
+                            }
+                        }
+                    }
+
+                    Ordering::Equal => {
+                        let unannotated_line = sm
+                            .get_line(annotated_lines[annotated_line_idx].line_index + 1)
+                            .unwrap_or("");
+
+                        let last_buffer_line_num = buffer.num_lines();
+
+                        self.draw_line(
+                            buffer,
+                            &normalize_whitespace(unannotated_line),
+                            annotated_lines[annotated_line_idx + 1].line_index - 1,
+                            last_buffer_line_num,
+                            width_offset,
+                            code_offset,
+                            margin,
+                        );
+
+                        for (depth, style) in &multilines {
+                            self.draw_multiline_line(
+                                buffer,
+                                last_buffer_line_num,
+                                width_offset,
+                                *depth,
+                                *style,
+                            );
+                        }
+                        if let Some(line) = annotated_lines.get(annotated_line_idx) {
+                            for ann in &line.annotations {
+                                if let LineAnnotationType::MultilineStart(pos) = ann.annotation_type
+                                {
+                                    self.draw_multiline_line(
+                                        buffer,
+                                        last_buffer_line_num,
+                                        width_offset,
+                                        pos,
+                                        ElementStyle::Level(ann.level),
+                                    );
+                                }
+                            }
+                        }
+                    }
+                    Ordering::Less => {}
+                }
+            }
+
+            multilines.extend(&to_add);
+        }
+    }
+
+    fn render_source_line(
+        &self,
+        line_info: &AnnotatedLineInfo<'_>,
+        buffer: &mut StyledBuffer,
+        width_offset: usize,
+        code_offset: usize,
+        margin: Margin,
+    ) -> Vec<(usize, ElementStyle)> {
+        // Draw:
+        //
+        //   LL | ... code ...
+        //      |     ^^-^ span label
+        //      |       |
+        //      |       secondary span label
+        //
+        //   ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it
+        //   |  | |   |
+        //   |  | |   actual code found in your source code and the spans we use to mark it
+        //   |  | when there's too much wasted space to the left, trim it
+        //   |  vertical divider between the column number and the code
+        //   column number
+
+        if line_info.line_index == 0 {
+            return Vec::new();
+        }
+
+        let source_string = normalize_whitespace(line_info.line);
+
+        let line_offset = buffer.num_lines();
+
+        // Left trim
+        let left = margin.left(source_string.len());
+
+        // FIXME: This looks fishy. See #132860.
+        // Account for unicode characters of width !=0 that were removed.
+        let left = source_string.chars().take(left).map(char_width).sum();
+
+        self.draw_line(
+            buffer,
+            &source_string,
+            line_info.line_index,
+            line_offset,
+            width_offset,
+            code_offset,
+            margin,
+        );
+
+        // Special case when there's only one annotation involved, it is the start of a multiline
+        // span and there's no text at the beginning of the code line. Instead of doing the whole
+        // graph:
+        //
+        // 2 |   fn foo() {
+        //   |  _^
+        // 3 | |
+        // 4 | | }
+        //   | |_^ test
+        //
+        // we simplify the output to:
+        //
+        // 2 | / fn foo() {
+        // 3 | |
+        // 4 | | }
+        //   | |_^ test
+        let mut buffer_ops = vec![];
+        let mut annotations = vec![];
+        let mut short_start = true;
+        for ann in &line_info.annotations {
+            if let LineAnnotationType::MultilineStart(depth) = ann.annotation_type {
+                if source_string
+                    .chars()
+                    .take(ann.start.display)
+                    .all(char::is_whitespace)
+                {
+                    let style = ElementStyle::Level(ann.level);
+                    annotations.push((depth, style));
+                    buffer_ops.push((line_offset, width_offset + depth - 1, '/', style));
+                } else {
+                    short_start = false;
+                    break;
+                }
+            } else if let LineAnnotationType::MultilineLine(_) = ann.annotation_type {
+            } else {
+                short_start = false;
+                break;
+            }
+        }
+        if short_start {
+            for (y, x, c, s) in buffer_ops {
+                buffer.putc(y, x, c, s);
+            }
+            return annotations;
+        }
+
+        // We want to display like this:
+        //
+        //      vec.push(vec.pop().unwrap());
+        //      ---      ^^^               - previous borrow ends here
+        //      |        |
+        //      |        error occurs here
+        //      previous borrow of `vec` occurs here
+        //
+        // But there are some weird edge cases to be aware of:
+        //
+        //      vec.push(vec.pop().unwrap());
+        //      --------                    - previous borrow ends here
+        //      ||
+        //      |this makes no sense
+        //      previous borrow of `vec` occurs here
+        //
+        // For this reason, we group the lines into "highlight lines"
+        // and "annotations lines", where the highlight lines have the `^`.
+
+        // Sort the annotations by (start, end col)
+        // The labels are reversed, sort and then reversed again.
+        // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where
+        // the letter signifies the span. Here we are only sorting by the
+        // span and hence, the order of the elements with the same span will
+        // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get
+        // (C1, C2, B1, B2, A1, A2). All the elements with the same span are
+        // still ordered first to last, but all the elements with different
+        // spans are ordered by their spans in last to first order. Last to
+        // first order is important, because the jiggly lines and | are on
+        // the left, so the rightmost span needs to be rendered first,
+        // otherwise the lines would end up needing to go over a message.
+
+        let mut annotations = line_info.annotations.clone();
+        annotations.sort_by_key(|a| Reverse(a.start.display));
+
+        // First, figure out where each label will be positioned.
+        //
+        // In the case where you have the following annotations:
+        //
+        //      vec.push(vec.pop().unwrap());
+        //      --------                    - previous borrow ends here [C]
+        //      ||
+        //      |this makes no sense [B]
+        //      previous borrow of `vec` occurs here [A]
+        //
+        // `annotations_position` will hold [(2, A), (1, B), (0, C)].
+        //
+        // We try, when possible, to stick the rightmost annotation at the end
+        // of the highlight line:
+        //
+        //      vec.push(vec.pop().unwrap());
+        //      ---      ---               - previous borrow ends here
+        //
+        // But sometimes that's not possible because one of the other
+        // annotations overlaps it. For example, from the test
+        // `span_overlap_label`, we have the following annotations
+        // (written on distinct lines for clarity):
+        //
+        //      fn foo(x: u32) {
+        //      --------------
+        //             -
+        //
+        // In this case, we can't stick the rightmost-most label on
+        // the highlight line, or we would get:
+        //
+        //      fn foo(x: u32) {
+        //      -------- x_span
+        //      |
+        //      fn_span
+        //
+        // which is totally weird. Instead we want:
+        //
+        //      fn foo(x: u32) {
+        //      --------------
+        //      |      |
+        //      |      x_span
+        //      fn_span
+        //
+        // which is...less weird, at least. In fact, in general, if
+        // the rightmost span overlaps with any other span, we should
+        // use the "hang below" version, so we can at least make it
+        // clear where the span *starts*. There's an exception for this
+        // logic, when the labels do not have a message:
+        //
+        //      fn foo(x: u32) {
+        //      --------------
+        //             |
+        //             x_span
+        //
+        // instead of:
+        //
+        //      fn foo(x: u32) {
+        //      --------------
+        //      |      |
+        //      |      x_span
+        //      <EMPTY LINE>
+        //
+        let mut annotations_position = vec![];
+        let mut line_len: usize = 0;
+        let mut p = 0;
+        for (i, annotation) in annotations.iter().enumerate() {
+            for (j, next) in annotations.iter().enumerate() {
+                if overlaps(next, annotation, 0)  // This label overlaps with another one and both
+                    && annotation.has_label()     // take space (they have text and are not
+                    && j > i                      // multiline lines).
+                    && p == 0
+                // We're currently on the first line, move the label one line down
+                {
+                    // If we're overlapping with an un-labelled annotation with the same span
+                    // we can just merge them in the output
+                    if next.start.display == annotation.start.display
+                        && next.end.display == annotation.end.display
+                        && !next.has_label()
+                    {
+                        continue;
+                    }
+
+                    // This annotation needs a new line in the output.
+                    p += 1;
+                    break;
+                }
+            }
+            annotations_position.push((p, annotation));
+            for (j, next) in annotations.iter().enumerate() {
+                if j > i {
+                    let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
+                    if (overlaps(next, annotation, l) // Do not allow two labels to be in the same
+                        // line if they overlap including padding, to
+                        // avoid situations like:
+                        //
+                        //      fn foo(x: u32) {
+                        //      -------^------
+                        //      |      |
+                        //      fn_spanx_span
+                        //
+                        && annotation.has_label()    // Both labels must have some text, otherwise
+                        && next.has_label())         // they are not overlapping.
+                        // Do not add a new line if this annotation
+                        // or the next are vertical line placeholders.
+                        || (annotation.takes_space() // If either this or the next annotation is
+                        && next.has_label())     // multiline start/end, move it to a new line
+                        || (annotation.has_label()   // so as not to overlap the horizontal lines.
+                        && next.takes_space())
+                        || (annotation.takes_space() && next.takes_space())
+                        || (overlaps(next, annotation, l)
+                        && next.end.display <= annotation.end.display
+                        && next.has_label()
+                        && p == 0)
+                    // Avoid #42595.
+                    {
+                        // This annotation needs a new line in the output.
+                        p += 1;
+                        break;
+                    }
+                }
+            }
+            line_len = max(line_len, p);
+        }
+
+        if line_len != 0 {
+            line_len += 1;
+        }
+
+        // If there are no annotations or the only annotations on this line are
+        // MultilineLine, then there's only code being shown, stop processing.
+        if line_info.annotations.iter().all(LineAnnotation::is_line) {
+            return vec![];
+        }
+
+        if annotations_position
+            .iter()
+            .all(|(_, ann)| matches!(ann.annotation_type, LineAnnotationType::MultilineStart(_)))
+        {
+            if let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max() {
+                // Special case the following, so that we minimize overlapping multiline spans.
+                //
+                // 3 │       X0 Y0 Z0
+                //   │ ┏━━━━━┛  │  │     < We are writing these lines
+                //   │ ┃┌───────┘  │     < by reverting the "depth" of
+                //   │ ┃│┌─────────┘     < their multiline spans.
+                // 4 │ ┃││   X1 Y1 Z1
+                // 5 │ ┃││   X2 Y2 Z2
+                //   │ ┃│└────╿──│──┘ `Z` label
+                //   │ ┃└─────│──┤
+                //   │ ┗━━━━━━┥  `Y` is a good letter too
+                //   ╰╴       `X` is a good letter
+                for (pos, _) in &mut annotations_position {
+                    *pos = max_pos - *pos;
+                }
+                // We know then that we don't need an additional line for the span label, saving us
+                // one line of vertical space.
+                line_len = line_len.saturating_sub(1);
+            }
+        }
+
+        // Write the column separator.
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |
+        //   |
+        //   |
+        // 3 |
+        // 4 |   }
+        //   |
+        for pos in 0..=line_len {
+            self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2);
+        }
+
+        // Write the horizontal lines for multiline annotations
+        // (only the first and last lines need this).
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |  __________
+        //   |
+        //   |
+        // 3 |
+        // 4 |   }
+        //   |  _
+        for &(pos, annotation) in &annotations_position {
+            let style = ElementStyle::Level(annotation.level);
+            let pos = pos + 1;
+            match annotation.annotation_type {
+                LineAnnotationType::MultilineStart(depth)
+                | LineAnnotationType::MultilineEnd(depth) => {
+                    self.draw_range(
+                        buffer,
+                        '_', // underline.multiline_horizontal,
+                        line_offset + pos,
+                        width_offset + depth,
+                        (code_offset + annotation.start.display).saturating_sub(left),
+                        style,
+                    );
+                }
+                _ => {}
+            }
+        }
+
+        // Write the vertical lines for labels that are on a different line as the underline.
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |  __________
+        //   | |    |
+        //   | |
+        // 3 | |
+        // 4 | | }
+        //   | |_
+        for &(pos, annotation) in &annotations_position {
+            let style = ElementStyle::Level(annotation.level);
+            let pos = pos + 1;
+
+            if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
+                for p in line_offset + 1..=line_offset + pos {
+                    buffer.putc(
+                        p,
+                        (code_offset + annotation.start.display).saturating_sub(left),
+                        match annotation.annotation_type {
+                            LineAnnotationType::MultilineLine(_) => '|', // underline.multiline_vertical,
+                            _ => '|', // underline.vertical_text_line,
+                        },
+                        style,
+                    );
+                }
+                if let LineAnnotationType::MultilineStart(_) = annotation.annotation_type {
+                    buffer.putc(
+                        line_offset + pos,
+                        (code_offset + annotation.start.display).saturating_sub(left),
+                        '|', // underline.bottom_right,
+                        style,
+                    );
+                }
+                if matches!(
+                    annotation.annotation_type,
+                    LineAnnotationType::MultilineEnd(_)
+                ) && annotation.has_label()
+                {
+                    buffer.putc(
+                        line_offset + pos,
+                        (code_offset + annotation.start.display).saturating_sub(left),
+                        '|', // underline.multiline_bottom_right_with_text,
+                        style,
+                    );
+                }
+            }
+            match annotation.annotation_type {
+                LineAnnotationType::MultilineStart(depth) => {
+                    buffer.putc(
+                        line_offset + pos,
+                        width_offset + depth - 1,
+                        ' ', // underline.top_left,
+                        style,
+                    );
+                    for p in line_offset + pos + 1..line_offset + line_len + 2 {
+                        buffer.putc(
+                            p,
+                            width_offset + depth - 1,
+                            '|', // underline.multiline_vertical,
+                            style,
+                        );
+                    }
+                }
+                LineAnnotationType::MultilineEnd(depth) => {
+                    for p in line_offset..line_offset + pos {
+                        buffer.putc(
+                            p,
+                            width_offset + depth - 1,
+                            '|', // underline.multiline_vertical,
+                            style,
+                        );
+                    }
+                    buffer.putc(
+                        line_offset + pos,
+                        width_offset + depth - 1,
+                        '|', // underline.bottom_left,
+                        style,
+                    );
+                }
+                _ => (),
+            }
+        }
+
+        // Write the labels on the annotations that actually have a label.
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |  __________
+        //   |      |
+        //   |      something about `foo`
+        // 3 |
+        // 4 |   }
+        //   |  _  test
+        for &(pos, annotation) in &annotations_position {
+            let style = ElementStyle::Level(annotation.level);
+            let (pos, col) = if pos == 0 {
+                if annotation.end.display == 0 {
+                    (pos + 1, (annotation.end.display + 2).saturating_sub(left))
+                } else {
+                    (pos + 1, (annotation.end.display + 1).saturating_sub(left))
+                }
+            } else {
+                (pos + 2, annotation.start.display.saturating_sub(left))
+            };
+            if let Some(label) = annotation.label {
+                buffer.puts(line_offset + pos, code_offset + col, label, style);
+            }
+        }
+
+        // Sort from biggest span to smallest span so that smaller spans are
+        // represented in the output:
+        //
+        // x | fn foo()
+        //   | ^^^---^^
+        //   | |  |
+        //   | |  something about `foo`
+        //   | something about `fn foo()`
+        annotations_position.sort_by_key(|(_, ann)| {
+            // Decreasing order. When annotations share the same length, prefer `Primary`.
+            Reverse(ann.len())
+        });
+
+        // Write the underlines.
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |  ____-_____^
+        //   |      |
+        //   |      something about `foo`
+        // 3 |
+        // 4 |   }
+        //   |  _^  test
+        for &(pos, annotation) in &annotations_position {
+            let style = ElementStyle::Level(annotation.level);
+            let underline = if annotation.level == Level::Error {
+                '^'
+            } else {
+                '-'
+            };
+            for p in annotation.start.display..annotation.end.display {
+                // The default span label underline.
+                buffer.putc(
+                    line_offset + 1,
+                    (code_offset + p).saturating_sub(left),
+                    underline,
+                    style,
+                );
+            }
+
+            if pos == 0
+                && matches!(
+                    annotation.annotation_type,
+                    LineAnnotationType::MultilineStart(_) | LineAnnotationType::MultilineEnd(_)
+                )
+            {
+                // The beginning of a multiline span with its leftward moving line on the same line.
+                buffer.putc(
+                    line_offset + 1,
+                    (code_offset + annotation.start.display).saturating_sub(left),
+                    underline,
+                    style,
+                );
+            } else if pos != 0
+                && matches!(
+                    annotation.annotation_type,
+                    LineAnnotationType::MultilineStart(_) | LineAnnotationType::MultilineEnd(_)
+                )
+            {
+                // The beginning of a multiline span with its leftward moving line on another line,
+                // so we start going down first.
+                buffer.putc(
+                    line_offset + 1,
+                    (code_offset + annotation.start.display).saturating_sub(left),
+                    underline,
+                    style,
+                );
+            } else if pos != 0 && annotation.has_label() {
+                // The beginning of a span label with an actual label, we'll point down.
+                buffer.putc(
+                    line_offset + 1,
+                    (code_offset + annotation.start.display).saturating_sub(left),
+                    underline,
+                    style,
+                );
+            }
+        }
+        annotations_position
+            .iter()
+            .filter_map(|&(_, annotation)| match annotation.annotation_type {
+                LineAnnotationType::MultilineStart(p) | LineAnnotationType::MultilineEnd(p) => {
+                    let style = ElementStyle::Level(annotation.level);
+                    Some((p, style))
+                }
+                _ => None,
+            })
+            .collect::<Vec<_>>()
+    }
+
+    #[allow(clippy::too_many_arguments)]
+    fn draw_line(
+        &self,
+        buffer: &mut StyledBuffer,
+        source_string: &str,
+        line_index: usize,
+        line_offset: usize,
+        width_offset: usize,
+        code_offset: usize,
+        margin: Margin,
+    ) {
+        // Tabs are assumed to have been replaced by spaces in calling code.
+        debug_assert!(!source_string.contains('\t'));
+        let line_len = source_string.len();
+        // Create the source line we will highlight.
+        let left = margin.left(line_len);
+        let right = margin.right(line_len);
+        // FIXME: The following code looks fishy. See #132860.
+        // On long lines, we strip the source line, accounting for unicode.
+        let mut taken = 0;
+        let code: String = source_string
+            .chars()
+            .skip(left)
+            .take_while(|ch| {
+                // Make sure that the trimming on the right will fall within the terminal width.
+                let next = char_width(*ch);
+                if taken + next > right - left {
+                    return false;
+                }
+                taken += next;
+                true
+            })
+            .collect();
+
+        buffer.puts(line_offset, code_offset, &code, ElementStyle::Quotation);
+        if margin.was_cut_left() {
+            // We have stripped some code/whitespace from the beginning, make it clear.
+            buffer.puts(line_offset, code_offset, "...", ElementStyle::LineNumber);
+        }
+        if margin.was_cut_right(line_len) {
+            // We have stripped some code after the rightmost span end, make it clear we did so.
+            buffer.puts(
+                line_offset,
+                code_offset + taken - 3,
+                "...",
+                ElementStyle::LineNumber,
+            );
+        }
+        buffer.puts(
+            line_offset,
+            0,
+            &self.maybe_anonymized(line_index),
+            ElementStyle::LineNumber,
+        );
+
+        self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
+    }
+
+    fn draw_range(
+        &self,
+        buffer: &mut StyledBuffer,
+        symbol: char,
+        line: usize,
+        col_from: usize,
+        col_to: usize,
+        style: ElementStyle,
+    ) {
+        for col in col_from..col_to {
+            buffer.putc(line, col, symbol, style);
+        }
+    }
+
+    fn draw_multiline_line(
+        &self,
+        buffer: &mut StyledBuffer,
+        line: usize,
+        offset: usize,
+        depth: usize,
+        style: ElementStyle,
+    ) {
+        buffer.putc(line, offset + depth - 1, '|', style);
+    }
+
+    fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
+        self.draw_col_separator_no_space_with_style(
+            buffer,
+            '|',
+            line,
+            col,
+            ElementStyle::LineNumber,
+        );
+    }
+
+    fn draw_col_separator_no_space_with_style(
+        &self,
+        buffer: &mut StyledBuffer,
+        chr: char,
+        line: usize,
+        col: usize,
+        style: ElementStyle,
+    ) {
+        buffer.putc(line, col, chr, style);
+    }
+
+    fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
+        if self.anonymized_line_numbers {
+            Cow::Borrowed(ANONYMIZED_LINE_NUM)
+        } else {
+            Cow::Owned(line_num.to_string())
+        }
+    }
+
+    fn file_start(&self) -> &str {
+        "--> "
+    }
+
+    fn secondary_file_start(&self) -> &str {
+        "::: "
+    }
+}
+
+// instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until
+// we're higher. If the loop isn't exited by the `return`, the last multiplication will wrap, which
+// is OK, because while we cannot fit a higher power of 10 in a usize, the loop will end anyway.
+// This is also why we need the max number of decimal digits within a `usize`.
+fn num_decimal_digits(num: usize) -> usize {
+    #[cfg(target_pointer_width = "64")]
+    const MAX_DIGITS: usize = 20;
+
+    #[cfg(target_pointer_width = "32")]
+    const MAX_DIGITS: usize = 10;
+
+    #[cfg(target_pointer_width = "16")]
+    const MAX_DIGITS: usize = 5;
+
+    let mut lim = 10;
+    for num_digits in 1..MAX_DIGITS {
+        if num < lim {
+            return num_digits;
+        }
+        lim = lim.wrapping_mul(10);
+    }
+    MAX_DIGITS
+}
+
+pub fn str_width(s: &str) -> usize {
+    s.chars().map(char_width).sum()
+}
+
+pub fn char_width(ch: char) -> usize {
+    // 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.
+    match ch {
+        '\t' => 4,
+        // Keep the following list in sync with `rustc_errors::emitter::OUTPUT_REPLACEMENTS`. These
+        // are control points that we replace before printing with a visible codepoint for the sake
+        // of being able to point at them with underlines.
+        '\u{0000}' | '\u{0001}' | '\u{0002}' | '\u{0003}' | '\u{0004}' | '\u{0005}'
+        | '\u{0006}' | '\u{0007}' | '\u{0008}' | '\u{000B}' | '\u{000C}' | '\u{000D}'
+        | '\u{000E}' | '\u{000F}' | '\u{0010}' | '\u{0011}' | '\u{0012}' | '\u{0013}'
+        | '\u{0014}' | '\u{0015}' | '\u{0016}' | '\u{0017}' | '\u{0018}' | '\u{0019}'
+        | '\u{001A}' | '\u{001B}' | '\u{001C}' | '\u{001D}' | '\u{001E}' | '\u{001F}'
+        | '\u{007F}' | '\u{202A}' | '\u{202B}' | '\u{202D}' | '\u{202E}' | '\u{2066}'
+        | '\u{2067}' | '\u{2068}' | '\u{202C}' | '\u{2069}' => 1,
+        _ => unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1),
+    }
+}
+
+fn num_overlap(
+    a_start: usize,
+    a_end: usize,
+    b_start: usize,
+    b_end: usize,
+    inclusive: bool,
+) -> bool {
+    let extra = usize::from(inclusive);
+    (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
+}
+
+fn overlaps(a1: &LineAnnotation<'_>, a2: &LineAnnotation<'_>, padding: usize) -> bool {
+    num_overlap(
+        a1.start.display,
+        a1.end.display + padding,
+        a2.start.display,
+        a2.end.display,
+        false,
+    )
+}
+
+#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub(crate) enum LineAnnotationType {
+    /// Annotation under a single line of code
+    Singleline,
+
+    // The Multiline type above is replaced with the following three in order
+    // to reuse the current label drawing code.
+    //
+    // Each of these corresponds to one part of the following diagram:
+    //
+    //     x |   foo(1 + bar(x,
+    //       |  _________^              < MultilineStart
+    //     x | |             y),        < MultilineLine
+    //       | |______________^ label   < MultilineEnd
+    //     x |       z);
+    /// Annotation marking the first character of a fully shown multiline span
+    MultilineStart(usize),
+    /// Annotation marking the last character of a fully shown multiline span
+    MultilineEnd(usize),
+    /// Line at the left enclosing the lines of a fully shown multiline span
+    // Just a placeholder for the drawing algorithm, to know that it shouldn't skip the first 4
+    // and last 2 lines of code. The actual line is drawn in `emit_message_default` and not in
+    // `draw_multiline_line`.
+    MultilineLine(usize),
+}
+
+#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub(crate) struct LineAnnotation<'a> {
+    /// Start column.
+    /// Note that it is important that this field goes
+    /// first, so that when we sort, we sort orderings by start
+    /// column.
+    pub start: Loc,
+
+    /// End column within the line (exclusive)
+    pub end: Loc,
+
+    /// level
+    pub level: Level,
+
+    /// Optional label to display adjacent to the annotation.
+    pub label: Option<&'a str>,
+
+    /// Is this a single line, multiline or multiline span minimized down to a
+    /// smaller span.
+    pub annotation_type: LineAnnotationType,
+}
+
+impl LineAnnotation<'_> {
+    /// Whether this annotation is a vertical line placeholder.
+    pub(crate) fn is_line(&self) -> bool {
+        matches!(self.annotation_type, LineAnnotationType::MultilineLine(_))
+    }
 
-    /// Render a snippet into a `Display`able object
-    pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a {
-        DisplayList::new(
-            msg,
-            &self.stylesheet,
-            self.anonymized_line_numbers,
-            self.term_width,
+    /// Length of this annotation as displayed in the stderr output
+    pub(crate) fn len(&self) -> usize {
+        // Account for usize underflows
+        if self.end.display > self.start.display {
+            self.end.display - self.start.display
+        } else {
+            self.start.display - self.end.display
+        }
+    }
+
+    pub(crate) fn has_label(&self) -> bool {
+        if let Some(label) = self.label {
+            // Consider labels with no text as effectively not being there
+            // to avoid weird output with unnecessary vertical lines, like:
+            //
+            //     X | fn foo(x: u32) {
+            //       | -------^------
+            //       | |      |
+            //       | |
+            //       |
+            //
+            // Note that this would be the complete output users would see.
+            !label.is_empty()
+        } else {
+            false
+        }
+    }
+
+    pub(crate) fn takes_space(&self) -> bool {
+        // Multiline annotations always have to keep vertical space.
+        matches!(
+            self.annotation_type,
+            LineAnnotationType::MultilineStart(_) | LineAnnotationType::MultilineEnd(_)
         )
     }
 }
+
+// We replace some characters so the CLI output is always consistent and underlines aligned.
+// Keep the following list in sync with `rustc_span::char_width`.
+const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
+    // In terminals without Unicode support the following will be garbled, but in *all* terminals
+    // the underlying codepoint will be as well. We could gate this replacement behind a "unicode
+    // support" gate.
+    ('\0', "␀"),
+    ('\u{0001}', "␁"),
+    ('\u{0002}', "␂"),
+    ('\u{0003}', "␃"),
+    ('\u{0004}', "␄"),
+    ('\u{0005}', "␅"),
+    ('\u{0006}', "␆"),
+    ('\u{0007}', "␇"),
+    ('\u{0008}', "␈"),
+    ('\t', "    "), // We do our own tab replacement
+    ('\u{000b}', "␋"),
+    ('\u{000c}', "␌"),
+    ('\u{000d}', "␍"),
+    ('\u{000e}', "␎"),
+    ('\u{000f}', "␏"),
+    ('\u{0010}', "␐"),
+    ('\u{0011}', "␑"),
+    ('\u{0012}', "␒"),
+    ('\u{0013}', "␓"),
+    ('\u{0014}', "␔"),
+    ('\u{0015}', "␕"),
+    ('\u{0016}', "␖"),
+    ('\u{0017}', "␗"),
+    ('\u{0018}', "␘"),
+    ('\u{0019}', "␙"),
+    ('\u{001a}', "␚"),
+    ('\u{001b}', "␛"),
+    ('\u{001c}', "␜"),
+    ('\u{001d}', "␝"),
+    ('\u{001e}', "␞"),
+    ('\u{001f}', "␟"),
+    ('\u{007f}', "␡"),
+    ('\u{200d}', ""), // Replace ZWJ 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{202c}', "�"), // not corresponding to the visible source code, so we replace them always.
+    ('\u{202d}', "�"),
+    ('\u{202e}', "�"),
+    ('\u{2066}', "�"),
+    ('\u{2067}', "�"),
+    ('\u{2068}', "�"),
+    ('\u{2069}', "�"),
+];
+
+fn normalize_whitespace(s: &str) -> String {
+    // Scan the input string for a character in the ordered table above.
+    // If it's present, replace it with its alternative string (it can be more than 1 char!).
+    // Otherwise, retain the input char.
+    s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
+        match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
+            Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
+            _ => s.push(c),
+        }
+        s
+    })
+}
+
+#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub(crate) enum ElementStyle {
+    MainHeaderMsg,
+    HeaderMsg,
+    LineAndColumn,
+    LineNumber,
+    Quotation,
+    NoStyle,
+    Level(Level),
+}
+
+impl ElementStyle {
+    fn color_spec(&self, stylesheet: &Stylesheet) -> Style {
+        match self {
+            ElementStyle::LineAndColumn => stylesheet.none,
+            ElementStyle::LineNumber => stylesheet.line_no,
+            ElementStyle::Quotation => stylesheet.none,
+            ElementStyle::MainHeaderMsg => stylesheet.emphasis,
+            ElementStyle::HeaderMsg | ElementStyle::NoStyle => stylesheet.none,
+            ElementStyle::Level(lvl) => lvl.style(stylesheet),
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::OUTPUT_REPLACEMENTS;
+    use snapbox::IntoData;
+
+    fn format_replacements(replacements: Vec<(char, &str)>) -> String {
+        replacements
+            .into_iter()
+            .map(|r| format!("    {r:?}"))
+            .collect::<Vec<_>>()
+            .join("\n")
+    }
+
+    #[test]
+    /// The [`OUTPUT_REPLACEMENTS`] array must be sorted (for binary search to
+    /// work) and must contain no duplicate entries
+    fn ensure_output_replacements_is_sorted() {
+        let mut expected = OUTPUT_REPLACEMENTS.to_owned();
+        expected.sort_by_key(|r| r.0);
+        expected.dedup_by_key(|r| r.0);
+        let expected = format_replacements(expected);
+        let actual = format_replacements(OUTPUT_REPLACEMENTS.to_owned());
+        snapbox::assert_data_eq!(actual, expected.into_data().raw());
+    }
+}
diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs
new file mode 100644
index 0000000..0169566
--- /dev/null
+++ b/src/renderer/source_map.rs
@@ -0,0 +1,446 @@
+use crate::renderer::{char_width, num_overlap, LineAnnotation, LineAnnotationType};
+use crate::{Annotation, Level};
+use std::cmp::{max, min};
+use std::ops::Range;
+
+#[derive(Debug)]
+pub(crate) struct SourceMap<'a> {
+    lines: Vec<LineInfo<'a>>,
+    source: &'a str,
+}
+
+impl<'a> SourceMap<'a> {
+    pub(crate) fn new(source: &'a str, line_start: usize) -> Self {
+        let mut current_index = 0;
+
+        let mut mapping = vec![];
+        for (idx, (line, end_line)) in CursorLines::new(source).enumerate() {
+            let line_length = line.len();
+            let line_range = current_index..current_index + line_length;
+            let end_line_size = end_line.len();
+
+            mapping.push(LineInfo {
+                line,
+                line_index: line_start + idx,
+                start_byte: line_range.start,
+                end_byte: line_range.end + end_line_size,
+                end_line_size,
+            });
+
+            current_index += line_length + end_line_size;
+        }
+        Self {
+            lines: mapping,
+            source,
+        }
+    }
+
+    pub(crate) fn get_line(&self, idx: usize) -> Option<&'a str> {
+        self.lines
+            .iter()
+            .find(|l| l.line_index == idx)
+            .map(|info| info.line)
+    }
+
+    pub(crate) fn span_to_locations(&self, span: Range<usize>) -> (Loc, Loc) {
+        let start_info = self
+            .lines
+            .iter()
+            .find(|info| span.start >= info.start_byte && span.start < info.end_byte)
+            .unwrap_or(self.lines.last().unwrap());
+        let (mut start_char_pos, start_display_pos) = start_info.line
+            [0..(span.start - start_info.start_byte).min(start_info.line.len())]
+            .chars()
+            .fold((0, 0), |(char_pos, byte_pos), c| {
+                let display = char_width(c);
+                (char_pos + 1, byte_pos + display)
+            });
+        // correct the char pos if we are highlighting the end of a line
+        if (span.start - start_info.start_byte).saturating_sub(start_info.line.len()) > 0 {
+            start_char_pos += 1;
+        }
+        let start = Loc {
+            line: start_info.line_index,
+            char: start_char_pos,
+            display: start_display_pos,
+            byte: span.start,
+        };
+
+        if span.start == span.end {
+            return (start, start);
+        }
+
+        let end_info = self
+            .lines
+            .iter()
+            .find(|info| info.end_byte > span.end.saturating_sub(1))
+            .unwrap_or(self.lines.last().unwrap());
+        let (mut end_char_pos, end_display_pos) = end_info.line
+            [0..(span.end - end_info.start_byte).min(end_info.line.len())]
+            .chars()
+            .fold((0, 0), |(char_pos, byte_pos), c| {
+                let display = char_width(c);
+                (char_pos + 1, byte_pos + display)
+            });
+
+        // correct the char pos if we are highlighting the end of a line
+        if (span.end - end_info.start_byte).saturating_sub(end_info.line.len()) > 0 {
+            end_char_pos += 1;
+        }
+        let mut end = Loc {
+            line: end_info.line_index,
+            char: end_char_pos,
+            display: end_display_pos,
+            byte: span.end,
+        };
+        if start.line != end.line && end.byte > end_info.end_byte - end_info.end_line_size {
+            end.char += 1;
+            end.display += 1;
+        }
+
+        (start, end)
+    }
+
+    pub(crate) fn annotated_lines(
+        &self,
+        annotations: Vec<Annotation<'a>>,
+        fold: bool,
+    ) -> Vec<AnnotatedLineInfo<'a>> {
+        let source_len = self.source.len();
+        if let Some(bigger) = annotations.iter().find_map(|x| {
+            // Allow highlighting one past the last character in the source.
+            if source_len + 1 < x.range.end {
+                Some(&x.range)
+            } else {
+                None
+            }
+        }) {
+            panic!("Annotation range `{bigger:?}` is beyond the end of buffer `{source_len}`")
+        }
+
+        let mut annotated_line_infos = self
+            .lines
+            .iter()
+            .map(|info| AnnotatedLineInfo {
+                line: info.line,
+                line_index: info.line_index,
+                annotations: vec![],
+            })
+            .collect::<Vec<_>>();
+        let mut multiline_annotations = vec![];
+
+        for Annotation {
+            range,
+            label,
+            level,
+        } in annotations
+        {
+            let (lo, mut hi) = self.span_to_locations(range);
+
+            // Watch out for "empty spans". If we get a span like 6..6, we
+            // want to just display a `^` at 6, so convert that to
+            // 6..7. This is degenerate input, but it's best to degrade
+            // gracefully -- and the parser likes to supply a span like
+            // that for EOF, in particular.
+
+            if lo.display == hi.display && lo.line == hi.line {
+                hi.display += 1;
+            }
+
+            if lo.line == hi.line {
+                let line_ann = LineAnnotation {
+                    start: lo,
+                    end: hi,
+                    level,
+                    label,
+                    annotation_type: LineAnnotationType::Singleline,
+                };
+                self.add_annotation_to_file(&mut annotated_line_infos, lo.line, line_ann);
+            } else {
+                multiline_annotations.push(MultilineAnnotation {
+                    depth: 1,
+                    start: lo,
+                    end: hi,
+                    level,
+                    label,
+                    overlaps_exactly: false,
+                });
+            }
+        }
+
+        // Find overlapping multiline annotations, put them at different depths
+        multiline_annotations
+            .sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line, ml.start.byte));
+        for ann in multiline_annotations.clone() {
+            for a in &mut multiline_annotations {
+                // Move all other multiline annotations overlapping with this one
+                // one level to the right.
+                if !ann.same_span(a)
+                    && num_overlap(ann.start.line, ann.end.line, a.start.line, a.end.line, true)
+                {
+                    a.increase_depth();
+                } else if ann.same_span(a) && &ann != a {
+                    a.overlaps_exactly = true;
+                } else {
+                    break;
+                }
+            }
+        }
+
+        let mut max_depth = 0; // max overlapping multiline spans
+        for ann in &multiline_annotations {
+            max_depth = max(max_depth, ann.depth);
+        }
+        // Change order of multispan depth to minimize the number of overlaps in the ASCII art.
+        for a in &mut multiline_annotations {
+            a.depth = max_depth - a.depth + 1;
+        }
+        for ann in multiline_annotations {
+            let mut end_ann = ann.as_end();
+            if ann.overlaps_exactly {
+                end_ann.annotation_type = LineAnnotationType::Singleline;
+            } else {
+                // avoid output like
+                //
+                //  |        foo(
+                //  |   _____^
+                //  |  |_____|
+                //  | ||         bar,
+                //  | ||     );
+                //  | ||      ^
+                //  | ||______|
+                //  |  |______foo
+                //  |         baz
+                //
+                // and instead get
+                //
+                //  |       foo(
+                //  |  _____^
+                //  | |         bar,
+                //  | |     );
+                //  | |      ^
+                //  | |      |
+                //  | |______foo
+                //  |        baz
+                self.add_annotation_to_file(
+                    &mut annotated_line_infos,
+                    ann.start.line,
+                    ann.as_start(),
+                );
+                // 4 is the minimum vertical length of a multiline span when presented: two lines
+                // of code and two lines of underline. This is not true for the special case where
+                // the beginning doesn't have an underline, but the current logic seems to be
+                // working correctly.
+                let middle = min(ann.start.line + 4, ann.end.line);
+                // We'll show up to 4 lines past the beginning of the multispan start.
+                // We will *not* include the tail of lines that are only whitespace, a comment or
+                // a bare delimiter.
+                let filter = |s: &str| {
+                    let s = s.trim();
+                    // Consider comments as empty, but don't consider docstrings to be empty.
+                    !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
+                        // Consider lines with nothing but whitespace, a single delimiter as empty.
+                        && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
+                };
+                let until = (ann.start.line..middle)
+                    .rev()
+                    .filter_map(|line| self.get_line(line).map(|s| (line + 1, s)))
+                    .find(|(_, s)| filter(s))
+                    .map_or(ann.start.line, |(line, _)| line);
+                for line in ann.start.line + 1..until {
+                    // Every `|` that joins the beginning of the span (`___^`) to the end (`|__^`).
+                    self.add_annotation_to_file(&mut annotated_line_infos, line, ann.as_line());
+                }
+                let line_end = ann.end.line - 1;
+                let end_is_empty = self.get_line(line_end).map_or(false, |s| !filter(s));
+                if middle < line_end && !end_is_empty {
+                    self.add_annotation_to_file(&mut annotated_line_infos, line_end, ann.as_line());
+                }
+            }
+            self.add_annotation_to_file(&mut annotated_line_infos, end_ann.end.line, end_ann);
+        }
+
+        if fold {
+            annotated_line_infos.retain(|l| !l.annotations.is_empty());
+        }
+
+        annotated_line_infos
+            .iter_mut()
+            .for_each(|l| l.annotations.sort_by(|a, b| a.start.cmp(&b.start)));
+
+        annotated_line_infos
+    }
+
+    fn add_annotation_to_file(
+        &self,
+        annotated_line_infos: &mut Vec<AnnotatedLineInfo<'a>>,
+        line_index: usize,
+        line_ann: LineAnnotation<'a>,
+    ) {
+        if let Some(line_info) = annotated_line_infos
+            .iter_mut()
+            .find(|line_info| line_info.line_index == line_index)
+        {
+            line_info.annotations.push(line_ann);
+        } else {
+            let info = self
+                .lines
+                .iter()
+                .find(|l| l.line_index == line_index)
+                .unwrap();
+            annotated_line_infos.push(AnnotatedLineInfo {
+                line: info.line,
+                line_index,
+                annotations: vec![line_ann],
+            });
+            annotated_line_infos.sort_by_key(|l| l.line_index);
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub(crate) struct MultilineAnnotation<'a> {
+    pub depth: usize,
+    pub start: Loc,
+    pub end: Loc,
+    pub level: Level,
+    pub label: Option<&'a str>,
+    pub overlaps_exactly: bool,
+}
+
+impl<'a> MultilineAnnotation<'a> {
+    pub(crate) fn increase_depth(&mut self) {
+        self.depth += 1;
+    }
+
+    /// Compare two `MultilineAnnotation`s considering only the `Span` they cover.
+    pub(crate) fn same_span(&self, other: &MultilineAnnotation<'_>) -> bool {
+        self.start == other.start && self.end == other.end
+    }
+
+    pub(crate) fn as_start(&self) -> LineAnnotation<'a> {
+        LineAnnotation {
+            start: self.start,
+            end: Loc {
+                line: self.start.line,
+                char: self.start.char + 1,
+                display: self.start.display + 1,
+                byte: self.start.byte + 1,
+            },
+            level: self.level,
+            label: None,
+            annotation_type: LineAnnotationType::MultilineStart(self.depth),
+        }
+    }
+
+    pub(crate) fn as_end(&self) -> LineAnnotation<'a> {
+        LineAnnotation {
+            start: Loc {
+                line: self.end.line,
+                char: self.end.char.saturating_sub(1),
+                display: self.end.display.saturating_sub(1),
+                byte: self.end.byte.saturating_sub(1),
+            },
+            end: self.end,
+            level: self.level,
+            label: self.label,
+            annotation_type: LineAnnotationType::MultilineEnd(self.depth),
+        }
+    }
+
+    pub(crate) fn as_line(&self) -> LineAnnotation<'a> {
+        LineAnnotation {
+            start: Loc::default(),
+            end: Loc::default(),
+            level: self.level,
+            label: None,
+            annotation_type: LineAnnotationType::MultilineLine(self.depth),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct LineInfo<'a> {
+    pub(crate) line: &'a str,
+    pub(crate) line_index: usize,
+    pub(crate) start_byte: usize,
+    pub(crate) end_byte: usize,
+    end_line_size: usize,
+}
+
+#[derive(Debug)]
+pub(crate) struct AnnotatedLineInfo<'a> {
+    pub(crate) line: &'a str,
+    pub(crate) line_index: usize,
+    pub(crate) annotations: Vec<LineAnnotation<'a>>,
+}
+
+/// A source code location used for error reporting.
+#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq)]
+pub(crate) struct Loc {
+    /// The (1-based) line number.
+    pub(crate) line: usize,
+    /// The (0-based) column offset.
+    pub(crate) char: usize,
+    /// The (0-based) column offset when displayed.
+    pub(crate) display: usize,
+    /// The (0-based) byte offset.
+    pub(crate) byte: usize,
+}
+
+struct CursorLines<'a>(&'a str);
+
+impl CursorLines<'_> {
+    fn new(src: &str) -> CursorLines<'_> {
+        CursorLines(src)
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+enum EndLine {
+    Eof,
+    Lf,
+    Crlf,
+}
+
+impl EndLine {
+    /// The number of characters this line ending occupies in bytes.
+    pub(crate) fn len(self) -> usize {
+        match self {
+            EndLine::Eof => 0,
+            EndLine::Lf => 1,
+            EndLine::Crlf => 2,
+        }
+    }
+}
+
+impl<'a> Iterator for CursorLines<'a> {
+    type Item = (&'a str, EndLine);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.0.is_empty() {
+            None
+        } else {
+            self.0
+                .find('\n')
+                .map(|x| {
+                    let ret = if 0 < x {
+                        if self.0.as_bytes()[x - 1] == b'\r' {
+                            (&self.0[..x - 1], EndLine::Crlf)
+                        } else {
+                            (&self.0[..x], EndLine::Lf)
+                        }
+                    } else {
+                        ("", EndLine::Lf)
+                    };
+                    self.0 = &self.0[x + 1..];
+                    ret
+                })
+                .or_else(|| {
+                    let ret = Some((self.0, EndLine::Eof));
+                    self.0 = "";
+                    ret
+                })
+        }
+    }
+}
diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs
index ec834e1..fd72358 100644
--- a/src/renderer/styled_buffer.rs
+++ b/src/renderer/styled_buffer.rs
@@ -3,7 +3,7 @@
 //! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs
 
 use crate::renderer::stylesheet::Stylesheet;
-use anstyle::Style;
+use crate::renderer::ElementStyle;
 use std::fmt;
 use std::fmt::Write;
 
@@ -15,13 +15,13 @@ pub(crate) struct StyledBuffer {
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub(crate) struct StyledChar {
     ch: char,
-    style: Style,
+    style: ElementStyle,
 }
 
 impl StyledChar {
-    pub(crate) const SPACE: Self = StyledChar::new(' ', Style::new());
+    pub(crate) const SPACE: Self = StyledChar::new(' ', ElementStyle::NoStyle);
 
-    pub(crate) const fn new(ch: char, style: Style) -> StyledChar {
+    pub(crate) const fn new(ch: char, style: ElementStyle) -> StyledChar {
         StyledChar { ch, style }
     }
 }
@@ -41,15 +41,16 @@ impl StyledBuffer {
         let mut str = String::new();
         for (i, line) in self.lines.iter().enumerate() {
             let mut current_style = stylesheet.none;
-            for ch in line {
-                if ch.style != current_style {
+            for StyledChar { ch, style } in line {
+                let ch_style = style.color_spec(stylesheet);
+                if ch_style != current_style {
                     if !line.is_empty() {
                         write!(str, "{}", current_style.render_reset())?;
                     }
-                    current_style = ch.style;
+                    current_style = ch_style;
                     write!(str, "{}", current_style.render())?;
                 }
-                write!(str, "{}", ch.ch)?;
+                write!(str, "{ch}")?;
             }
             write!(str, "{}", current_style.render_reset())?;
             if i != self.lines.len() - 1 {
@@ -62,7 +63,7 @@ impl StyledBuffer {
     /// Sets `chr` with `style` for given `line`, `col`.
     /// If `line` does not exist in our buffer, adds empty lines up to the given
     /// and fills the last line with unstyled whitespace.
-    pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) {
+    pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: ElementStyle) {
         self.ensure_lines(line);
         if col >= self.lines[line].len() {
             self.lines[line].resize(col + 1, StyledChar::SPACE);
@@ -73,16 +74,17 @@ impl StyledBuffer {
     /// Sets `string` with `style` for given `line`, starting from `col`.
     /// If `line` does not exist in our buffer, adds empty lines up to the given
     /// and fills the last line with unstyled whitespace.
-    pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) {
+    pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: ElementStyle) {
         let mut n = col;
         for c in string.chars() {
             self.putc(line, n, c, style);
             n += 1;
         }
     }
+
     /// For given `line` inserts `string` with `style` after old content of that line,
     /// adding lines if needed
-    pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) {
+    pub(crate) fn append(&mut self, line: usize, string: &str, style: ElementStyle) {
         if line >= self.lines.len() {
             self.puts(line, 0, string, style);
         } else {
@@ -91,6 +93,22 @@ impl StyledBuffer {
         }
     }
 
+    /// For given `line` inserts `string` with `style` before old content of that line,
+    /// adding lines if needed
+    pub(crate) fn prepend(&mut self, line: usize, string: &str, style: ElementStyle) {
+        self.ensure_lines(line);
+        let string_len = string.chars().count();
+
+        if !self.lines[line].is_empty() {
+            // Push the old content over to make room for new content
+            for _ in 0..string_len {
+                self.lines[line].insert(0, StyledChar::SPACE);
+            }
+        }
+
+        self.puts(line, 0, string, style);
+    }
+
     pub(crate) fn num_lines(&self) -> usize {
         self.lines.len()
     }
diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs
index ee1ab93..72a5f0e 100644
--- a/src/renderer/stylesheet.rs
+++ b/src/renderer/stylesheet.rs
@@ -32,37 +32,3 @@ impl Stylesheet {
         }
     }
 }
-
-impl Stylesheet {
-    pub(crate) fn error(&self) -> &Style {
-        &self.error
-    }
-
-    pub(crate) fn warning(&self) -> &Style {
-        &self.warning
-    }
-
-    pub(crate) fn info(&self) -> &Style {
-        &self.info
-    }
-
-    pub(crate) fn note(&self) -> &Style {
-        &self.note
-    }
-
-    pub(crate) fn help(&self) -> &Style {
-        &self.help
-    }
-
-    pub(crate) fn line_no(&self) -> &Style {
-        &self.line_no
-    }
-
-    pub(crate) fn emphasis(&self) -> &Style {
-        &self.emphasis
-    }
-
-    pub(crate) fn none(&self) -> &Style {
-        &self.none
-    }
-}
diff --git a/src/snippet.rs b/src/snippet.rs
index 8e9a3a8..d9ff594 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -10,8 +10,16 @@
 //!     .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
 //! ```
 
+use crate::renderer::stylesheet::Stylesheet;
+use anstyle::Style;
 use std::ops::Range;
 
+pub(crate) const ERROR_TXT: &str = "error";
+pub(crate) const HELP_TXT: &str = "help";
+pub(crate) const INFO_TXT: &str = "info";
+pub(crate) const NOTE_TXT: &str = "note";
+pub(crate) const WARNING_TXT: &str = "warning";
+
 /// Primary structure provided for formatting
 ///
 /// See [`Level::title`] to create a [`Message`]
@@ -51,6 +59,46 @@ impl<'a> Message<'a> {
     }
 }
 
+impl Message<'_> {
+    pub(crate) fn has_primary_spans(&self) -> bool {
+        self.snippets.iter().any(|s| !s.annotations.is_empty())
+    }
+    pub(crate) fn has_span_labels(&self) -> bool {
+        self.snippets.iter().any(|s| !s.annotations.is_empty())
+    }
+
+    pub(crate) fn max_line_number(&self) -> usize {
+        let mut max = self
+            .snippets
+            .iter()
+            .map(|s| {
+                let start = s
+                    .annotations
+                    .iter()
+                    .map(|a| a.range.start)
+                    .min()
+                    .unwrap_or(0);
+
+                let end = s
+                    .annotations
+                    .iter()
+                    .map(|a| a.range.end)
+                    .max()
+                    .unwrap_or(s.source.len())
+                    .min(s.source.len());
+
+                s.line_start + newline_count(&s.source[start..end])
+            })
+            .max()
+            .unwrap_or(1);
+
+        for footer in &self.footer {
+            max = max.max(footer.max_line_number());
+        }
+        max
+    }
+}
+
 /// Structure containing the slice of text to be annotated and
 /// basic information about the location of the slice.
 ///
@@ -108,7 +156,7 @@ impl<'a> Snippet<'a> {
 /// An annotation for a [`Snippet`].
 ///
 /// See [`Level::span`] to create a [`Annotation`]
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct Annotation<'a> {
     /// The byte range of the annotation in the `source` string
     pub(crate) range: Range<usize>,
@@ -124,7 +172,7 @@ impl<'a> Annotation<'a> {
 }
 
 /// Types of annotations.
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
 pub enum Level {
     /// Error annotations are displayed using red color and "^" character.
     Error,
@@ -155,3 +203,36 @@ impl Level {
         }
     }
 }
+
+impl Level {
+    pub(crate) fn as_str(&self) -> &'static str {
+        match self {
+            Level::Error => ERROR_TXT,
+            Level::Warning => WARNING_TXT,
+            Level::Info => INFO_TXT,
+            Level::Note => NOTE_TXT,
+            Level::Help => HELP_TXT,
+        }
+    }
+
+    pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style {
+        match self {
+            Level::Error => stylesheet.error,
+            Level::Warning => stylesheet.warning,
+            Level::Info => stylesheet.info,
+            Level::Note => stylesheet.note,
+            Level::Help => stylesheet.help,
+        }
+    }
+}
+
+fn newline_count(body: &str) -> usize {
+    #[cfg(feature = "simd")]
+    {
+        memchr::memchr_iter(b'\n', body.as_bytes()).count()
+    }
+    #[cfg(not(feature = "simd"))]
+    {
+        body.lines().count()
+    }
+}
diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg
index 3e813a0..2ff0364 100644
--- a/tests/fixtures/color/ann_multiline.svg
+++ b/tests/fixtures/color/ann_multiline.svg
@@ -27,7 +27,7 @@
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                           if let DisplayLine::Source {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">________________________________^</tspan>
+    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold"> ________________________________^</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>                             ref mut inline_marks,</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg
index b68a553..1afc652 100644
--- a/tests/fixtures/color/fold_ann_multiline.svg
+++ b/tests/fixtures/color/fold_ann_multiline.svg
@@ -1,4 +1,4 @@
-<svg width="869px" height="218px" xmlns="http://www.w3.org/2000/svg">
+<svg width="869px" height="236px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -34,13 +34,15 @@
 </tspan>
     <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         match (ann.range.0, ann.range.1) {</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">...</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">|</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (None, None) =&gt; continue,</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">71</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>         }</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>             (Some(start), Some(end)) if start &gt; end_index || end &lt; start_index =&gt; continue,</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">...</tspan><tspan>  </tspan><tspan class="fg-bright-red bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan>
+    <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan>     }</tspan>
+</tspan>
+    <tspan x="10px" y="226px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg
index 4bd5f58..bd075e4 100644
--- a/tests/fixtures/color/fold_bad_origin_line.svg
+++ b/tests/fixtures/color/fold_bad_origin_line.svg
@@ -20,7 +20,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: </tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>path/to/error.rs:3:1</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg
index 23b31d4..22a66c7 100644
--- a/tests/fixtures/color/fold_leading.svg
+++ b/tests/fixtures/color/fold_leading.svg
@@ -21,13 +21,13 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:11:13</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:11:13</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> workspace = 20</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11|</tspan><tspan> workspace = 20</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             </tspan><tspan class="fg-bright-red bold">^^</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             </tspan><tspan class="fg-bright-red bold">^^</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg
index 6ba0199..4e3bf1e 100644
--- a/tests/fixtures/color/issue_9.svg
+++ b/tests/fixtures/color/issue_9.svg
@@ -1,4 +1,4 @@
-<svg width="911px" height="200px" xmlns="http://www.w3.org/2000/svg">
+<svg width="919px" height="218px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -22,23 +22,25 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
+    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
+    <tspan x="10px" y="172px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
+</tspan>
+    <tspan x="10px" y="208px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg
index 26df4ae..96101a5 100644
--- a/tests/fixtures/color/multiple_annotations.svg
+++ b/tests/fixtures/color/multiple_annotations.svg
@@ -19,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: </tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg
index d1d5d87..ef59075 100644
--- a/tests/fixtures/color/simple.svg
+++ b/tests/fixtures/color/simple.svg
@@ -22,7 +22,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format_color.rs:171:9</tspan>
+    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format_color.rs:169:11</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg
index 89047b3..6f799d3 100644
--- a/tests/fixtures/color/strip_line_non_ws.svg
+++ b/tests/fixtures/color/strip_line_non_ws.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/non-whitespace-trimming.rs:4:242</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/non-whitespace-trimming.rs:4:233</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml
index c6573ff..bfe9f31 100644
--- a/tests/fixtures/color/strip_line_non_ws.toml
+++ b/tests/fixtures/color/strip_line_non_ws.toml
@@ -13,12 +13,12 @@ origin = "$DIR/non-whitespace-trimming.rs"
 [[message.snippets.annotations]]
 label = "expected `()`, found integer"
 level = "Error"
-range = [241, 243]
+range = [237, 239]
 
 [[message.snippets.annotations]]
 label = "expected due to this"
 level = "Error"
-range = [236, 238]
+range = [232, 234]
 
 
 [renderer]
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index 5ff1105..2708262 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -37,6 +37,6 @@ fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> {
     let renderer: Renderer = fixture.renderer.into();
     let message: Message<'_> = (&fixture.message).into();
 
-    let actual = renderer.render(message).to_string();
+    let actual = renderer.render(message);
     Ok(Data::from(actual).coerce_to(DataFormat::TermSvg))
 }
diff --git a/tests/formatter.rs b/tests/formatter.rs
index d169421..3e9246a 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -19,7 +19,7 @@ error: oops
 "#]];
 
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+    assert_data_eq!(renderer.render(snippets), expected);
 }
 
 #[test]
@@ -31,7 +31,7 @@ fn test_point_to_double_width_characters() {
     );
 
     let expected = str![[r#"
-error
+error: 
  --> <current file>:1:7
   |
 1 | こんにちは、世界
@@ -39,7 +39,7 @@ error
 "#]];
 
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+    assert_data_eq!(renderer.render(snippets), expected);
 }
 
 #[test]
@@ -51,7 +51,7 @@ fn test_point_to_double_width_characters_across_lines() {
     );
 
     let expected = str![[r#"
-error
+error: 
  --> <current file>:1:3
   |
 1 |   おはよう
@@ -61,7 +61,7 @@ error
 "#]];
 
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+    assert_data_eq!(renderer.render(snippets), expected);
 }
 
 #[test]
@@ -74,17 +74,17 @@ fn test_point_to_double_width_characters_multiple() {
     );
 
     let expected = str![[r#"
-error
+error: 
  --> <current file>:1:1
   |
 1 | お寿司
   | ^^^^^^ Sushi1
 2 | 食べたい🍣
-  |     ---- note: Sushi2
+  |     ---- Sushi2
 "#]];
 
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+    assert_data_eq!(renderer.render(snippets), expected);
 }
 
 #[test]
@@ -96,7 +96,7 @@ fn test_point_to_double_width_characters_mixed() {
     );
 
     let expected = str![[r#"
-error
+error: 
  --> <current file>:1:7
   |
 1 | こんにちは、新しいWorld!
@@ -104,7 +104,7 @@ error
 "#]];
 
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+    assert_data_eq!(renderer.render(snippets), expected);
 }
 
 #[test]
@@ -113,7 +113,7 @@ fn test_format_title() {
 
     let expected = str![r#"error[E0001]: This is a title"#];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -124,13 +124,13 @@ fn test_format_snippet_only() {
         .snippet(Snippet::source(source).line_start(5402));
 
     let expected = str![[r#"
-error
+error: 
      |
 5402 | This is line 1
 5403 | This is line 2
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -142,7 +142,7 @@ fn test_format_snippets_continuation() {
         .snippet(Snippet::source(src_0).line_start(5402).origin("file1.rs"))
         .snippet(Snippet::source(src_1).line_start(2).origin("file2.rs"));
     let expected = str![[r#"
-error
+error: 
     --> file1.rs
      |
 5402 | This is slice 1
@@ -152,7 +152,7 @@ error
 2    | This is slice 2
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -168,14 +168,14 @@ fn test_format_snippet_annotation_standalone() {
             .annotation(Level::Info.span(range.clone()).label("Test annotation")),
     );
     let expected = str![[r#"
-error
+error: 
      |
 5402 | This is line 1
 5403 | This is line 2
-     |        -- info: Test annotation
+     |        -- Test annotation
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -184,11 +184,11 @@ fn test_format_footer_title() {
         .title("")
         .footer(Level::Error.title("This __is__ a title"));
     let expected = str![[r#"
-error
- = error: This __is__ a title
+error: 
+  = error: This __is__ a title
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -202,7 +202,7 @@ fn test_i26() {
             .annotation(Level::Error.span(0..source.len() + 2).label(label)),
     );
     let renderer = Renderer::plain();
-    let _ = renderer.render(input).to_string();
+    let _ = renderer.render(input);
 }
 
 #[test]
@@ -212,13 +212,13 @@ fn test_source_content() {
         .title("")
         .snippet(Snippet::source(source).line_start(56));
     let expected = str![[r#"
-error
+error: 
    |
 56 | This is an example
 57 | of content lines
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -230,13 +230,13 @@ fn test_source_annotation_standalone_singleline() {
             .annotation(Level::Help.span(0..5).label("Example string")),
     );
     let expected = str![[r#"
-error
+error: 
   |
 1 | tests
-  | ----- help: Example string
+  | ----- Example string
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -249,16 +249,16 @@ fn test_source_annotation_standalone_multiline() {
             .annotation(Level::Help.span(0..5).label("Second line")),
     );
     let expected = str![[r#"
-error
+error: 
   |
 1 | tests
   | -----
   | |
-  | help: Example string
-  | help: Second line
+  | Example string
+  | Second line
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -267,12 +267,12 @@ fn test_only_source() {
         .title("")
         .snippet(Snippet::source("").origin("file.rs"));
     let expected = str![[r#"
-error
---> file.rs
- |
+error: 
+ --> file.rs
+  |
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -282,7 +282,7 @@ fn test_anon_lines() {
         .title("")
         .snippet(Snippet::source(source).line_start(56));
     let expected = str![[r#"
-error
+error: 
    |
 LL | This is an example
 LL | of content lines
@@ -290,7 +290,7 @@ LL |
 LL | abc
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -312,7 +312,7 @@ error: dummy
   | |___^
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -329,7 +329,7 @@ a\"
             .annotation(Level::Error.span(0..10)), // 1..10 works
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:1
   |
 3 | / a"
@@ -337,7 +337,7 @@ error
   | |_______^
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -350,7 +350,7 @@ fn char_and_nl_annotate_char() {
             .annotation(Level::Error.span(0..2)), // a\r
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:1
   |
 3 | a
@@ -358,7 +358,7 @@ error
 4 | b
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -371,7 +371,7 @@ fn char_eol_annotate_char() {
             .annotation(Level::Error.span(0..3)), // a\r\n
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:1
   |
 3 | a
@@ -379,7 +379,7 @@ error
 4 | b
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -391,7 +391,7 @@ fn char_eol_annotate_char_double_width() {
     );
 
     let expected = str![[r#"
-error
+error: 
  --> <current file>:1:2
   |
 1 | こん
@@ -401,7 +401,7 @@ error
 "#]];
 
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+    assert_data_eq!(renderer.render(snippets), expected);
 }
 
 #[test]
@@ -414,7 +414,7 @@ fn annotate_eol() {
             .annotation(Level::Error.span(1..2)), // \r
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:2
   |
 3 | a
@@ -422,7 +422,7 @@ error
 4 | b
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -435,7 +435,7 @@ fn annotate_eol2() {
             .annotation(Level::Error.span(1..3)), // \r\n
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:2
   |
 3 | a
@@ -443,7 +443,7 @@ error
 4 | b
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -456,15 +456,15 @@ fn annotate_eol3() {
             .annotation(Level::Error.span(2..3)), // \n
     );
     let expected = str![[r#"
-error
- --> file/path:3:2
+error: 
+ --> file/path:3:3
   |
 3 | a
   |  ^
 4 | b
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -477,15 +477,15 @@ fn annotate_eol4() {
             .annotation(Level::Error.span(2..2)), // \n
     );
     let expected = str![[r#"
-error
- --> file/path:3:2
+error: 
+ --> file/path:3:3
   |
 3 | a
   |  ^
 4 | b
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -497,8 +497,8 @@ fn annotate_eol_double_width() {
     );
 
     let expected = str![[r#"
-error
- --> <current file>:1:3
+error: 
+ --> <current file>:1:4
   |
 1 | こん
   |     ^
@@ -507,7 +507,7 @@ error
 "#]];
 
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+    assert_data_eq!(renderer.render(snippets), expected);
 }
 
 #[test]
@@ -520,7 +520,7 @@ fn multiline_eol_start() {
             .annotation(Level::Error.span(1..4)), // \r\nb
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:2
   |
 3 |   a
@@ -529,7 +529,7 @@ error
   | |_^
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -542,8 +542,8 @@ fn multiline_eol_start2() {
             .annotation(Level::Error.span(2..4)), // \nb
     );
     let expected = str![[r#"
-error
- --> file/path:3:2
+error: 
+ --> file/path:3:3
   |
 3 |   a
   |  __^
@@ -551,7 +551,7 @@ error
   | |_^
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -564,7 +564,7 @@ fn multiline_eol_start3() {
             .annotation(Level::Error.span(1..3)), // \nb
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:2
   |
 3 |   a
@@ -573,7 +573,7 @@ error
   | |_^
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -585,8 +585,8 @@ fn multiline_eol_start_double_width() {
     );
 
     let expected = str![[r#"
-error
- --> <current file>:1:3
+error: 
+ --> <current file>:1:4
   |
 1 |   こん
   |  _____^
@@ -596,7 +596,7 @@ error
 "#]];
 
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(snippets).to_string(), expected);
+    assert_data_eq!(renderer.render(snippets), expected);
 }
 
 #[test]
@@ -609,7 +609,7 @@ fn multiline_eol_start_eol_end() {
             .annotation(Level::Error.span(1..4)), // \nb\n
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:2
   |
 3 |   a
@@ -619,7 +619,7 @@ error
 5 |   c
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -632,8 +632,8 @@ fn multiline_eol_start_eol_end2() {
             .annotation(Level::Error.span(2..5)), // \nb\r
     );
     let expected = str![[r#"
-error
- --> file/path:3:2
+error: 
+ --> file/path:3:3
   |
 3 |   a
   |  __^
@@ -642,7 +642,7 @@ error
 5 |   c
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -655,8 +655,8 @@ fn multiline_eol_start_eol_end3() {
             .annotation(Level::Error.span(2..6)), // \nb\r\n
     );
     let expected = str![[r#"
-error
- --> file/path:3:2
+error: 
+ --> file/path:3:3
   |
 3 |   a
   |  __^
@@ -665,7 +665,7 @@ error
 5 |   c
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -678,7 +678,7 @@ fn multiline_eol_start_eof_end() {
             .annotation(Level::Error.span(1..5)), // \r\nb(EOF)
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:2
   |
 3 |   a
@@ -687,7 +687,7 @@ error
   | |__^
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -700,7 +700,7 @@ fn multiline_eol_start_eof_end_double_width() {
             .annotation(Level::Error.span(3..9)), // \r\nに(EOF)
     );
     let expected = str![[r#"
-error
+error: 
  --> file/path:3:2
   |
 3 |   ん
@@ -709,7 +709,7 @@ error
   | |___^
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -735,12 +735,12 @@ error: unused optional dependency
  --> Cargo.toml:4:1
   |
 4 | bar = { version = "0.1.0", optional = true }
-  | ^^^                        --------------- info: This should also be long but not too long
+  | ^^^                        --------------- This should also be long but not too long
   | |
   | I need this to be really long so I can test overlaps
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(false);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -770,14 +770,14 @@ error: unused optional dependency
 4 |   bar = { version = "0.1.0", optional = true }
   |  ____________________________--------------^
   | |                            |
-  | |                            info: This should also be long but not too long
+  | |                            This should also be long but not too long
 5 | | this is another line
 6 | | so is this
 7 | | bar = { version = "0.1.0", optional = true }
   | |__________________________________________^ I need this to be really long so I can test overlaps
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -812,7 +812,7 @@ error: unused optional dependency
 4 |    bar = { version = "0.1.0", optional = true }
   |   _________^__________________--------------^
   |  |         |                  |
-  |  |_________|                  info: This should also be long but not too long
+  |  |_________|                  This should also be long but not too long
   | ||
 5 | || this is another line
 6 | || so is this
@@ -822,7 +822,7 @@ error: unused optional dependency
   |                            I need this to be really long so I can test overlaps
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -863,7 +863,7 @@ error: unused optional dependency
 4 |     bar = { version = "0.1.0", optional = true }
   |   __________^__________________--------------^
   |  |          |                  |
-  |  |__________|                  info: This should also be long but not too long
+  |  |__________|                  This should also be long but not too long
   | ||
 5 | ||  this is another line
   | || ____^
@@ -876,7 +876,7 @@ error: unused optional dependency
   |   |____^ I need this to be really long so I can test overlaps
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -900,7 +900,7 @@ error: title
 4 | ddd
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -924,5 +924,5 @@ error: title
 4 | ddd
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index c2e72d3..9567265 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -30,7 +30,7 @@ error: foo
   | |_^ test
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn ends_on_col2() {
@@ -54,13 +54,12 @@ error: foo
   |
 2 |   fn foo() {
   |  __________^
-3 | |
-4 | |
+... |
 5 | |   }
   | |___^ test
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn non_nested() {
@@ -98,7 +97,7 @@ error: foo
   |       `X` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn nested() {
@@ -134,7 +133,7 @@ error: foo
   |       `Y` is a good letter too
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn different_overlap() {
@@ -173,7 +172,7 @@ error: foo
   |  |____- `Y` is a good letter too
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn triple_overlap() {
@@ -214,7 +213,7 @@ error: foo
   |        `X` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn triple_exact_overlap() {
@@ -249,13 +248,13 @@ error: foo
 4 | |   X1 Y1 Z1
 5 | |   X2 Y2 Z2
   | |    -
-  | |____|
-  |      `X` is a good letter
-  |      `Y` is a good letter too
+  | |    |
+  | |    `X` is a good letter
+  | |____`Y` is a good letter too
   |      `Z` label
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn minimum_depth() {
@@ -299,7 +298,7 @@ error: foo
   |  |_______- `Z`
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn non_overlapping() {
@@ -337,7 +336,7 @@ error: foo
   | |__________- `Y` is a good letter too
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn overlapping_start_and_end() {
@@ -377,7 +376,7 @@ error: foo
   |  |__________- `Y` is a good letter too
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn multiple_labels_primary_without_message() {
@@ -398,13 +397,13 @@ fn foo() {
 
     let expected = str![[r#"
 error: foo
- --> test.rs:3:7
+ --> test.rs:3:3
   |
 3 |   a { b { c } d }
   |   ----^^^^-^^-- `a` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn multiple_labels_secondary_without_message() {
@@ -430,7 +429,7 @@ error: foo
   |   ^^^^-------^^ `a` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn multiple_labels_primary_without_message_2() {
@@ -451,7 +450,7 @@ fn foo() {
 
     let expected = str![[r#"
 error: foo
- --> test.rs:3:7
+ --> test.rs:3:3
   |
 3 |   a { b { c } d }
   |   ----^^^^-^^--
@@ -459,7 +458,7 @@ error: foo
   |       `b` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn multiple_labels_secondary_without_message_2() {
@@ -487,7 +486,7 @@ error: foo
   |       `b` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn multiple_labels_secondary_without_message_3() {
@@ -515,7 +514,7 @@ error: foo
   |   `a` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn multiple_labels_without_message() {
@@ -541,7 +540,7 @@ error: foo
   |   ^^^^-------^^
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn multiple_labels_without_message_2() {
@@ -562,13 +561,13 @@ fn foo() {
 
     let expected = str![[r#"
 error: foo
- --> test.rs:3:7
+ --> test.rs:3:3
   |
 3 |   a { b { c } d }
   |   ----^^^^-^^--
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn multiple_labels_with_message() {
@@ -597,7 +596,7 @@ error: foo
   |   `a` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn ingle_label_with_message() {
@@ -622,7 +621,7 @@ error: foo
   |   ^^^^^^^^^^^^^ `a` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn single_label_without_message() {
@@ -647,7 +646,7 @@ error: foo
   |   ^^^^^^^^^^^^^
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn long_snippet() {
@@ -693,13 +692,15 @@ error: foo
    | ||____|
    |  |    `X` is a good letter
 5  |  | 1
+6  |  | 2
+7  |  | 3
 ...   |
 15 |  |   X2 Y2 Z2
 16 |  |   X3 Y3 Z3
    |  |__________- `Y` is a good letter too
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 #[test]
 fn long_snippet_multiple_spans() {
@@ -750,14 +751,13 @@ error: foo
 10 | || 6
 11 | ||   X2 Y2 Z2
    | ||__________- `Z` is a good letter too
-12 | |  7
 ...  |
 15 | |  10
 16 | |    X3 Y3 Z3
    | |________^ `Y` is a good letter
 "#]];
     let renderer = Renderer::plain();
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -798,7 +798,7 @@ LL | fn f(){||yield(((){),
    |       unclosed delimiter
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -863,7 +863,7 @@ fn main() {
         );
     let expected = str![[r#"
 error[E0571]: `break` with value from a `while` loop
-  --> $DIR/issue-114529-illegal-break-with-value.rs:22:9
+  --> $DIR/issue-114529-illegal-break-with-value.rs:21:5
    |
 LL |       while true {
    |       ---------- you can't `break` with a value in a `while` loop
@@ -877,11 +877,11 @@ help: use `break` on its own without a value inside this `while` loop
 LL | /         break (|| { //~ ERROR `break` with value from a `while` loop
 LL | |             let local = 9;
 LL | |         });
-   | |__________- help: break
+   | |__________- break
 "#]];
 
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -1072,24 +1072,25 @@ fn nsize() {
 error[E0277]: `V0usize` cannot be safely transmuted into `[usize; 2]`
   --> $DIR/primitive_reprs_should_have_correct_length.rs:144:44
    |
-LL |           assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
-   |                                              ^^^^^^ the size of `V0usize` is smaller than the size of `[usize; 2]`
+LL |         assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
+   |                                            ^^^^^^ the size of `V0usize` is smaller than the size of `[usize; 2]`
 note: required by a bound in `is_transmutable`
   --> $DIR/primitive_reprs_should_have_correct_length.rs:10:12
    |
 LL |       pub fn is_transmutable<Src, Dst>()
-   |              --------------- note: required by a bound in this function
+   |              --------------- required by a bound in this function
 LL |       where
 LL |           Dst: TransmuteFrom<Src, {
    |  ______________^
 LL | |             Assume {
+LL | |                 alignment: true,
+LL | |                 lifetimes: true,
 ...  |
-LL | |             }
 LL | |         }>
    | |__________^ required by this bound in `is_transmutable`
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -1139,7 +1140,7 @@ LL | ...ic [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely
    |                ^^^^^^^^^^^^^^^^^ the minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2)
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -1210,12 +1211,12 @@ fn main() {}
             );
     let expected = str![[r#"
 error[E0618]: expected function, found `{integer}`
-  --> $DIR/missing-semicolon.rs:5:13
+  --> $DIR/missing-semicolon.rs:4:9
    |
 LL |       let x = 5;
    |           - `x` has type `{integer}`
 LL |       let y = x //~ ERROR expected function
-   |               ^- help: consider using a semicolon here to finish the statement: `;`
+   |               -- help: consider using a semicolon here to finish the statement: `;`
    |  _____________|
    | |
 LL | |     () //~ ERROR expected `;`, found `}`
@@ -1223,7 +1224,7 @@ LL | |     () //~ ERROR expected `;`, found `}`
 "#]];
 
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -1319,25 +1320,26 @@ LL |   macro_rules! outer_macro {
 ...
 LL | /         macro_rules! inner_macro {
 LL | |             ($bang_macro:ident, $attr_macro:ident) => {
-...  |
+LL | |                 $bang_macro!($name);
+LL | |                 #[$attr_macro] struct $attr_struct_name {}
 LL | |             }
 LL | |         }
    | |_________^
    |
   ::: $DIR/nested-macro-rules.rs:23:5
    |
-LL |       nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct);
-   |       ---------------------------------------------------------------- in this macro invocation
+LL |     nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct);
+   |     ---------------------------------------------------------------- in this macro invocation
    = help: remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`
    = note: a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute
 note: the lint level is defined here
   --> $DIR/nested-macro-rules.rs:8:9
    |
-LL |   #![warn(non_local_definitions)]
-   |           ^^^^^^^^^^^^^^^^^^^^^
+LL | #![warn(non_local_definitions)]
+   |         ^^^^^^^^^^^^^^^^^^^^^
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -1425,10 +1427,10 @@ help: you must specify a type for this binding, like `i32`
   --> $DIR/auxiliary/macro-in-other-crate.rs:3:35
    |
 LL |     ($ident:ident) => { let $ident = 42; }
-   |                                   - help: : i32
+   |                                   - : i32
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -1475,7 +1477,7 @@ LL |         .sum::<_>() //~ ERROR type annotations needed
    |          ^^^ cannot infer type of the type parameter `S` declared on the method `sum`
 "#]];
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -1628,13 +1630,13 @@ help: ensure that all possible cases are being handled by adding a match arm wit
   --> $DIR/empty-match.rs:17:33
    |
 LL |                 _ if false => {}
-   |                                 - help: ,
+   |                                 - ,
                 _ => todo!()
 "#]];
     let renderer = Renderer::plain()
         .anonymized_line_numbers(true)
         .term_width(annotate_snippets::renderer::DEFAULT_TERM_WIDTH + 4);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }
 
 #[test]
@@ -1695,7 +1697,7 @@ error[E0038]: the trait alias `EqAlias` is not dyn compatible
 LL |     let _: &dyn EqAlias = &123;
    |                 ^^^^^^^ `EqAlias` is not dyn compatible
 note: for a trait to be dyn compatible it needs to allow building a vtable
-for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
+      for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
   --> $SRC_DIR/core/src/cmp.rs
    |
    |
@@ -1706,5 +1708,5 @@ LL | trait EqAlias = Eq;
 "#]];
 
     let renderer = Renderer::plain().anonymized_line_numbers(true);
-    assert_data_eq!(renderer.render(input).to_string(), expected);
+    assert_data_eq!(renderer.render(input), expected);
 }

From 9c0eb0cea0f61ca7cbce4a58ee107f7c8dff681b Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 19 Feb 2025 05:38:35 -0700
Subject: [PATCH 276/302] feat: Right align line numbers

---
 examples/multislice.svg                       |  2 +-
 src/renderer/mod.rs                           |  7 +++++-
 src/snippet.rs                                |  6 +++--
 tests/fixtures/color/issue_9.svg              | 22 ++++++++---------
 tests/fixtures/color/multiple_annotations.svg |  8 +++----
 tests/formatter.rs                            |  2 +-
 tests/rustc_tests.rs                          | 24 +++++++++----------
 7 files changed, 39 insertions(+), 32 deletions(-)

diff --git a/examples/multislice.svg b/examples/multislice.svg
index bbb3fc9..f0c1f65 100644
--- a/examples/multislice.svg
+++ b/examples/multislice.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 5fa9c8e..c588c98 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -534,6 +534,7 @@ impl Renderer {
                 buffer,
                 width_offset,
                 code_offset,
+                max_line_num_len,
                 margin,
             );
 
@@ -606,6 +607,7 @@ impl Renderer {
                             last_buffer_line_num,
                             width_offset,
                             code_offset,
+                            max_line_num_len,
                             margin,
                         );
 
@@ -647,6 +649,7 @@ impl Renderer {
         buffer: &mut StyledBuffer,
         width_offset: usize,
         code_offset: usize,
+        max_line_num_len: usize,
         margin: Margin,
     ) -> Vec<(usize, ElementStyle)> {
         // Draw:
@@ -685,6 +688,7 @@ impl Renderer {
             line_offset,
             width_offset,
             code_offset,
+            max_line_num_len,
             margin,
         );
 
@@ -1186,6 +1190,7 @@ impl Renderer {
         line_offset: usize,
         width_offset: usize,
         code_offset: usize,
+        max_line_num_len: usize,
         margin: Margin,
     ) {
         // Tabs are assumed to have been replaced by spaces in calling code.
@@ -1228,7 +1233,7 @@ impl Renderer {
         buffer.puts(
             line_offset,
             0,
-            &self.maybe_anonymized(line_index),
+            &format!("{:>max_line_num_len$}", self.maybe_anonymized(line_index)),
             ElementStyle::LineNumber,
         );
 
diff --git a/src/snippet.rs b/src/snippet.rs
index d9ff594..bf0ae91 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -229,10 +229,12 @@ impl Level {
 fn newline_count(body: &str) -> usize {
     #[cfg(feature = "simd")]
     {
-        memchr::memchr_iter(b'\n', body.as_bytes()).count()
+        memchr::memchr_iter(b'\n', body.as_bytes())
+            .count()
+            .saturating_sub(1)
     }
     #[cfg(not(feature = "simd"))]
     {
-        body.lines().count()
+        body.lines().count().saturating_sub(1)
     }
 }
diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg
index 4e3bf1e..6854bc3 100644
--- a/tests/fixtures/color/issue_9.svg
+++ b/tests/fixtures/color/issue_9.svg
@@ -1,4 +1,4 @@
-<svg width="919px" height="218px" xmlns="http://www.w3.org/2000/svg">
+<svg width="911px" height="218px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -22,25 +22,25 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
+    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
+    <tspan x="10px" y="154px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="172px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
+    <tspan x="10px" y="208px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg
index 96101a5..2c5c4a8 100644
--- a/tests/fixtures/color/multiple_annotations.svg
+++ b/tests/fixtures/color/multiple_annotations.svg
@@ -23,15 +23,15 @@
 </tspan>
     <tspan x="10px" y="46px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">96</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
+    <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &amp;mut Vec&lt;String&gt;, main_annotation: Option&lt;&amp;Annotation&gt;) {</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">97</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     if let Some(annotation) = main_annotation {</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     if let Some(annotation) = main_annotation {</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                 </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Variable defined here</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">98</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         result.push(format_title_line(</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         result.push(format_title_line(</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">99</tspan><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             &amp;annotation.annotation_type,</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             &amp;annotation.annotation_type,</tspan>
 </tspan>
     <tspan x="10px" y="154px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>              </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced here</tspan>
 </tspan>
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 3e9246a..df5d081 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -149,7 +149,7 @@ error:
      |
     ::: file2.rs
      |
-2    | This is slice 2
+   2 | This is slice 2
 "#]];
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input), expected);
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index 9567265..f8b36d8 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -685,15 +685,15 @@ fn foo() {
 error: foo
   --> test.rs:3:6
    |
-3  |      X0 Y0 Z0
+ 3 |      X0 Y0 Z0
    |  _______^
-4  | |    X1 Y1 Z1
+ 4 | |    X1 Y1 Z1
    | | ____^____-
    | ||____|
    |  |    `X` is a good letter
-5  |  | 1
-6  |  | 2
-7  |  | 3
+ 5 |  | 1
+ 6 |  | 2
+ 7 |  | 3
 ...   |
 15 |  |   X2 Y2 Z2
 16 |  |   X3 Y3 Z3
@@ -739,15 +739,15 @@ fn foo() {
 error: foo
   --> test.rs:3:6
    |
-3  |      X0 Y0 Z0
+ 3 |      X0 Y0 Z0
    |  _______^
-4  | |  1
-5  | |  2
-6  | |  3
-7  | |    X1 Y1 Z1
+ 4 | |  1
+ 5 | |  2
+ 6 | |  3
+ 7 | |    X1 Y1 Z1
    | | _________-
-8  | || 4
-9  | || 5
+ 8 | || 4
+ 9 | || 5
 10 | || 6
 11 | ||   X2 Y2 Z2
    | ||__________- `Z` is a good letter too

From e6fcf0b33370b0a363a1c0ee19dbb2bdca170159 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sun, 9 Feb 2025 12:41:57 -0700
Subject: [PATCH 277/302] feat!: Move to new API

---
 benches/bench.rs                              |  52 +-
 examples/expected_type.rs                     |  31 +-
 examples/expected_type.svg                    |   2 +-
 examples/footer.rs                            |  23 +-
 examples/footer.svg                           |  11 +-
 examples/format.rs                            |  32 +-
 examples/format.svg                           |   5 +-
 examples/multislice.rs                        |  27 +-
 examples/multislice.svg                       |   2 +-
 src/lib.rs                                    |   1 +
 src/renderer/mod.rs                           | 637 ++++++++----
 src/renderer/source_map.rs                    |  36 +-
 src/renderer/styled_buffer.rs                 |  10 +-
 src/renderer/stylesheet.rs                    |   2 +
 src/snippet.rs                                | 326 +++---
 tests/fixtures/color/ann_eof.toml             |  10 +-
 tests/fixtures/color/ann_insertion.toml       |  10 +-
 tests/fixtures/color/ann_multiline.toml       |  10 +-
 tests/fixtures/color/ann_multiline2.toml      |  10 +-
 tests/fixtures/color/ann_removed_nl.toml      |  10 +-
 .../color/ensure-emoji-highlight-width.toml   |  10 +-
 tests/fixtures/color/fold_ann_multiline.svg   |   5 +-
 tests/fixtures/color/fold_ann_multiline.toml  |  15 +-
 tests/fixtures/color/fold_bad_origin_line.svg |   3 +-
 .../fixtures/color/fold_bad_origin_line.toml  |  10 +-
 tests/fixtures/color/fold_leading.svg         |   8 +-
 tests/fixtures/color/fold_leading.toml        |  10 +-
 tests/fixtures/color/fold_trailing.toml       |  10 +-
 tests/fixtures/color/issue_9.svg              |  25 +-
 tests/fixtures/color/issue_9.toml             |  21 +-
 .../fixtures/color/multiple_annotations.toml  |  15 +-
 tests/fixtures/color/simple.svg               |   5 +-
 tests/fixtures/color/simple.toml              |  11 +-
 tests/fixtures/color/strip_line.toml          |   7 +-
 tests/fixtures/color/strip_line_char.toml     |   9 +-
 tests/fixtures/color/strip_line_non_ws.toml   |  11 +-
 tests/fixtures/deserialize.rs                 |  84 +-
 tests/formatter.rs                            | 542 +++++-----
 tests/rustc_tests.rs                          | 931 +++++++++++-------
 39 files changed, 1834 insertions(+), 1145 deletions(-)

diff --git a/benches/bench.rs b/benches/bench.rs
index ed4e82c..01364af 100644
--- a/benches/bench.rs
+++ b/benches/bench.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Level, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 #[divan::bench]
 fn simple() -> String {
@@ -24,20 +24,22 @@ fn simple() -> String {
             _ => continue,
         }
     }"#;
-    let message = Level::Error.title("mismatched types").id("E0308").snippet(
-        Snippet::source(source)
-            .line_start(51)
-            .origin("src/format.rs")
-            .annotation(
-                Level::Warning
-                    .span(5..19)
-                    .label("expected `Option<String>` because of return type"),
-            )
-            .annotation(
-                Level::Error
-                    .span(26..724)
-                    .label("expected enum `std::option::Option`"),
-            ),
+    let message = Level::Error.message("mismatched types").id("E0308").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(51)
+                .origin("src/format.rs")
+                .annotation(
+                    AnnotationKind::Context
+                        .span(5..19)
+                        .label("expected `Option<String>` because of return type"),
+                )
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(26..724)
+                        .label("expected enum `std::option::Option`"),
+                ),
+        ),
     );
 
     let renderer = Renderer::plain();
@@ -67,15 +69,17 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) {
             (input, span)
         })
         .bench_values(|(input, span)| {
-            let message = Level::Error.title("mismatched types").id("E0308").snippet(
-                Snippet::source(&input)
-                    .fold(true)
-                    .origin("src/format.rs")
-                    .annotation(
-                        Level::Warning
-                            .span(span)
-                            .label("expected `Option<String>` because of return type"),
-                    ),
+            let message = Level::Error.message("mismatched types").id("E0308").group(
+                Group::new().element(
+                    Snippet::source(&input)
+                        .fold(true)
+                        .origin("src/format.rs")
+                        .annotation(
+                            AnnotationKind::Context
+                                .span(span)
+                                .label("expected `Option<String>` because of return type"),
+                        ),
+                ),
             );
 
             let renderer = Renderer::plain();
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 0184dee..f61999d 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,22 +1,27 @@
-use annotate_snippets::{Level, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 fn main() {
     let source = r#"                annotations: vec![SourceAnnotation {
                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
                     ,
                 range: <22, 25>,"#;
-    let message = Level::Error.title("expected type, found `22`").snippet(
-        Snippet::source(source)
-            .line_start(26)
-            .origin("examples/footer.rs")
-            .fold(true)
-            .annotation(
-                Level::Error
-                    .span(193..195)
-                    .label("expected struct `annotate_snippets::snippet::Slice`, found reference"),
-            )
-            .annotation(Level::Info.span(34..50).label("while parsing this struct")),
-    );
+    let message =
+        Level::Error.message("expected type, found `22`").group(
+            Group::new().element(
+                Snippet::source(source)
+                    .line_start(26)
+                    .origin("examples/footer.rs")
+                    .fold(true)
+                    .annotation(AnnotationKind::Primary.span(193..195).label(
+                        "expected struct `annotate_snippets::snippet::Slice`, found reference",
+                    ))
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(34..50)
+                            .label("while parsing this struct"),
+                    ),
+            ),
+        );
 
     let renderer = Renderer::styled();
     anstream::println!("{}", renderer.render(message));
diff --git a/examples/expected_type.svg b/examples/expected_type.svg
index 749fc3a..7c1b073 100644
--- a/examples/expected_type.svg
+++ b/examples/expected_type.svg
@@ -21,7 +21,7 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected type, found `22`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>examples/footer.rs:26:35</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>examples/footer.rs:29:25</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/examples/footer.rs b/examples/footer.rs
index 3580905..29b27c1 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,21 +1,22 @@
-use annotate_snippets::{Level, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 fn main() {
-    let message =
-        Level::Error
-            .title("mismatched types")
-            .id("E0308")
-            .snippet(
+    let message = Level::Error
+        .message("mismatched types")
+        .id("E0308")
+        .group(
+            Group::new().element(
                 Snippet::source("        slices: vec![\"A\",")
                     .line_start(13)
                     .origin("src/multislice.rs")
-                    .annotation(Level::Error.span(21..24).label(
+                    .annotation(AnnotationKind::Primary.span(21..24).label(
                         "expected struct `annotate_snippets::snippet::Slice`, found reference",
                     )),
-            )
-            .footer(Level::Note.title(
-                "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
-            ));
+            ),
+        )
+        .group(Group::new().element(Level::Note.title(
+            "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
+        )));
 
     let renderer = Renderer::styled();
     anstream::println!("{}", renderer.render(message));
diff --git a/examples/footer.svg b/examples/footer.svg
index e3bb892..e24ba5f 100644
--- a/examples/footer.svg
+++ b/examples/footer.svg
@@ -1,8 +1,9 @@
-<svg width="844px" height="164px" xmlns="http://www.w3.org/2000/svg">
+<svg width="844px" height="182px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
+    .fg-bright-green { fill: #55FF55 }
     .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
@@ -29,11 +30,13 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                      </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>              found type: `__&amp;__snippet::Annotation`</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-green bold">note</tspan><tspan class="bold">: expected type: `snippet::Annotation`</tspan>
 </tspan>
-    <tspan x="10px" y="154px">
+    <tspan x="10px" y="154px"><tspan class="bold">         found type: `__&amp;__snippet::Annotation`</tspan>
+</tspan>
+    <tspan x="10px" y="172px">
 </tspan>
   </text>
 
diff --git a/examples/format.rs b/examples/format.rs
index 1606777..df6f927 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Level, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 fn main() {
     let source = r#") -> Option<String> {
@@ -23,20 +23,22 @@ fn main() {
             _ => continue,
         }
     }"#;
-    let message = Level::Error.title("mismatched types").id("E0308").snippet(
-        Snippet::source(source)
-            .line_start(51)
-            .origin("src/format.rs")
-            .annotation(
-                Level::Warning
-                    .span(5..19)
-                    .label("expected `Option<String>` because of return type"),
-            )
-            .annotation(
-                Level::Error
-                    .span(26..724)
-                    .label("expected enum `std::option::Option`"),
-            ),
+    let message = Level::Error.message("mismatched types").id("E0308").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(51)
+                .origin("src/format.rs")
+                .annotation(
+                    AnnotationKind::Context
+                        .span(5..19)
+                        .label("expected `Option<String>` because of return type"),
+                )
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(26..724)
+                        .label("expected enum `std::option::Option`"),
+                ),
+        ),
     );
 
     let renderer = Renderer::styled();
diff --git a/examples/format.svg b/examples/format.svg
index f9bf80e..e4a4042 100644
--- a/examples/format.svg
+++ b/examples/format.svg
@@ -4,7 +4,6 @@
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
     .fg-bright-red { fill: #FF5555 }
-    .fg-yellow { fill: #AA5500 }
     .container {
       padding: 0 10px;
       line-height: 18px;
@@ -22,13 +21,13 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format.rs:51:6</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format.rs:52:5</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>   ) -&gt; Option&lt;String&gt; {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>        </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option&lt;String&gt;` because of return type</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>        </tspan><tspan class="fg-bright-blue bold">--------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected `Option&lt;String&gt;` because of return type</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan>     for ann in annotations {</tspan>
 </tspan>
diff --git a/examples/multislice.rs b/examples/multislice.rs
index ea31bbd..d1ad72a 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,18 +1,19 @@
-use annotate_snippets::{Level, Renderer, Snippet};
+use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet};
 
 fn main() {
-    let message = Level::Error
-        .title("mismatched types")
-        .snippet(
-            Snippet::source("Foo")
-                .line_start(51)
-                .origin("src/format.rs"),
-        )
-        .snippet(
-            Snippet::source("Faa")
-                .line_start(129)
-                .origin("src/display.rs"),
-        );
+    let message = Level::Error.message("mismatched types").group(
+        Group::new()
+            .element(
+                Snippet::<Annotation<'_>>::source("Foo")
+                    .line_start(51)
+                    .origin("src/format.rs"),
+            )
+            .element(
+                Snippet::<Annotation<'_>>::source("Faa")
+                    .line_start(129)
+                    .origin("src/display.rs"),
+            ),
+    );
 
     let renderer = Renderer::styled();
     anstream::println!("{}", renderer.render(message));
diff --git a/examples/multislice.svg b/examples/multislice.svg
index f0c1f65..5bc0145 100644
--- a/examples/multislice.svg
+++ b/examples/multislice.svg
@@ -29,7 +29,7 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs:129</tspan>
 </tspan>
     <tspan x="10px" y="136px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/src/lib.rs b/src/lib.rs
index ed9b3f8..533e8f7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -47,4 +47,5 @@ mod snippet;
 
 #[doc(inline)]
 pub use renderer::Renderer;
+pub use snippet::ColumnSeparator;
 pub use snippet::*;
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index c588c98..246afb6 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -4,13 +4,36 @@
 //!
 //! # Example
 //! ```
-//! use annotate_snippets::{Renderer, Snippet, Level};
-//! let snippet = Level::Error.title("mismatched types")
-//!     .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs"))
-//!     .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
+//! use annotate_snippets::*;
 //!
-//!  let renderer = Renderer::styled();
-//!  println!("{}", renderer.render(snippet));
+//! let source = r#"
+//! use baz::zed::bar;
+//!
+//! mod baz {}
+//! mod zed {
+//!     pub fn bar() { println!("bar3"); }
+//! }
+//! fn main() {
+//!     bar();
+//! }
+//! "#;
+//! Level::Error
+//!     .message("unresolved import `baz::zed`")
+//!     .id("E0432")
+//!     .group(
+//!         Group::new().element(
+//!             Snippet::source(source)
+//!                 .origin("temp.rs")
+//!                 .line_start(1)
+//!                 .fold(true)
+//!                 .annotation(
+//!                     AnnotationKind::Primary
+//!                         .span(10..13)
+//!                          .label("could not find `zed` in `baz`"),
+//!                 )
+//!         )
+//!     );
+//! ```
 
 mod margin;
 mod source_map;
@@ -19,14 +42,14 @@ pub(crate) mod stylesheet;
 
 use crate::renderer::source_map::{AnnotatedLineInfo, Loc, SourceMap};
 use crate::renderer::styled_buffer::StyledBuffer;
-use crate::snippet::Message;
-use crate::{Level, Snippet};
+use crate::{Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Snippet, Title};
 pub use anstyle::*;
 use indexmap::IndexMap;
 use margin::Margin;
 use rustc_hash::{FxHashMap, FxHasher};
 use std::borrow::Cow;
 use std::cmp::{max, min, Ordering, Reverse};
+use std::collections::VecDeque;
 use std::hash::BuildHasherDefault;
 use stylesheet::Stylesheet;
 
@@ -84,6 +107,7 @@ impl Renderer {
                 }
                 .effects(Effects::BOLD),
                 none: Style::new(),
+                context: BRIGHT_BLUE.effects(Effects::BOLD),
             },
             ..Self::plain()
         }
@@ -164,7 +188,7 @@ impl Renderer {
 }
 
 impl Renderer {
-    pub fn render(&self, message: Message<'_>) -> String {
+    pub fn render(&self, mut message: Message<'_>) -> String {
         let mut buffer = StyledBuffer::new();
         let max_line_num_len = if self.anonymized_line_numbers {
             ANONYMIZED_LINE_NUM.len()
@@ -172,10 +196,20 @@ impl Renderer {
             let n = message.max_line_number();
             num_decimal_digits(n)
         };
+        let title = message.groups.remove(0).elements.remove(0);
+        let level = if let Element::Title(title) = &title {
+            title.level
+        } else {
+            panic!("Expected a title as the first element of the message")
+        };
+        if let Some(first) = message.groups.first_mut() {
+            first.elements.insert(0, title);
+        } else {
+            message.groups.push(Group::new().element(title));
+        }
+        self.render_message(&mut buffer, message, max_line_num_len);
 
-        self.render_message(&mut buffer, message, max_line_num_len, false);
-
-        buffer.render(&self.stylesheet).unwrap()
+        buffer.render(level, &self.stylesheet).unwrap()
     }
 
     fn render_message(
@@ -183,67 +217,200 @@ impl Renderer {
         buffer: &mut StyledBuffer,
         message: Message<'_>,
         max_line_num_len: usize,
-        is_secondary: bool,
     ) {
-        self.render_title(buffer, &message, max_line_num_len, is_secondary);
-
-        let primary_origin = message.snippets.first().and_then(|s| s.origin);
-
-        for snippet in message.snippets {
-            let source_map = SourceMap::new(snippet.source, snippet.line_start);
-            self.render_snippet_annotations(
-                buffer,
-                max_line_num_len,
-                &snippet,
-                primary_origin,
-                &source_map,
-            );
-        }
+        let group_len = message.groups.len();
+        for (g, group) in message.groups.into_iter().enumerate() {
+            let primary_origin = group
+                .elements
+                .iter()
+                .find_map(|s| match &s {
+                    Element::Cause(cause) => {
+                        if cause.markers.iter().any(|m| m.kind.is_primary()) {
+                            Some(cause.origin)
+                        } else {
+                            None
+                        }
+                    }
+                    Element::Origin(origin) => {
+                        if origin.primary {
+                            Some(Some(origin.origin))
+                        } else {
+                            None
+                        }
+                    }
+                    _ => None,
+                })
+                .unwrap_or(
+                    group
+                        .elements
+                        .iter()
+                        .find_map(|s| match &s {
+                            Element::Cause(cause) => Some(cause.origin),
+                            Element::Origin(origin) => Some(Some(origin.origin)),
+                            _ => None,
+                        })
+                        .unwrap_or_default(),
+                );
+            let mut source_map_annotated_lines = VecDeque::new();
+            let mut max_depth = 0;
+            for e in &group.elements {
+                if let Element::Cause(cause) = e {
+                    let source_map = SourceMap::new(cause.source, cause.line_start);
+                    let (depth, annotated_lines) =
+                        source_map.annotated_lines(cause.markers.clone(), cause.fold);
+                    max_depth = max(max_depth, depth);
+                    source_map_annotated_lines.push_back((source_map, annotated_lines));
+                }
+            }
+            let mut message_iter = group.elements.iter().enumerate().peekable();
+            while let Some((i, section)) = message_iter.next() {
+                let peek = message_iter.peek().map(|(_, s)| s).copied();
+                match &section {
+                    Element::Title(title) => {
+                        self.render_title(
+                            buffer,
+                            title,
+                            peek,
+                            max_line_num_len,
+                            if i == 0 { false } else { !title.primary },
+                            message.id.as_ref().and_then(|id| {
+                                if g == 0 && i == 0 {
+                                    Some(id)
+                                } else {
+                                    None
+                                }
+                            }),
+                        );
+                    }
+                    Element::Cause(cause) => {
+                        if let Some((source_map, annotated_lines)) =
+                            source_map_annotated_lines.pop_front()
+                        {
+                            self.render_snippet_annotations(
+                                buffer,
+                                max_line_num_len,
+                                cause,
+                                primary_origin,
+                                &source_map,
+                                &annotated_lines,
+                                max_depth,
+                            );
 
-        for footer in message.footer {
-            self.render_message(buffer, footer, max_line_num_len, true);
+                            if g == 0 && group_len > 1 {
+                                if matches!(peek, Some(Element::Title(level)) if level.level != Level::None)
+                                {
+                                    self.draw_col_separator_no_space(
+                                        buffer,
+                                        buffer.num_lines(),
+                                        max_line_num_len + 1,
+                                    );
+                                // We want to draw the separator when it is
+                                // requested, or when it is the last element
+                                } else if peek.is_none() {
+                                    self.draw_col_separator_no_space_with_style(
+                                        buffer,
+                                        '|',
+                                        buffer.num_lines(),
+                                        max_line_num_len + 1,
+                                        ElementStyle::LineNumber,
+                                    );
+                                }
+                            }
+                        }
+                    }
+                    Element::Origin(origin) => {
+                        self.render_origin(buffer, max_line_num_len, origin);
+                    }
+                    Element::ColumnSeparator(_) => {
+                        self.draw_col_separator_no_space(
+                            buffer,
+                            buffer.num_lines(),
+                            max_line_num_len + 1,
+                        );
+                    }
+                }
+                if g == 0
+                    && (matches!(section, Element::Origin(_))
+                        || (matches!(section, Element::Title(_)) && i == 0)
+                        || matches!(section, Element::Title(level) if level.level == Level::None))
+                {
+                    if peek.is_none() && group_len > 1 {
+                        self.draw_col_separator_no_space_with_style(
+                            buffer,
+                            '|',
+                            buffer.num_lines(),
+                            max_line_num_len + 1,
+                            ElementStyle::LineNumber,
+                        );
+                    } else if matches!(peek, Some(Element::Title(level)) if level.level != Level::None)
+                    {
+                        self.draw_col_separator_no_space(
+                            buffer,
+                            buffer.num_lines(),
+                            max_line_num_len + 1,
+                        );
+                    }
+                }
+            }
         }
     }
 
     fn render_title(
         &self,
         buffer: &mut StyledBuffer,
-        message: &Message<'_>,
+        title: &Title<'_>,
+        next_section: Option<&Element<'_>>,
         max_line_num_len: usize,
         is_secondary: bool,
+        id: Option<&&str>,
     ) {
         let line_offset = buffer.num_lines();
-        if !message.has_primary_spans() && !message.has_span_labels() && is_secondary {
+
+        let (has_primary_spans, has_span_labels) =
+            next_section.map_or((false, false), |s| match s {
+                Element::Title(_) | Element::ColumnSeparator(_) => (false, false),
+                Element::Cause(cause) => (
+                    cause.markers.iter().any(|m| m.kind.is_primary()),
+                    cause.markers.iter().any(|m| m.label.is_some()),
+                ),
+                Element::Origin(_) => (false, true),
+            });
+
+        if !has_primary_spans && !has_span_labels && is_secondary {
             // This is a secondary message with no span info
             for _ in 0..max_line_num_len {
                 buffer.prepend(line_offset, " ", ElementStyle::NoStyle);
             }
-            buffer.puts(
-                line_offset,
-                max_line_num_len + 1,
-                "= ",
-                ElementStyle::LineNumber,
-            );
-            buffer.append(
-                line_offset,
-                message.level.as_str(),
-                ElementStyle::MainHeaderMsg,
-            );
-            buffer.append(line_offset, ": ", ElementStyle::NoStyle);
-            self.msgs_to_buffer(buffer, message.title, max_line_num_len, "note", None);
+            if title.level != Level::None {
+                buffer.puts(
+                    line_offset,
+                    max_line_num_len + 1,
+                    "= ",
+                    ElementStyle::LineNumber,
+                );
+                buffer.append(
+                    line_offset,
+                    title.level.as_str(),
+                    ElementStyle::MainHeaderMsg,
+                );
+                buffer.append(line_offset, ": ", ElementStyle::NoStyle);
+            }
+            self.msgs_to_buffer(buffer, title.title, max_line_num_len, "note", None);
         } else {
             let mut label_width = 0;
 
-            buffer.append(
-                line_offset,
-                message.level.as_str(),
-                ElementStyle::Level(message.level),
-            );
-            label_width += message.level.as_str().len();
-            if let Some(id) = message.id {
-                buffer.append(line_offset, "[", ElementStyle::Level(message.level));
-                buffer.append(line_offset, id, ElementStyle::Level(message.level));
-                buffer.append(line_offset, "]", ElementStyle::Level(message.level));
+            if title.level != Level::None {
+                buffer.append(
+                    line_offset,
+                    title.level.as_str(),
+                    ElementStyle::Level(title.level),
+                );
+            }
+            label_width += title.level.as_str().len();
+            if let Some(id) = id {
+                buffer.append(line_offset, "[", ElementStyle::Level(title.level));
+                buffer.append(line_offset, id, ElementStyle::Level(title.level));
+                buffer.append(line_offset, "]", ElementStyle::Level(title.level));
                 label_width += 2 + id.len();
             }
             let header_style = if is_secondary {
@@ -251,10 +418,12 @@ impl Renderer {
             } else {
                 ElementStyle::MainHeaderMsg
             };
-            buffer.append(line_offset, ": ", header_style);
-            label_width += 2;
-            if !message.title.is_empty() {
-                for (line, text) in normalize_whitespace(message.title).lines().enumerate() {
+            if title.level != Level::None {
+                buffer.append(line_offset, ": ", header_style);
+                label_width += 2;
+            }
+            if !title.title.is_empty() {
+                for (line, text) in normalize_whitespace(title.title).lines().enumerate() {
                     buffer.append(
                         line_offset + line,
                         &format!(
@@ -343,51 +512,106 @@ impl Renderer {
         line_number
     }
 
+    fn render_origin(
+        &self,
+        buffer: &mut StyledBuffer,
+        max_line_num_len: usize,
+        origin: &Origin<'_>,
+    ) {
+        let buffer_msg_line_offset = buffer.num_lines();
+        if origin.primary {
+            buffer.prepend(
+                buffer_msg_line_offset,
+                self.file_start(),
+                ElementStyle::LineNumber,
+            );
+        } else {
+            // if !origin.standalone {
+            //     // Add spacing line, as shown:
+            //     //   --> $DIR/file:54:15
+            //     //    |
+            //     // LL |         code
+            //     //    |         ^^^^
+            //     //    | (<- It prints *this* line)
+            //     //   ::: $DIR/other_file.rs:15:5
+            //     //    |
+            //     // LL |     code
+            //     //    |     ----
+            //     self.draw_col_separator_no_space(
+            //         buffer,
+            //         buffer_msg_line_offset,
+            //         max_line_num_len + 1,
+            //     );
+            //
+            //     buffer_msg_line_offset += 1;
+            // }
+            // Then, the secondary file indicator
+            buffer.prepend(
+                buffer_msg_line_offset,
+                self.secondary_file_start(),
+                ElementStyle::LineNumber,
+            );
+        }
+
+        let str = match (&origin.line, &origin.char_column) {
+            (Some(line), Some(col)) => {
+                format!("{}:{}:{}", origin.origin, line, col)
+            }
+            (Some(line), None) => format!("{}:{}", origin.origin, line),
+            _ => origin.origin.to_owned(),
+        };
+        buffer.append(buffer_msg_line_offset, &str, ElementStyle::LineAndColumn);
+        for _ in 0..max_line_num_len {
+            buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle);
+        }
+
+        if let Some(label) = &origin.label {
+            self.draw_col_separator_no_space(
+                buffer,
+                buffer_msg_line_offset + 1,
+                max_line_num_len + 1,
+            );
+            let title = Level::Note.title(label);
+            self.render_title(buffer, &title, None, max_line_num_len, true, None);
+        }
+    }
+
+    #[allow(clippy::too_many_arguments)]
     fn render_snippet_annotations(
         &self,
         buffer: &mut StyledBuffer,
         max_line_num_len: usize,
-        snippet: &Snippet<'_>,
+        snippet: &Snippet<'_, Annotation<'_>>,
         primary_origin: Option<&str>,
         sm: &SourceMap<'_>,
+        annotated_lines: &[AnnotatedLineInfo<'_>],
+        multiline_depth: usize,
     ) {
-        let annotated_lines = sm.annotated_lines(snippet.annotations.clone(), snippet.fold);
-        // print out the span location and spacer before we print the annotated source
-        // to do this, we need to know if this span will be primary
-        let is_primary = primary_origin == snippet.origin;
-
-        if is_primary {
-            if let Some(origin) = snippet.origin {
-                // remember where we are in the output buffer for easy reference
-                let buffer_msg_line_offset = buffer.num_lines();
-
-                buffer.prepend(
-                    buffer_msg_line_offset,
-                    self.file_start(),
-                    ElementStyle::LineNumber,
-                );
-                let loc = if let Some(first_line) =
-                    annotated_lines.iter().find(|l| !l.annotations.is_empty())
+        if let Some(origin) = snippet.origin {
+            let mut origin = Origin::new(origin);
+            // print out the span location and spacer before we print the annotated source
+            // to do this, we need to know if this span will be primary
+            let is_primary = primary_origin == Some(origin.origin);
+
+            if is_primary {
+                origin.primary = true;
+                if let Some(primary_line) = annotated_lines
+                    .iter()
+                    .find(|l| l.annotations.iter().any(LineAnnotation::is_primary))
+                    .or(annotated_lines.iter().find(|l| !l.annotations.is_empty()))
                 {
-                    let col = if let Some(first_annotation) = first_line.annotations.first() {
-                        format!(":{}", first_annotation.start.char + 1)
-                    } else {
-                        String::new()
-                    };
-                    format!("{}:{}{}", origin, first_line.line_index, col)
-                } else {
-                    origin.to_owned()
-                };
-                buffer.append(buffer_msg_line_offset, &loc, ElementStyle::LineAndColumn);
-                for _ in 0..max_line_num_len {
-                    buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle);
+                    origin.line = Some(primary_line.line_index);
+                    if let Some(first_annotation) = primary_line
+                        .annotations
+                        .iter()
+                        .find(|a| a.is_primary())
+                        .or(primary_line.annotations.first())
+                    {
+                        origin.char_column = Some(first_annotation.start.char + 1);
+                    }
                 }
-            }
-        } else {
-            if let Some(origin) = snippet.origin {
-                // remember where we are in the output buffer for easy reference
+            } else {
                 let buffer_msg_line_offset = buffer.num_lines();
-
                 // Add spacing line, as shown:
                 //   --> $DIR/file:54:15
                 //    |
@@ -403,34 +627,14 @@ impl Renderer {
                     buffer_msg_line_offset,
                     max_line_num_len + 1,
                 );
-
-                // Then, the secondary file indicator
-                buffer.prepend(
-                    buffer_msg_line_offset + 1,
-                    self.secondary_file_start(),
-                    ElementStyle::LineNumber,
-                );
-                let loc = if let Some(first_line) =
-                    annotated_lines.iter().find(|l| !l.annotations.is_empty())
-                {
-                    let col = if let Some(first_annotation) = first_line.annotations.first() {
-                        format!(":{}", first_annotation.start.char + 1)
-                    } else {
-                        String::new()
-                    };
-                    format!("{}:{}{}", origin, first_line.line_index, col)
-                } else {
-                    origin.to_owned()
-                };
-                buffer.append(
-                    buffer_msg_line_offset + 1,
-                    &loc,
-                    ElementStyle::LineAndColumn,
-                );
-                for _ in 0..max_line_num_len {
-                    buffer.prepend(buffer_msg_line_offset + 1, " ", ElementStyle::NoStyle);
+                if let Some(first_line) = annotated_lines.first() {
+                    origin.line = Some(first_line.line_index);
+                    if let Some(first_annotation) = first_line.annotations.first() {
+                        origin.char_column = Some(first_annotation.start.char + 1);
+                    }
                 }
             }
+            self.render_origin(buffer, max_line_num_len, &origin);
         }
 
         // Put in the spacer between the location and annotated source
@@ -442,7 +646,7 @@ impl Renderer {
 
         // Get the left-side margin to remove it
         let mut whitespace_margin = usize::MAX;
-        for line_info in &annotated_lines {
+        for line_info in annotated_lines {
             // Whitespace can only be removed (aka considered leading)
             // if the lexer considers it whitespace.
             // non-rustc_lexer::is_whitespace() chars are reported as an
@@ -470,7 +674,7 @@ impl Renderer {
 
         // Left-most column any visible span points at.
         let mut span_left_margin = usize::MAX;
-        for line_info in &annotated_lines {
+        for line_info in annotated_lines {
             for ann in &line_info.annotations {
                 span_left_margin = min(span_left_margin, ann.start.display);
                 span_left_margin = min(span_left_margin, ann.end.display);
@@ -484,7 +688,7 @@ impl Renderer {
         let mut span_right_margin = 0;
         let mut label_right_margin = 0;
         let mut max_line_len = 0;
-        for line_info in &annotated_lines {
+        for line_info in annotated_lines {
             max_line_len = max(max_line_len, line_info.line.len());
             for ann in &line_info.annotations {
                 span_right_margin = max(span_right_margin, ann.start.display);
@@ -494,19 +698,6 @@ impl Renderer {
                 label_right_margin = max(label_right_margin, ann.end.display + label_right);
             }
         }
-        let multiline_depth = annotated_lines.iter().fold(0, |acc, line_info| {
-            line_info.annotations.iter().fold(acc, |acc2, ann| {
-                max(
-                    acc2,
-                    match ann.annotation_type {
-                        LineAnnotationType::Singleline => 0,
-                        LineAnnotationType::MultilineStart(depth) => depth,
-                        LineAnnotationType::MultilineEnd(depth) => depth,
-                        LineAnnotationType::MultilineLine(depth) => depth,
-                    },
-                )
-            })
-        });
         let width_offset = 3 + max_line_num_len;
         let code_offset = if multiline_depth == 0 {
             width_offset
@@ -586,7 +777,11 @@ impl Renderer {
                                         last_buffer_line_num,
                                         width_offset,
                                         pos,
-                                        ElementStyle::Level(ann.level),
+                                        if ann.is_primary() {
+                                            ElementStyle::UnderlinePrimary
+                                        } else {
+                                            ElementStyle::UnderlineSecondary
+                                        },
                                     );
                                 }
                             }
@@ -629,7 +824,11 @@ impl Renderer {
                                         last_buffer_line_num,
                                         width_offset,
                                         pos,
-                                        ElementStyle::Level(ann.level),
+                                        if ann.is_primary() {
+                                            ElementStyle::UnderlinePrimary
+                                        } else {
+                                            ElementStyle::UnderlineSecondary
+                                        },
                                     );
                                 }
                             }
@@ -643,6 +842,7 @@ impl Renderer {
         }
     }
 
+    #[allow(clippy::too_many_arguments)]
     fn render_source_line(
         &self,
         line_info: &AnnotatedLineInfo<'_>,
@@ -718,9 +918,10 @@ impl Renderer {
                     .take(ann.start.display)
                     .all(char::is_whitespace)
                 {
-                    let style = ElementStyle::Level(ann.level);
-                    annotations.push((depth, style));
-                    buffer_ops.push((line_offset, width_offset + depth - 1, '/', style));
+                    let uline = self.underline(ann.is_primary());
+                    let chr = uline.multiline_whole_line;
+                    annotations.push((depth, uline.style));
+                    buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style));
                 } else {
                     short_start = false;
                     break;
@@ -961,18 +1162,18 @@ impl Renderer {
         // 4 |   }
         //   |  _
         for &(pos, annotation) in &annotations_position {
-            let style = ElementStyle::Level(annotation.level);
+            let underline = self.underline(annotation.is_primary());
             let pos = pos + 1;
             match annotation.annotation_type {
                 LineAnnotationType::MultilineStart(depth)
                 | LineAnnotationType::MultilineEnd(depth) => {
                     self.draw_range(
                         buffer,
-                        '_', // underline.multiline_horizontal,
+                        underline.multiline_horizontal,
                         line_offset + pos,
                         width_offset + depth,
                         (code_offset + annotation.start.display).saturating_sub(left),
-                        style,
+                        underline.style,
                     );
                 }
                 _ => {}
@@ -991,7 +1192,7 @@ impl Renderer {
         // 4 | | }
         //   | |_
         for &(pos, annotation) in &annotations_position {
-            let style = ElementStyle::Level(annotation.level);
+            let underline = self.underline(annotation.is_primary());
             let pos = pos + 1;
 
             if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
@@ -1000,18 +1201,18 @@ impl Renderer {
                         p,
                         (code_offset + annotation.start.display).saturating_sub(left),
                         match annotation.annotation_type {
-                            LineAnnotationType::MultilineLine(_) => '|', // underline.multiline_vertical,
-                            _ => '|', // underline.vertical_text_line,
+                            LineAnnotationType::MultilineLine(_) => underline.multiline_vertical,
+                            _ => underline.vertical_text_line,
                         },
-                        style,
+                        underline.style,
                     );
                 }
                 if let LineAnnotationType::MultilineStart(_) = annotation.annotation_type {
                     buffer.putc(
                         line_offset + pos,
                         (code_offset + annotation.start.display).saturating_sub(left),
-                        '|', // underline.bottom_right,
-                        style,
+                        underline.bottom_right,
+                        underline.style,
                     );
                 }
                 if matches!(
@@ -1022,8 +1223,8 @@ impl Renderer {
                     buffer.putc(
                         line_offset + pos,
                         (code_offset + annotation.start.display).saturating_sub(left),
-                        '|', // underline.multiline_bottom_right_with_text,
-                        style,
+                        underline.multiline_bottom_right_with_text,
+                        underline.style,
                     );
                 }
             }
@@ -1032,15 +1233,15 @@ impl Renderer {
                     buffer.putc(
                         line_offset + pos,
                         width_offset + depth - 1,
-                        ' ', // underline.top_left,
-                        style,
+                        underline.top_left,
+                        underline.style,
                     );
                     for p in line_offset + pos + 1..line_offset + line_len + 2 {
                         buffer.putc(
                             p,
                             width_offset + depth - 1,
-                            '|', // underline.multiline_vertical,
-                            style,
+                            underline.multiline_vertical,
+                            underline.style,
                         );
                     }
                 }
@@ -1049,15 +1250,15 @@ impl Renderer {
                         buffer.putc(
                             p,
                             width_offset + depth - 1,
-                            '|', // underline.multiline_vertical,
-                            style,
+                            underline.multiline_vertical,
+                            underline.style,
                         );
                     }
                     buffer.putc(
                         line_offset + pos,
                         width_offset + depth - 1,
-                        '|', // underline.bottom_left,
-                        style,
+                        underline.bottom_left,
+                        underline.style,
                     );
                 }
                 _ => (),
@@ -1076,7 +1277,11 @@ impl Renderer {
         // 4 |   }
         //   |  _  test
         for &(pos, annotation) in &annotations_position {
-            let style = ElementStyle::Level(annotation.level);
+            let style = if annotation.is_primary() {
+                ElementStyle::LabelPrimary
+            } else {
+                ElementStyle::LabelSecondary
+            };
             let (pos, col) = if pos == 0 {
                 if annotation.end.display == 0 {
                     (pos + 1, (annotation.end.display + 2).saturating_sub(left))
@@ -1101,7 +1306,7 @@ impl Renderer {
         //   | something about `fn foo()`
         annotations_position.sort_by_key(|(_, ann)| {
             // Decreasing order. When annotations share the same length, prefer `Primary`.
-            Reverse(ann.len())
+            (Reverse(ann.len()), ann.is_primary())
         });
 
         // Write the underlines.
@@ -1116,19 +1321,14 @@ impl Renderer {
         // 4 |   }
         //   |  _^  test
         for &(pos, annotation) in &annotations_position {
-            let style = ElementStyle::Level(annotation.level);
-            let underline = if annotation.level == Level::Error {
-                '^'
-            } else {
-                '-'
-            };
+            let uline = self.underline(annotation.is_primary());
             for p in annotation.start.display..annotation.end.display {
                 // The default span label underline.
                 buffer.putc(
                     line_offset + 1,
                     (code_offset + p).saturating_sub(left),
-                    underline,
-                    style,
+                    uline.underline,
+                    uline.style,
                 );
             }
 
@@ -1142,8 +1342,12 @@ impl Renderer {
                 buffer.putc(
                     line_offset + 1,
                     (code_offset + annotation.start.display).saturating_sub(left),
-                    underline,
-                    style,
+                    match annotation.annotation_type {
+                        LineAnnotationType::MultilineStart(_) => uline.top_right_flat,
+                        LineAnnotationType::MultilineEnd(_) => uline.multiline_end_same_line,
+                        _ => panic!("unexpected annotation type: {annotation:?}"),
+                    },
+                    uline.style,
                 );
             } else if pos != 0
                 && matches!(
@@ -1156,16 +1360,20 @@ impl Renderer {
                 buffer.putc(
                     line_offset + 1,
                     (code_offset + annotation.start.display).saturating_sub(left),
-                    underline,
-                    style,
+                    match annotation.annotation_type {
+                        LineAnnotationType::MultilineStart(_) => uline.multiline_start_down,
+                        LineAnnotationType::MultilineEnd(_) => uline.multiline_end_up,
+                        _ => panic!("unexpected annotation type: {annotation:?}"),
+                    },
+                    uline.style,
                 );
             } else if pos != 0 && annotation.has_label() {
                 // The beginning of a span label with an actual label, we'll point down.
                 buffer.putc(
                     line_offset + 1,
                     (code_offset + annotation.start.display).saturating_sub(left),
-                    underline,
-                    style,
+                    uline.label_start,
+                    uline.style,
                 );
             }
         }
@@ -1173,7 +1381,11 @@ impl Renderer {
             .iter()
             .filter_map(|&(_, annotation)| match annotation.annotation_type {
                 LineAnnotationType::MultilineStart(p) | LineAnnotationType::MultilineEnd(p) => {
-                    let style = ElementStyle::Level(annotation.level);
+                    let style = if annotation.is_primary() {
+                        ElementStyle::LabelPrimary
+                    } else {
+                        ElementStyle::LabelSecondary
+                    };
                     Some((p, style))
                 }
                 _ => None,
@@ -1301,6 +1513,46 @@ impl Renderer {
     fn secondary_file_start(&self) -> &str {
         "::: "
     }
+
+    fn underline(&self, is_primary: bool) -> UnderlineParts {
+        if is_primary {
+            UnderlineParts {
+                style: ElementStyle::UnderlinePrimary,
+                underline: '^',
+                label_start: '^',
+                vertical_text_line: '|',
+                multiline_vertical: '|',
+                multiline_horizontal: '_',
+                multiline_whole_line: '/',
+                multiline_start_down: '^',
+                bottom_right: '|',
+                top_left: ' ',
+                top_right_flat: '^',
+                bottom_left: '|',
+                multiline_end_up: '^',
+                multiline_end_same_line: '^',
+                multiline_bottom_right_with_text: '|',
+            }
+        } else {
+            UnderlineParts {
+                style: ElementStyle::UnderlineSecondary,
+                underline: '-',
+                label_start: '-',
+                vertical_text_line: '|',
+                multiline_vertical: '|',
+                multiline_horizontal: '_',
+                multiline_whole_line: '/',
+                multiline_start_down: '-',
+                bottom_right: '|',
+                top_left: ' ',
+                top_right_flat: '-',
+                bottom_left: '|',
+                multiline_end_up: '-',
+                multiline_end_same_line: '-',
+                multiline_bottom_right_with_text: '|',
+            }
+        }
+    }
 }
 
 // instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until
@@ -1409,7 +1661,7 @@ pub(crate) struct LineAnnotation<'a> {
     pub end: Loc,
 
     /// level
-    pub level: Level,
+    pub kind: AnnotationKind,
 
     /// Optional label to display adjacent to the annotation.
     pub label: Option<&'a str>,
@@ -1420,6 +1672,10 @@ pub(crate) struct LineAnnotation<'a> {
 }
 
 impl LineAnnotation<'_> {
+    pub(crate) fn is_primary(&self) -> bool {
+        self.kind == AnnotationKind::Primary
+    }
+
     /// Whether this annotation is a vertical line placeholder.
     pub(crate) fn is_line(&self) -> bool {
         matches!(self.annotation_type, LineAnnotationType::MultilineLine(_))
@@ -1532,23 +1788,48 @@ pub(crate) enum ElementStyle {
     LineAndColumn,
     LineNumber,
     Quotation,
+    UnderlinePrimary,
+    UnderlineSecondary,
+    LabelPrimary,
+    LabelSecondary,
     NoStyle,
     Level(Level),
 }
 
 impl ElementStyle {
-    fn color_spec(&self, stylesheet: &Stylesheet) -> Style {
+    fn color_spec(&self, level: Level, stylesheet: &Stylesheet) -> Style {
         match self {
             ElementStyle::LineAndColumn => stylesheet.none,
             ElementStyle::LineNumber => stylesheet.line_no,
             ElementStyle::Quotation => stylesheet.none,
             ElementStyle::MainHeaderMsg => stylesheet.emphasis,
+            ElementStyle::UnderlinePrimary | ElementStyle::LabelPrimary => level.style(stylesheet),
+            ElementStyle::UnderlineSecondary | ElementStyle::LabelSecondary => stylesheet.context,
             ElementStyle::HeaderMsg | ElementStyle::NoStyle => stylesheet.none,
             ElementStyle::Level(lvl) => lvl.style(stylesheet),
         }
     }
 }
 
+#[derive(Debug, Clone, Copy)]
+struct UnderlineParts {
+    style: ElementStyle,
+    underline: char,
+    label_start: char,
+    vertical_text_line: char,
+    multiline_vertical: char,
+    multiline_horizontal: char,
+    multiline_whole_line: char,
+    multiline_start_down: char,
+    bottom_right: char,
+    top_left: char,
+    top_right_flat: char,
+    bottom_left: char,
+    multiline_end_up: char,
+    multiline_end_same_line: char,
+    multiline_bottom_right_with_text: char,
+}
+
 #[cfg(test)]
 mod test {
     use super::OUTPUT_REPLACEMENTS;
diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs
index 0169566..c29774a 100644
--- a/src/renderer/source_map.rs
+++ b/src/renderer/source_map.rs
@@ -1,5 +1,5 @@
 use crate::renderer::{char_width, num_overlap, LineAnnotation, LineAnnotationType};
-use crate::{Annotation, Level};
+use crate::{Annotation, AnnotationKind};
 use std::cmp::{max, min};
 use std::ops::Range;
 
@@ -105,7 +105,7 @@ impl<'a> SourceMap<'a> {
         &self,
         annotations: Vec<Annotation<'a>>,
         fold: bool,
-    ) -> Vec<AnnotatedLineInfo<'a>> {
+    ) -> (usize, Vec<AnnotatedLineInfo<'a>>) {
         let source_len = self.source.len();
         if let Some(bigger) = annotations.iter().find_map(|x| {
             // Allow highlighting one past the last character in the source.
@@ -129,12 +129,7 @@ impl<'a> SourceMap<'a> {
             .collect::<Vec<_>>();
         let mut multiline_annotations = vec![];
 
-        for Annotation {
-            range,
-            label,
-            level,
-        } in annotations
-        {
+        for Annotation { range, label, kind } in annotations {
             let (lo, mut hi) = self.span_to_locations(range);
 
             // Watch out for "empty spans". If we get a span like 6..6, we
@@ -151,7 +146,7 @@ impl<'a> SourceMap<'a> {
                 let line_ann = LineAnnotation {
                     start: lo,
                     end: hi,
-                    level,
+                    kind,
                     label,
                     annotation_type: LineAnnotationType::Singleline,
                 };
@@ -161,17 +156,22 @@ impl<'a> SourceMap<'a> {
                     depth: 1,
                     start: lo,
                     end: hi,
-                    level,
+                    kind,
                     label,
                     overlaps_exactly: false,
                 });
             }
         }
 
+        let mut primary_spans = vec![];
+
         // Find overlapping multiline annotations, put them at different depths
         multiline_annotations
             .sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line, ml.start.byte));
         for ann in multiline_annotations.clone() {
+            if ann.kind.is_primary() {
+                primary_spans.push((ann.start, ann.end));
+            }
             for a in &mut multiline_annotations {
                 // Move all other multiline annotations overlapping with this one
                 // one level to the right.
@@ -182,6 +182,12 @@ impl<'a> SourceMap<'a> {
                 } else if ann.same_span(a) && &ann != a {
                     a.overlaps_exactly = true;
                 } else {
+                    if primary_spans
+                        .iter()
+                        .any(|(s, e)| a.start == *s && a.end == *e)
+                    {
+                        a.kind = AnnotationKind::Primary;
+                    }
                     break;
                 }
             }
@@ -268,7 +274,7 @@ impl<'a> SourceMap<'a> {
             .iter_mut()
             .for_each(|l| l.annotations.sort_by(|a, b| a.start.cmp(&b.start)));
 
-        annotated_line_infos
+        (max_depth, annotated_line_infos)
     }
 
     fn add_annotation_to_file(
@@ -303,7 +309,7 @@ pub(crate) struct MultilineAnnotation<'a> {
     pub depth: usize,
     pub start: Loc,
     pub end: Loc,
-    pub level: Level,
+    pub kind: AnnotationKind,
     pub label: Option<&'a str>,
     pub overlaps_exactly: bool,
 }
@@ -327,7 +333,7 @@ impl<'a> MultilineAnnotation<'a> {
                 display: self.start.display + 1,
                 byte: self.start.byte + 1,
             },
-            level: self.level,
+            kind: self.kind,
             label: None,
             annotation_type: LineAnnotationType::MultilineStart(self.depth),
         }
@@ -342,7 +348,7 @@ impl<'a> MultilineAnnotation<'a> {
                 byte: self.end.byte.saturating_sub(1),
             },
             end: self.end,
-            level: self.level,
+            kind: self.kind,
             label: self.label,
             annotation_type: LineAnnotationType::MultilineEnd(self.depth),
         }
@@ -352,7 +358,7 @@ impl<'a> MultilineAnnotation<'a> {
         LineAnnotation {
             start: Loc::default(),
             end: Loc::default(),
-            level: self.level,
+            kind: self.kind,
             label: None,
             annotation_type: LineAnnotationType::MultilineLine(self.depth),
         }
diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs
index fd72358..3f19242 100644
--- a/src/renderer/styled_buffer.rs
+++ b/src/renderer/styled_buffer.rs
@@ -4,6 +4,8 @@
 
 use crate::renderer::stylesheet::Stylesheet;
 use crate::renderer::ElementStyle;
+use crate::Level;
+
 use std::fmt;
 use std::fmt::Write;
 
@@ -37,12 +39,16 @@ impl StyledBuffer {
         }
     }
 
-    pub(crate) fn render(&self, stylesheet: &Stylesheet) -> Result<String, fmt::Error> {
+    pub(crate) fn render(
+        &self,
+        level: Level,
+        stylesheet: &Stylesheet,
+    ) -> Result<String, fmt::Error> {
         let mut str = String::new();
         for (i, line) in self.lines.iter().enumerate() {
             let mut current_style = stylesheet.none;
             for StyledChar { ch, style } in line {
-                let ch_style = style.color_spec(stylesheet);
+                let ch_style = style.color_spec(level, stylesheet);
                 if ch_style != current_style {
                     if !line.is_empty() {
                         write!(str, "{}", current_style.render_reset())?;
diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs
index 72a5f0e..c11a279 100644
--- a/src/renderer/stylesheet.rs
+++ b/src/renderer/stylesheet.rs
@@ -10,6 +10,7 @@ pub(crate) struct Stylesheet {
     pub(crate) line_no: Style,
     pub(crate) emphasis: Style,
     pub(crate) none: Style,
+    pub(crate) context: Style,
 }
 
 impl Default for Stylesheet {
@@ -29,6 +30,7 @@ impl Stylesheet {
             line_no: Style::new(),
             emphasis: Style::new(),
             none: Style::new(),
+            context: Style::new(),
         }
     }
 }
diff --git a/src/snippet.rs b/src/snippet.rs
index bf0ae91..1c3efb6 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -1,14 +1,4 @@
 //! Structures used as an input for the library.
-//!
-//! Example:
-//!
-//! ```
-//! use annotate_snippets::*;
-//!
-//! Level::Error.title("mismatched types")
-//!     .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs"))
-//!     .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
-//! ```
 
 use crate::renderer::stylesheet::Stylesheet;
 use anstyle::Style;
@@ -20,16 +10,10 @@ pub(crate) const INFO_TXT: &str = "info";
 pub(crate) const NOTE_TXT: &str = "note";
 pub(crate) const WARNING_TXT: &str = "warning";
 
-/// Primary structure provided for formatting
-///
-/// See [`Level::title`] to create a [`Message`]
 #[derive(Debug)]
 pub struct Message<'a> {
-    pub(crate) level: Level,
-    pub(crate) id: Option<&'a str>,
-    pub(crate) title: &'a str,
-    pub(crate) snippets: Vec<Snippet<'a>>,
-    pub(crate) footer: Vec<Message<'a>>,
+    pub(crate) id: Option<&'a str>, // for "correctness", could be sloppy and be on Title
+    pub(crate) groups: Vec<Group<'a>>,
 }
 
 impl<'a> Message<'a> {
@@ -38,90 +22,189 @@ impl<'a> Message<'a> {
         self
     }
 
-    pub fn snippet(mut self, slice: Snippet<'a>) -> Self {
-        self.snippets.push(slice);
+    pub fn group(mut self, group: Group<'a>) -> Self {
+        self.groups.push(group);
         self
     }
 
-    pub fn snippets(mut self, slice: impl IntoIterator<Item = Snippet<'a>>) -> Self {
-        self.snippets.extend(slice);
-        self
+    pub(crate) fn max_line_number(&self) -> usize {
+        self.groups
+            .iter()
+            .map(|v| {
+                v.elements
+                    .iter()
+                    .map(|s| match s {
+                        Element::Title(_) | Element::Origin(_) | Element::ColumnSeparator(_) => 0,
+                        Element::Cause(cause) => {
+                            let end = cause
+                                .markers
+                                .iter()
+                                .map(|a| a.range.end)
+                                .max()
+                                .unwrap_or(cause.source.len())
+                                .min(cause.source.len());
+
+                            cause.line_start + newline_count(&cause.source[..end])
+                        }
+                    })
+                    .max()
+                    .unwrap_or(1)
+            })
+            .max()
+            .unwrap_or(1)
+    }
+}
+
+#[derive(Debug)]
+pub struct Group<'a> {
+    pub(crate) elements: Vec<Element<'a>>,
+}
+
+impl Default for Group<'_> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'a> Group<'a> {
+    pub fn new() -> Self {
+        Self { elements: vec![] }
     }
 
-    pub fn footer(mut self, footer: Message<'a>) -> Self {
-        self.footer.push(footer);
+    pub fn element(mut self, section: impl Into<Element<'a>>) -> Self {
+        self.elements.push(section.into());
         self
     }
 
-    pub fn footers(mut self, footer: impl IntoIterator<Item = Message<'a>>) -> Self {
-        self.footer.extend(footer);
+    pub fn elements(mut self, sections: impl IntoIterator<Item = impl Into<Element<'a>>>) -> Self {
+        self.elements.extend(sections.into_iter().map(Into::into));
         self
     }
+
+    pub fn is_empty(&self) -> bool {
+        self.elements.is_empty()
+    }
 }
 
-impl Message<'_> {
-    pub(crate) fn has_primary_spans(&self) -> bool {
-        self.snippets.iter().any(|s| !s.annotations.is_empty())
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum Element<'a> {
+    Title(Title<'a>),
+    Cause(Snippet<'a, Annotation<'a>>),
+    Origin(Origin<'a>),
+    ColumnSeparator(ColumnSeparator),
+}
+
+impl<'a> From<Title<'a>> for Element<'a> {
+    fn from(value: Title<'a>) -> Self {
+        Element::Title(value)
     }
-    pub(crate) fn has_span_labels(&self) -> bool {
-        self.snippets.iter().any(|s| !s.annotations.is_empty())
+}
+
+impl<'a> From<Snippet<'a, Annotation<'a>>> for Element<'a> {
+    fn from(value: Snippet<'a, Annotation<'a>>) -> Self {
+        Element::Cause(value)
     }
+}
 
-    pub(crate) fn max_line_number(&self) -> usize {
-        let mut max = self
-            .snippets
-            .iter()
-            .map(|s| {
-                let start = s
-                    .annotations
-                    .iter()
-                    .map(|a| a.range.start)
-                    .min()
-                    .unwrap_or(0);
+impl<'a> From<Origin<'a>> for Element<'a> {
+    fn from(value: Origin<'a>) -> Self {
+        Element::Origin(value)
+    }
+}
 
-                let end = s
-                    .annotations
-                    .iter()
-                    .map(|a| a.range.end)
-                    .max()
-                    .unwrap_or(s.source.len())
-                    .min(s.source.len());
+impl From<ColumnSeparator> for Element<'_> {
+    fn from(value: ColumnSeparator) -> Self {
+        Self::ColumnSeparator(value)
+    }
+}
 
-                s.line_start + newline_count(&s.source[start..end])
-            })
-            .max()
-            .unwrap_or(1);
+#[derive(Debug)]
+pub struct ColumnSeparator;
+
+#[derive(Debug)]
+pub struct Title<'a> {
+    pub(crate) level: Level,
+    pub(crate) title: &'a str,
+    pub(crate) primary: bool,
+}
+
+impl Title<'_> {
+    pub fn primary(mut self, primary: bool) -> Self {
+        self.primary = primary;
+        self
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum Level {
+    Error,
+    Warning,
+    Info,
+    Note,
+    Help,
+    None,
+}
 
-        for footer in &self.footer {
-            max = max.max(footer.max_line_number());
+impl Level {
+    pub fn message(self, title: &str) -> Message<'_> {
+        Message {
+            id: None,
+            groups: vec![Group::new().element(Element::Title(Title {
+                level: self,
+                title,
+                primary: true,
+            }))],
+        }
+    }
+
+    pub fn title(self, title: &str) -> Title<'_> {
+        Title {
+            level: self,
+            title,
+            primary: false,
+        }
+    }
+
+    pub(crate) fn as_str(self) -> &'static str {
+        match self {
+            Level::Error => ERROR_TXT,
+            Level::Warning => WARNING_TXT,
+            Level::Info => INFO_TXT,
+            Level::Note => NOTE_TXT,
+            Level::Help => HELP_TXT,
+            Level::None => "",
+        }
+    }
+
+    pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style {
+        match self {
+            Level::Error => stylesheet.error,
+            Level::Warning => stylesheet.warning,
+            Level::Info => stylesheet.info,
+            Level::Note => stylesheet.note,
+            Level::Help => stylesheet.help,
+            Level::None => stylesheet.none,
         }
-        max
     }
 }
 
-/// Structure containing the slice of text to be annotated and
-/// basic information about the location of the slice.
-///
-/// One `Snippet` is meant to represent a single, continuous,
-/// slice of source code that you want to annotate.
 #[derive(Debug)]
-pub struct Snippet<'a> {
+pub struct Snippet<'a, T> {
     pub(crate) origin: Option<&'a str>,
     pub(crate) line_start: usize,
-
     pub(crate) source: &'a str,
-    pub(crate) annotations: Vec<Annotation<'a>>,
-
+    pub(crate) markers: Vec<T>,
     pub(crate) fold: bool,
 }
 
-impl<'a> Snippet<'a> {
+impl<'a, T: Clone> Snippet<'a, T> {
     pub fn source(source: &'a str) -> Self {
         Self {
             origin: None,
             line_start: 1,
             source,
-            annotations: vec![],
+            markers: vec![],
             fold: false,
         }
     }
@@ -136,32 +219,29 @@ impl<'a> Snippet<'a> {
         self
     }
 
-    pub fn annotation(mut self, annotation: Annotation<'a>) -> Self {
-        self.annotations.push(annotation);
+    pub fn fold(mut self, fold: bool) -> Self {
+        self.fold = fold;
         self
     }
+}
 
-    pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self {
-        self.annotations.extend(annotation);
+impl<'a> Snippet<'a, Annotation<'a>> {
+    pub fn annotation(mut self, annotation: Annotation<'a>) -> Snippet<'a, Annotation<'a>> {
+        self.markers.push(annotation);
         self
     }
 
-    /// Hide lines without [`Annotation`]s
-    pub fn fold(mut self, fold: bool) -> Self {
-        self.fold = fold;
+    pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self {
+        self.markers.extend(annotation);
         self
     }
 }
 
-/// An annotation for a [`Snippet`].
-///
-/// See [`Level::span`] to create a [`Annotation`]
 #[derive(Clone, Debug)]
 pub struct Annotation<'a> {
-    /// The byte range of the annotation in the `source` string
     pub(crate) range: Range<usize>,
     pub(crate) label: Option<&'a str>,
-    pub(crate) level: Level,
+    pub(crate) kind: AnnotationKind,
 }
 
 impl<'a> Annotation<'a> {
@@ -171,58 +251,66 @@ impl<'a> Annotation<'a> {
     }
 }
 
-/// Types of annotations.
-#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
-pub enum Level {
-    /// Error annotations are displayed using red color and "^" character.
-    Error,
-    /// Warning annotations are displayed using blue color and "-" character.
-    Warning,
-    Info,
-    Note,
-    Help,
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum AnnotationKind {
+    /// Color to [`Message`]'s [`Level`]
+    Primary,
+    /// "secondary"; fixed color
+    Context,
 }
 
-impl Level {
-    pub fn title(self, title: &str) -> Message<'_> {
-        Message {
-            level: self,
-            id: None,
-            title,
-            snippets: vec![],
-            footer: vec![],
-        }
-    }
-
-    /// Create a [`Annotation`] with the given span for a [`Snippet`]
+impl AnnotationKind {
     pub fn span<'a>(self, span: Range<usize>) -> Annotation<'a> {
         Annotation {
             range: span,
             label: None,
-            level: self,
+            kind: self,
         }
     }
+
+    pub(crate) fn is_primary(&self) -> bool {
+        matches!(self, AnnotationKind::Primary)
+    }
 }
 
-impl Level {
-    pub(crate) fn as_str(&self) -> &'static str {
-        match self {
-            Level::Error => ERROR_TXT,
-            Level::Warning => WARNING_TXT,
-            Level::Info => INFO_TXT,
-            Level::Note => NOTE_TXT,
-            Level::Help => HELP_TXT,
+#[derive(Clone, Debug)]
+pub struct Origin<'a> {
+    pub(crate) origin: &'a str,
+    pub(crate) line: Option<usize>,
+    pub(crate) char_column: Option<usize>,
+    pub(crate) primary: bool,
+    pub(crate) label: Option<&'a str>,
+}
+
+impl<'a> Origin<'a> {
+    pub fn new(origin: &'a str) -> Self {
+        Self {
+            origin,
+            line: None,
+            char_column: None,
+            primary: false,
+            label: None,
         }
     }
 
-    pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style {
-        match self {
-            Level::Error => stylesheet.error,
-            Level::Warning => stylesheet.warning,
-            Level::Info => stylesheet.info,
-            Level::Note => stylesheet.note,
-            Level::Help => stylesheet.help,
-        }
+    pub fn line(mut self, line: usize) -> Self {
+        self.line = Some(line);
+        self
+    }
+
+    pub fn char_column(mut self, char_column: usize) -> Self {
+        self.char_column = Some(char_column);
+        self
+    }
+
+    pub fn primary(mut self, primary: bool) -> Self {
+        self.primary = primary;
+        self
+    }
+
+    pub fn label(mut self, label: &'a str) -> Self {
+        self.label = Some(label);
+        self
     }
 }
 
diff --git a/tests/fixtures/color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml
index cee5f0f..ac129f3 100644
--- a/tests/fixtures/color/ann_eof.toml
+++ b/tests/fixtures/color/ann_eof.toml
@@ -2,14 +2,14 @@
 level = "Error"
 title = "expected `.`, `=`"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = "asdf"
 line_start = 1
 origin = "Cargo.toml"
-[[message.snippets.annotations]]
-label = ""
-level = "Error"
-range = [4, 4]
+annotations = [
+  { label = "", kind = "Primary", range = [4, 4] },
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml
index bf7411e..13bc13c 100644
--- a/tests/fixtures/color/ann_insertion.toml
+++ b/tests/fixtures/color/ann_insertion.toml
@@ -2,14 +2,14 @@
 level = "Error"
 title = "expected `.`, `=`"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = "asf"
 line_start = 1
 origin = "Cargo.toml"
-[[message.snippets.annotations]]
-label = "'d' belongs here"
-level = "Error"
-range = [2, 2]
+annotations = [
+    { label = "'d' belongs here", kind = "Primary", range = [2, 2] }
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml
index 9d8c30f..722c3e1 100644
--- a/tests/fixtures/color/ann_multiline.toml
+++ b/tests/fixtures/color/ann_multiline.toml
@@ -3,7 +3,8 @@ level = "Error"
 id = "E0027"
 title = "pattern does not mention fields `lineno`, `content`"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = """
                         if let DisplayLine::Source {
                             ref mut inline_marks,
@@ -12,10 +13,9 @@ source = """
 line_start = 139
 origin = "src/display_list.rs"
 fold = false
-[[message.snippets.annotations]]
-label = "missing fields `lineno`, `content`"
-level = "Error"
-range = [31, 128]
+annotations = [
+    { label = "missing fields `lineno`, `content`", kind = "Primary", range = [31, 128] }
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml
index 259d94b..329beb4 100644
--- a/tests/fixtures/color/ann_multiline2.toml
+++ b/tests/fixtures/color/ann_multiline2.toml
@@ -3,7 +3,8 @@ level = "Error"
 id = "E####"
 title = "spacing error found"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = """
 This is an example
 of an edge case of an annotation overflowing
@@ -12,10 +13,9 @@ to exactly one character on next line.
 line_start = 26
 origin = "foo.txt"
 fold = false
-[[message.snippets.annotations]]
-label = "this should not be on separate lines"
-level = "Error"
-range = [11, 19]
+annotations = [
+    { label = "this should not be on separate lines", kind = "Primary", range = [11, 19] },
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml
index 36f74ef..8ed96bc 100644
--- a/tests/fixtures/color/ann_removed_nl.toml
+++ b/tests/fixtures/color/ann_removed_nl.toml
@@ -2,14 +2,14 @@
 level = "Error"
 title = "expected `.`, `=`"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = "asdf"
 line_start = 1
 origin = "Cargo.toml"
-[[message.snippets.annotations]]
-label = ""
-level = "Error"
-range = [4, 5]
+annotations = [
+    { label = "", kind = "Primary", range = [4, 5] },
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml
index 52168b4..8d7a14a 100644
--- a/tests/fixtures/color/ensure-emoji-highlight-width.toml
+++ b/tests/fixtures/color/ensure-emoji-highlight-width.toml
@@ -3,16 +3,16 @@ title = "invalid character ` ` in package name: `haha this isn't a valid name 
 level = "Error"
 
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = """
 "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }
 """
 line_start = 7
 origin = "<file>"
-[[message.snippets.annotations]]
-label = ""
-level = "Error"
-range = [0, 35]
+annotations = [
+    { label = "", kind = "Primary", range = [0, 35] },
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg
index 1afc652..80197e5 100644
--- a/tests/fixtures/color/fold_ann_multiline.svg
+++ b/tests/fixtures/color/fold_ann_multiline.svg
@@ -4,7 +4,6 @@
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
     .fg-bright-red { fill: #FF5555 }
-    .fg-yellow { fill: #AA5500 }
     .container {
       padding: 0 10px;
       line-height: 18px;
@@ -22,13 +21,13 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format.rs:51:6</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format.rs:52:1</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>   ) -&gt; Option&lt;String&gt; {</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>        </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `std::option::Option&lt;std::string::String&gt;` because of return type</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>        </tspan><tspan class="fg-bright-blue bold">--------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected `std::option::Option&lt;std::string::String&gt;` because of return type</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan>     for ann in annotations {</tspan>
 </tspan>
diff --git a/tests/fixtures/color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml
index 80edfb5..745ef42 100644
--- a/tests/fixtures/color/fold_ann_multiline.toml
+++ b/tests/fixtures/color/fold_ann_multiline.toml
@@ -3,7 +3,8 @@ level = "Error"
 id = "E0308"
 title = "mismatched types"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = """
 ) -> Option<String> {
     for ann in annotations {
@@ -31,14 +32,10 @@ source = """
 line_start = 51
 origin = "src/format.rs"
 fold = true
-[[message.snippets.annotations]]
-label = "expected `std::option::Option<std::string::String>` because of return type"
-level = "Warning"
-range = [5, 19]
-[[message.snippets.annotations]]
-label = "expected enum `std::option::Option`, found ()"
-level = "Error"
-range = [22, 766]
+annotations = [
+    { label = "expected `std::option::Option<std::string::String>` because of return type", kind = "Context", range = [5, 19] },
+    { label = "expected enum `std::option::Option`, found ()", kind = "Primary", range = [22, 766] },
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg
index bd075e4..6608327 100644
--- a/tests/fixtures/color/fold_bad_origin_line.svg
+++ b/tests/fixtures/color/fold_bad_origin_line.svg
@@ -4,7 +4,6 @@
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
     .fg-bright-red { fill: #FF5555 }
-    .fg-yellow { fill: #AA5500 }
     .container {
       padding: 0 10px;
       line-height: 18px;
@@ -28,7 +27,7 @@
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> invalid syntax</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">error here</tspan>
+    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">--------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">error here</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml
index 3e40137..3b0d827 100644
--- a/tests/fixtures/color/fold_bad_origin_line.toml
+++ b/tests/fixtures/color/fold_bad_origin_line.toml
@@ -2,7 +2,8 @@
 level = "Error"
 title = ""
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = """
 
 
@@ -11,10 +12,9 @@ invalid syntax
 line_start = 1
 origin = "path/to/error.rs"
 fold = true
-[[message.snippets.annotations]]
-label = "error here"
-level = "Warning"
-range = [2,16]
+annotations = [
+    { label = "error here", kind = "Context", range = [2,16] },
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg
index 22a66c7..23b31d4 100644
--- a/tests/fixtures/color/fold_leading.svg
+++ b/tests/fixtures/color/fold_leading.svg
@@ -21,13 +21,13 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:11:13</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:11:13</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11|</tspan><tspan> workspace = 20</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> workspace = 20</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             </tspan><tspan class="fg-bright-red bold">^^</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>             </tspan><tspan class="fg-bright-red bold">^^</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml
index 90c6c8c..564187b 100644
--- a/tests/fixtures/color/fold_leading.toml
+++ b/tests/fixtures/color/fold_leading.toml
@@ -3,7 +3,8 @@ level = "Error"
 id = "E0308"
 title = "invalid type: integer `20`, expected a bool"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = """
 [workspace]
 
@@ -20,10 +21,9 @@ workspace = 20
 line_start = 1
 origin = "Cargo.toml"
 fold = true
-[[message.snippets.annotations]]
-label = ""
-level = "Error"
-range = [132, 134]
+annotations = [
+    { label = "", kind = "Primary", range = [132, 134] },
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml
index 10b2240..eea6365 100644
--- a/tests/fixtures/color/fold_trailing.toml
+++ b/tests/fixtures/color/fold_trailing.toml
@@ -3,7 +3,8 @@ level = "Error"
 id = "E0308"
 title = "invalid type: integer `20`, expected a lints table"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = """
 lints = 20
 
@@ -19,10 +20,9 @@ edition = "2021"
 line_start = 1
 origin = "Cargo.toml"
 fold = true
-[[message.snippets.annotations]]
-label = ""
-level = "Error"
-range = [8, 10]
+annotations = [
+    { label = "", kind = "Primary", range = [8, 10] },
+]
 
 [renderer]
 color = true
diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg
index 6854bc3..5ae5da7 100644
--- a/tests/fixtures/color/issue_9.svg
+++ b/tests/fixtures/color/issue_9.svg
@@ -1,10 +1,9 @@
-<svg width="911px" height="218px" xmlns="http://www.w3.org/2000/svg">
+<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
     .fg-bright-red { fill: #FF5555 }
-    .fg-yellow { fill: #AA5500 }
     .container {
       padding: 0 10px;
       line-height: 18px;
@@ -22,25 +21,27 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="64px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan>
+    <tspan x="10px" y="82px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
+    <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="118px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
+    <tspan x="10px" y="136px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
+    <tspan x="10px" y="172px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">value moved here</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
+    <tspan x="10px" y="190px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
+    <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
+</tspan>
+    <tspan x="10px" y="226px"><tspan>  </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
 </tspan>
   </text>
 
diff --git a/tests/fixtures/color/issue_9.toml b/tests/fixtures/color/issue_9.toml
index 9de1753..96ad2c0 100644
--- a/tests/fixtures/color/issue_9.toml
+++ b/tests/fixtures/color/issue_9.toml
@@ -2,29 +2,32 @@
 level = "Error"
 title = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = "let x = vec![1];"
 line_start = 4
 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs"
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait"
-level = "Warning"
+kind = "Context"
 range = [4, 5]
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = "let y = x;"
 line_start = 7
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "value moved here"
-level = "Warning"
+kind = "Context"
 range = [8, 9]
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = "x;"
 line_start = 9
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "value used here after move"
-level = "Error"
+kind = "Primary"
 range = [0, 1]
 
 [renderer]
diff --git a/tests/fixtures/color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml
index 824c530..f4c90a4 100644
--- a/tests/fixtures/color/multiple_annotations.toml
+++ b/tests/fixtures/color/multiple_annotations.toml
@@ -2,7 +2,8 @@
 level = "Error"
 title = ""
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = """
 fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {
     if let Some(annotation) = main_annotation {
@@ -15,17 +16,17 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>
 }
 """
 line_start = 96
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "Variable defined here"
-level = "Error"
+kind = "Primary"
 range = [100, 110]
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "Referenced here"
-level = "Error"
+kind = "Primary"
 range = [184, 194]
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "Referenced again here"
-level = "Error"
+kind = "Primary"
 range = [243, 253]
 
 [renderer]
diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg
index ef59075..b849cf4 100644
--- a/tests/fixtures/color/simple.svg
+++ b/tests/fixtures/color/simple.svg
@@ -4,7 +4,6 @@
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
     .fg-bright-red { fill: #FF5555 }
-    .fg-yellow { fill: #AA5500 }
     .container {
       padding: 0 10px;
       line-height: 18px;
@@ -22,13 +21,13 @@
   <text xml:space="preserve" class="container fg">
     <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
 </tspan>
-    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format_color.rs:169:11</tspan>
+    <tspan x="10px" y="46px"><tspan>   </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format_color.rs:171:9</tspan>
 </tspan>
     <tspan x="10px" y="64px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>         })</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>           </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected one of `.`, `;`, `?`, or an operator here</tspan>
+    <tspan x="10px" y="100px"><tspan>    </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>           </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected one of `.`, `;`, `?`, or an operator here</tspan>
 </tspan>
     <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
diff --git a/tests/fixtures/color/simple.toml b/tests/fixtures/color/simple.toml
index 2e6969f..b70f11c 100644
--- a/tests/fixtures/color/simple.toml
+++ b/tests/fixtures/color/simple.toml
@@ -2,20 +2,21 @@
 level = "Error"
 title = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = """
         })
 
         for line in &self.body {"""
 line_start = 169
 origin = "src/format_color.rs"
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "unexpected token"
-level = "Error"
+kind = "Primary"
 range = [20, 23]
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "expected one of `.`, `;`, `?`, or an operator here"
-level = "Warning"
+kind = "Context"
 range = [10, 11]
 
 [renderer]
diff --git a/tests/fixtures/color/strip_line.toml b/tests/fixtures/color/strip_line.toml
index 546c96a..d7af686 100644
--- a/tests/fixtures/color/strip_line.toml
+++ b/tests/fixtures/color/strip_line.toml
@@ -3,14 +3,15 @@ level = "Error"
 id = "E0308"
 title = "mismatched types"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = "                                                                                                                                                                                    let _: () = 42;"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "expected (), found integer"
-level = "Error"
+kind = "Primary"
 range = [192, 194]
 
 [renderer]
diff --git a/tests/fixtures/color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml
index 863abb3..6585005 100644
--- a/tests/fixtures/color/strip_line_char.toml
+++ b/tests/fixtures/color/strip_line_char.toml
@@ -1,16 +1,17 @@
 [message]
 level = "Error"
 id = "E0308"
-title = "mismatched types" 
+title = "mismatched types"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 source = "                                                                                                                                                                                    let _: () = 42ñ"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "expected (), found integer"
-level = "Error"
+kind = "Primary"
 range = [192, 194]
 
 [renderer]
diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml
index bfe9f31..1f085b5 100644
--- a/tests/fixtures/color/strip_line_non_ws.toml
+++ b/tests/fixtures/color/strip_line_non_ws.toml
@@ -3,21 +3,22 @@ level = "Error"
 id = "E0308"
 title = "mismatched types"
 
-[[message.snippets]]
+[[message.sections]]
+type = "Cause"
 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]]
+[[message.sections.annotations]]
 label = "expected `()`, found integer"
-level = "Error"
+kind = "Primary"
 range = [237, 239]
 
-[[message.snippets.annotations]]
+[[message.sections.annotations]]
 label = "expected due to this"
-level = "Error"
+kind = "Primary"
 range = [232, 234]
 
 
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 4dbf341..fffcf1f 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -2,7 +2,9 @@ use serde::Deserialize;
 use std::ops::Range;
 
 use annotate_snippets::renderer::DEFAULT_TERM_WIDTH;
-use annotate_snippets::{Annotation, Level, Message, Renderer, Snippet};
+use annotate_snippets::{
+    Annotation, AnnotationKind, Element, Group, Level, Message, Renderer, Snippet,
+};
 
 #[derive(Deserialize)]
 pub(crate) struct Fixture {
@@ -19,8 +21,7 @@ pub struct MessageDef {
     #[serde(default)]
     pub id: Option<String>,
     #[serde(default)]
-    pub footer: Vec<MessageDef>,
-    pub snippets: Vec<SnippetDef>,
+    pub sections: Vec<ElementDef>,
 }
 
 impl<'a> From<&'a MessageDef> for Message<'a> {
@@ -29,35 +30,60 @@ impl<'a> From<&'a MessageDef> for Message<'a> {
             level,
             title,
             id,
-            footer,
-            snippets,
+            sections,
         } = val;
-        let mut message = level.title(title);
+        let mut message = level.message(title);
         if let Some(id) = id {
             message = message.id(id);
         }
-        message = message.snippets(snippets.iter().map(Snippet::from));
-        message = message.footers(footer.iter().map(Into::into));
+
+        message = message.group(Group::new().elements(sections.iter().map(|s| match s {
+            ElementDef::Title(title) => Element::Title(title.level.title(&title.title)),
+            ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)),
+        })));
         message
     }
 }
 
 #[derive(Deserialize)]
-pub struct SnippetDef {
-    pub source: String,
-    pub line_start: usize,
-    pub origin: Option<String>,
-    pub annotations: Vec<AnnotationDef>,
+#[serde(tag = "type")]
+pub enum ElementDef {
+    Title(TitleDef),
+    Cause(SnippetAnnotationDef),
+}
+
+impl<'a> From<&'a ElementDef> for Element<'a> {
+    fn from(val: &'a ElementDef) -> Self {
+        match val {
+            ElementDef::Title(title) => Element::Title(title.level.title(&title.title)),
+            ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)),
+        }
+    }
+}
+
+#[derive(Deserialize)]
+pub struct TitleDef {
+    pub title: String,
+    #[serde(with = "LevelDef")]
+    pub level: Level,
+}
+
+#[derive(Deserialize)]
+pub struct SnippetAnnotationDef {
+    pub(crate) origin: Option<String>,
+    pub(crate) line_start: usize,
+    pub(crate) source: String,
+    pub(crate) annotations: Vec<AnnotationDef>,
     #[serde(default)]
-    pub fold: bool,
+    pub(crate) fold: bool,
 }
 
-impl<'a> From<&'a SnippetDef> for Snippet<'a> {
-    fn from(val: &'a SnippetDef) -> Self {
-        let SnippetDef {
-            source,
-            line_start,
+impl<'a> From<&'a SnippetAnnotationDef> for Snippet<'a, Annotation<'a>> {
+    fn from(val: &'a SnippetAnnotationDef) -> Self {
+        let SnippetAnnotationDef {
             origin,
+            line_start,
+            source,
             annotations,
             fold,
         } = val;
@@ -74,21 +100,25 @@ impl<'a> From<&'a SnippetDef> for Snippet<'a> {
 pub struct AnnotationDef {
     pub range: Range<usize>,
     pub label: String,
-    #[serde(with = "LevelDef")]
-    pub level: Level,
+    #[serde(with = "AnnotationKindDef")]
+    pub kind: AnnotationKind,
 }
 
 impl<'a> From<&'a AnnotationDef> for Annotation<'a> {
     fn from(val: &'a AnnotationDef) -> Self {
-        let AnnotationDef {
-            range,
-            label,
-            level,
-        } = val;
-        level.span(range.start..range.end).label(label)
+        let AnnotationDef { range, label, kind } = val;
+        kind.span(range.start..range.end).label(label)
     }
 }
 
+#[allow(dead_code)]
+#[derive(Deserialize)]
+#[serde(remote = "AnnotationKind")]
+enum AnnotationKindDef {
+    Primary,
+    Context,
+}
+
 #[allow(dead_code)]
 #[derive(Deserialize)]
 #[serde(remote = "Level")]
diff --git a/tests/formatter.rs b/tests/formatter.rs
index df5d081..33b998f 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,14 +1,16 @@
-use annotate_snippets::{Level, Renderer, Snippet};
+use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Renderer, Snippet};
 
 use snapbox::{assert_data_eq, str};
 
 #[test]
 fn test_i_29() {
-    let snippets = Level::Error.title("oops").snippet(
-        Snippet::source("First line\r\nSecond oops line")
-            .origin("<current file>")
-            .annotation(Level::Error.span(19..23).label("oops"))
-            .fold(true),
+    let snippets = Level::Error.message("oops").group(
+        Group::new().element(
+            Snippet::source("First line\r\nSecond oops line")
+                .origin("<current file>")
+                .annotation(AnnotationKind::Primary.span(19..23).label("oops"))
+                .fold(true),
+        ),
     );
     let expected = str![[r#"
 error: oops
@@ -24,10 +26,12 @@ error: oops
 
 #[test]
 fn test_point_to_double_width_characters() {
-    let snippets = Level::Error.title("").snippet(
-        Snippet::source("こんにちは、世界")
-            .origin("<current file>")
-            .annotation(Level::Error.span(18..24).label("world")),
+    let snippets = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source("こんにちは、世界")
+                .origin("<current file>")
+                .annotation(AnnotationKind::Primary.span(18..24).label("world")),
+        ),
     );
 
     let expected = str![[r#"
@@ -44,10 +48,12 @@ error:
 
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
-    let snippets = Level::Error.title("").snippet(
-        Snippet::source("おはよう\nございます")
-            .origin("<current file>")
-            .annotation(Level::Error.span(6..22).label("Good morning")),
+    let snippets = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source("おはよう\nございます")
+                .origin("<current file>")
+                .annotation(AnnotationKind::Primary.span(6..22).label("Good morning")),
+        ),
     );
 
     let expected = str![[r#"
@@ -66,11 +72,13 @@ error:
 
 #[test]
 fn test_point_to_double_width_characters_multiple() {
-    let snippets = Level::Error.title("").snippet(
-        Snippet::source("お寿司\n食べたい🍣")
-            .origin("<current file>")
-            .annotation(Level::Error.span(0..9).label("Sushi1"))
-            .annotation(Level::Note.span(16..22).label("Sushi2")),
+    let snippets = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source("お寿司\n食べたい🍣")
+                .origin("<current file>")
+                .annotation(AnnotationKind::Primary.span(0..9).label("Sushi1"))
+                .annotation(AnnotationKind::Context.span(16..22).label("Sushi2")),
+        ),
     );
 
     let expected = str![[r#"
@@ -89,10 +97,12 @@ error:
 
 #[test]
 fn test_point_to_double_width_characters_mixed() {
-    let snippets = Level::Error.title("").snippet(
-        Snippet::source("こんにちは、新しいWorld!")
-            .origin("<current file>")
-            .annotation(Level::Error.span(18..32).label("New world")),
+    let snippets = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source("こんにちは、新しいWorld!")
+                .origin("<current file>")
+                .annotation(AnnotationKind::Primary.span(18..32).label("New world")),
+        ),
     );
 
     let expected = str![[r#"
@@ -109,7 +119,7 @@ error:
 
 #[test]
 fn test_format_title() {
-    let input = Level::Error.title("This is a title").id("E0001");
+    let input = Level::Error.message("This is a title").id("E0001");
 
     let expected = str![r#"error[E0001]: This is a title"#];
     let renderer = Renderer::plain();
@@ -120,8 +130,8 @@ fn test_format_title() {
 fn test_format_snippet_only() {
     let source = "This is line 1\nThis is line 2";
     let input = Level::Error
-        .title("")
-        .snippet(Snippet::source(source).line_start(5402));
+        .message("")
+        .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(5402)));
 
     let expected = str![[r#"
 error: 
@@ -137,17 +147,26 @@ error:
 fn test_format_snippets_continuation() {
     let src_0 = "This is slice 1";
     let src_1 = "This is slice 2";
-    let input = Level::Error
-        .title("")
-        .snippet(Snippet::source(src_0).line_start(5402).origin("file1.rs"))
-        .snippet(Snippet::source(src_1).line_start(2).origin("file2.rs"));
+    let input = Level::Error.message("").group(
+        Group::new()
+            .element(
+                Snippet::<Annotation<'_>>::source(src_0)
+                    .line_start(5402)
+                    .origin("file1.rs"),
+            )
+            .element(
+                Snippet::<Annotation<'_>>::source(src_1)
+                    .line_start(2)
+                    .origin("file2.rs"),
+            ),
+    );
     let expected = str![[r#"
 error: 
     --> file1.rs
      |
 5402 | This is slice 1
      |
-    ::: file2.rs
+    ::: file2.rs:2
      |
    2 | This is slice 2
 "#]];
@@ -162,10 +181,14 @@ fn test_format_snippet_annotation_standalone() {
     let source = [line_1, line_2].join("\n");
     // In line 2
     let range = 22..24;
-    let input = Level::Error.title("").snippet(
-        Snippet::source(&source)
-            .line_start(5402)
-            .annotation(Level::Info.span(range.clone()).label("Test annotation")),
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(&source).line_start(5402).annotation(
+                AnnotationKind::Context
+                    .span(range.clone())
+                    .label("Test annotation"),
+            ),
+        ),
     );
     let expected = str![[r#"
 error: 
@@ -181,10 +204,11 @@ error:
 #[test]
 fn test_format_footer_title() {
     let input = Level::Error
-        .title("")
-        .footer(Level::Error.title("This __is__ a title"));
+        .message("")
+        .group(Group::new().element(Level::Error.title("This __is__ a title")));
     let expected = str![[r#"
 error: 
+  |
   = error: This __is__ a title
 "#]];
     let renderer = Renderer::plain();
@@ -196,10 +220,14 @@ error:
 fn test_i26() {
     let source = "short";
     let label = "label";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .line_start(0)
-            .annotation(Level::Error.span(0..source.len() + 2).label(label)),
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source).line_start(0).annotation(
+                AnnotationKind::Primary
+                    .span(0..source.len() + 2)
+                    .label(label),
+            ),
+        ),
     );
     let renderer = Renderer::plain();
     let _ = renderer.render(input);
@@ -209,8 +237,8 @@ fn test_i26() {
 fn test_source_content() {
     let source = "This is an example\nof content lines";
     let input = Level::Error
-        .title("")
-        .snippet(Snippet::source(source).line_start(56));
+        .message("")
+        .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56)));
     let expected = str![[r#"
 error: 
    |
@@ -224,10 +252,12 @@ error:
 #[test]
 fn test_source_annotation_standalone_singleline() {
     let source = "tests";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .annotation(Level::Help.span(0..5).label("Example string")),
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .annotation(AnnotationKind::Context.span(0..5).label("Example string")),
+        ),
     );
     let expected = str![[r#"
 error: 
@@ -242,11 +272,13 @@ error:
 #[test]
 fn test_source_annotation_standalone_multiline() {
     let source = "tests";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .annotation(Level::Help.span(0..5).label("Example string"))
-            .annotation(Level::Help.span(0..5).label("Second line")),
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .annotation(AnnotationKind::Context.span(0..5).label("Example string"))
+                .annotation(AnnotationKind::Context.span(0..5).label("Second line")),
+        ),
     );
     let expected = str![[r#"
 error: 
@@ -264,8 +296,8 @@ error:
 #[test]
 fn test_only_source() {
     let input = Level::Error
-        .title("")
-        .snippet(Snippet::source("").origin("file.rs"));
+        .message("")
+        .group(Group::new().element(Snippet::<Annotation<'_>>::source("").origin("file.rs")));
     let expected = str![[r#"
 error: 
  --> file.rs
@@ -279,8 +311,8 @@ error:
 fn test_anon_lines() {
     let source = "This is an example\nof content lines\n\nabc";
     let input = Level::Error
-        .title("")
-        .snippet(Snippet::source(source).line_start(56));
+        .message("")
+        .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56)));
     let expected = str![[r#"
 error: 
    |
@@ -295,12 +327,14 @@ LL | abc
 
 #[test]
 fn issue_130() {
-    let input = Level::Error.title("dummy").snippet(
-        Snippet::source("foo\nbar\nbaz")
-            .origin("file/path")
-            .line_start(3)
-            .fold(true)
-            .annotation(Level::Error.span(4..11)), // bar\nbaz
+    let input = Level::Error.message("dummy").group(
+        Group::new().element(
+            Snippet::source("foo\nbar\nbaz")
+                .origin("file/path")
+                .line_start(3)
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(4..11)),
+        ), // bar\nbaz
     );
 
     let expected = str![[r#"
@@ -321,12 +355,14 @@ fn unterminated_string_multiline() {
 a\"
 // ...
 ";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .fold(true)
-            .annotation(Level::Error.span(0..10)), // 1..10 works
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(0..10)),
+        ), // 1..10 works
     );
     let expected = str![[r#"
 error: 
@@ -343,11 +379,13 @@ error:
 #[test]
 fn char_and_nl_annotate_char() {
     let source = "a\r\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(0..2)), // a\r
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(0..2)),
+        ), // a\r
     );
     let expected = str![[r#"
 error: 
@@ -364,11 +402,13 @@ error:
 #[test]
 fn char_eol_annotate_char() {
     let source = "a\r\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(0..3)), // a\r\n
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(0..3)),
+        ), // a\r\n
     );
     let expected = str![[r#"
 error: 
@@ -384,10 +424,12 @@ error:
 
 #[test]
 fn char_eol_annotate_char_double_width() {
-    let snippets = Level::Error.title("").snippet(
-        Snippet::source("こん\r\nにちは\r\n世界")
-            .origin("<current file>")
-            .annotation(Level::Error.span(3..8)), // ん\r\n
+    let snippets = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source("こん\r\nにちは\r\n世界")
+                .origin("<current file>")
+                .annotation(AnnotationKind::Primary.span(3..8)),
+        ), // ん\r\n
     );
 
     let expected = str![[r#"
@@ -407,11 +449,13 @@ error:
 #[test]
 fn annotate_eol() {
     let source = "a\r\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(1..2)), // \r
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(1..2)),
+        ), // \r
     );
     let expected = str![[r#"
 error: 
@@ -428,11 +472,13 @@ error:
 #[test]
 fn annotate_eol2() {
     let source = "a\r\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(1..3)), // \r\n
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(1..3)),
+        ), // \r\n
     );
     let expected = str![[r#"
 error: 
@@ -449,11 +495,13 @@ error:
 #[test]
 fn annotate_eol3() {
     let source = "a\r\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(2..3)), // \n
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(2..3)),
+        ), // \n
     );
     let expected = str![[r#"
 error: 
@@ -470,11 +518,13 @@ error:
 #[test]
 fn annotate_eol4() {
     let source = "a\r\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(2..2)), // \n
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(2..2)),
+        ), // \n
     );
     let expected = str![[r#"
 error: 
@@ -490,10 +540,12 @@ error:
 
 #[test]
 fn annotate_eol_double_width() {
-    let snippets = Level::Error.title("").snippet(
-        Snippet::source("こん\r\nにちは\r\n世界")
-            .origin("<current file>")
-            .annotation(Level::Error.span(7..8)), // \n
+    let snippets = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source("こん\r\nにちは\r\n世界")
+                .origin("<current file>")
+                .annotation(AnnotationKind::Primary.span(7..8)),
+        ), // \n
     );
 
     let expected = str![[r#"
@@ -513,11 +565,13 @@ error:
 #[test]
 fn multiline_eol_start() {
     let source = "a\r\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(1..4)), // \r\nb
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(1..4)),
+        ), // \r\nb
     );
     let expected = str![[r#"
 error: 
@@ -535,11 +589,13 @@ error:
 #[test]
 fn multiline_eol_start2() {
     let source = "a\r\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(2..4)), // \nb
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(2..4)),
+        ), // \nb
     );
     let expected = str![[r#"
 error: 
@@ -557,11 +613,13 @@ error:
 #[test]
 fn multiline_eol_start3() {
     let source = "a\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(1..3)), // \nb
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(1..3)),
+        ), // \nb
     );
     let expected = str![[r#"
 error: 
@@ -578,10 +636,12 @@ error:
 
 #[test]
 fn multiline_eol_start_double_width() {
-    let snippets = Level::Error.title("").snippet(
-        Snippet::source("こん\r\nにちは\r\n世界")
-            .origin("<current file>")
-            .annotation(Level::Error.span(7..11)), // \r\nに
+    let snippets = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source("こん\r\nにちは\r\n世界")
+                .origin("<current file>")
+                .annotation(AnnotationKind::Primary.span(7..11)),
+        ), // \r\nに
     );
 
     let expected = str![[r#"
@@ -602,11 +662,13 @@ error:
 #[test]
 fn multiline_eol_start_eol_end() {
     let source = "a\nb\nc";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(1..4)), // \nb\n
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(1..4)),
+        ), // \nb\n
     );
     let expected = str![[r#"
 error: 
@@ -625,11 +687,13 @@ error:
 #[test]
 fn multiline_eol_start_eol_end2() {
     let source = "a\r\nb\r\nc";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(2..5)), // \nb\r
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(2..5)),
+        ), // \nb\r
     );
     let expected = str![[r#"
 error: 
@@ -648,11 +712,13 @@ error:
 #[test]
 fn multiline_eol_start_eol_end3() {
     let source = "a\r\nb\r\nc";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(2..6)), // \nb\r\n
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(2..6)),
+        ), // \nb\r\n
     );
     let expected = str![[r#"
 error: 
@@ -671,11 +737,13 @@ error:
 #[test]
 fn multiline_eol_start_eof_end() {
     let source = "a\r\nb";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(1..5)), // \r\nb(EOF)
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(1..5)),
+        ), // \r\nb(EOF)
     );
     let expected = str![[r#"
 error: 
@@ -693,11 +761,13 @@ error:
 #[test]
 fn multiline_eol_start_eof_end_double_width() {
     let source = "ん\r\nに";
-    let input = Level::Error.title("").snippet(
-        Snippet::source(source)
-            .origin("file/path")
-            .line_start(3)
-            .annotation(Level::Error.span(3..9)), // \r\nに(EOF)
+    let input = Level::Error.message("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("file/path")
+                .line_start(3)
+                .annotation(AnnotationKind::Primary.span(3..9)),
+        ), // \r\nに(EOF)
     );
     let expected = str![[r#"
 error: 
@@ -715,20 +785,22 @@ error:
 #[test]
 fn two_single_line_same_line() {
     let source = r#"bar = { version = "0.1.0", optional = true }"#;
-    let input = Level::Error.title("unused optional dependency").snippet(
-        Snippet::source(source)
-            .origin("Cargo.toml")
-            .line_start(4)
-            .annotation(
-                Level::Error
-                    .span(0..3)
-                    .label("I need this to be really long so I can test overlaps"),
-            )
-            .annotation(
-                Level::Info
-                    .span(27..42)
-                    .label("This should also be long but not too long"),
-            ),
+    let input = Level::Error.message("unused optional dependency").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("Cargo.toml")
+                .line_start(4)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(0..3)
+                        .label("I need this to be really long so I can test overlaps"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(27..42)
+                        .label("This should also be long but not too long"),
+                ),
+        ),
     );
     let expected = str![[r#"
 error: unused optional dependency
@@ -750,19 +822,21 @@ this is another line
 so is this
 bar = { version = "0.1.0", optional = true }
 "#;
-    let input = Level::Error.title("unused optional dependency").snippet(
-        Snippet::source(source)
-            .line_start(4)
-            .annotation(
-                Level::Error
-                    .span(41..119)
-                    .label("I need this to be really long so I can test overlaps"),
-            )
-            .annotation(
-                Level::Info
-                    .span(27..42)
-                    .label("This should also be long but not too long"),
-            ),
+    let input = Level::Error.message("unused optional dependency").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(4)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(41..119)
+                        .label("I need this to be really long so I can test overlaps"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(27..42)
+                        .label("This should also be long but not too long"),
+                ),
+        ),
     );
     let expected = str![[r#"
 error: unused optional dependency
@@ -787,24 +861,26 @@ this is another line
 so is this
 bar = { version = "0.1.0", optional = true }
 "#;
-    let input = Level::Error.title("unused optional dependency").snippet(
-        Snippet::source(source)
-            .line_start(4)
-            .annotation(
-                Level::Error
-                    .span(41..119)
-                    .label("I need this to be really long so I can test overlaps"),
-            )
-            .annotation(
-                Level::Error
-                    .span(8..102)
-                    .label("I need this to be really long so I can test overlaps"),
-            )
-            .annotation(
-                Level::Info
-                    .span(27..42)
-                    .label("This should also be long but not too long"),
-            ),
+    let input = Level::Error.message("unused optional dependency").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(4)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(41..119)
+                        .label("I need this to be really long so I can test overlaps"),
+                )
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(8..102)
+                        .label("I need this to be really long so I can test overlaps"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(27..42)
+                        .label("This should also be long but not too long"),
+                ),
+        ),
     );
     let expected = str![[r#"
 error: unused optional dependency
@@ -833,29 +909,31 @@ so is this
 bar = { version = "0.1.0", optional = true }
 this is another line
 "#;
-    let input = Level::Error.title("unused optional dependency").snippet(
-        Snippet::source(source)
-            .line_start(4)
-            .annotation(
-                Level::Error
-                    .span(41..119)
-                    .label("I need this to be really long so I can test overlaps"),
-            )
-            .annotation(
-                Level::Error
-                    .span(8..102)
-                    .label("I need this to be really long so I can test overlaps"),
-            )
-            .annotation(
-                Level::Error
-                    .span(48..126)
-                    .label("I need this to be really long so I can test overlaps"),
-            )
-            .annotation(
-                Level::Info
-                    .span(27..42)
-                    .label("This should also be long but not too long"),
-            ),
+    let input = Level::Error.message("unused optional dependency").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(4)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(41..119)
+                        .label("I need this to be really long so I can test overlaps"),
+                )
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(8..102)
+                        .label("I need this to be really long so I can test overlaps"),
+                )
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(48..126)
+                        .label("I need this to be really long so I can test overlaps"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(27..42)
+                        .label("This should also be long but not too long"),
+                ),
+        ),
     );
     let expected = str![[r#"
 error: unused optional dependency
@@ -882,11 +960,13 @@ error: unused optional dependency
 #[test]
 fn origin_correct_start_line() {
     let source = "aaa\nbbb\nccc\nddd\n";
-    let input = Level::Error.title("title").snippet(
-        Snippet::source(source)
-            .origin("origin.txt")
-            .fold(false)
-            .annotation(Level::Error.span(8..8 + 3).label("annotation")),
+    let input = Level::Error.message("title").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("origin.txt")
+                .fold(false)
+                .annotation(AnnotationKind::Primary.span(8..8 + 3).label("annotation")),
+        ),
     );
 
     let expected = str![[r#"
@@ -906,11 +986,17 @@ error: title
 #[test]
 fn origin_correct_mid_line() {
     let source = "aaa\nbbb\nccc\nddd\n";
-    let input = Level::Error.title("title").snippet(
-        Snippet::source(source)
-            .origin("origin.txt")
-            .fold(false)
-            .annotation(Level::Error.span(8 + 1..8 + 3).label("annotation")),
+    let input = Level::Error.message("title").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("origin.txt")
+                .fold(false)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(8 + 1..8 + 3)
+                        .label("annotation"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index f8b36d8..47a3399 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -2,7 +2,7 @@
 //!
 //! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs
 
-use annotate_snippets::{Level, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Origin, Renderer, Snippet};
 
 use snapbox::{assert_data_eq, str};
 
@@ -12,12 +12,14 @@ fn ends_on_col0() {
 fn foo() {
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(10..13).label("test")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(10..13).label("test")),
+        ),
     );
 
     let expected = str![[r#"
@@ -40,12 +42,14 @@ fn foo() {
 
   }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(10..17).label("test")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(10..17).label("test")),
+        ),
     );
 
     let expected = str![[r#"
@@ -70,17 +74,23 @@ fn foo() {
   X2 Y2
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..32).label("`X` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(17..35)
-                    .label("`Y` is a good letter too"),
-            ),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(14..32)
+                        .label("`X` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(17..35)
+                        .label("`Y` is a good letter too"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -107,17 +117,23 @@ fn foo() {
   Y1 X1
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..27).label("`X` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(17..24)
-                    .label("`Y` is a good letter too"),
-            ),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(14..27)
+                        .label("`X` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(17..24)
+                        .label("`Y` is a good letter too"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -145,17 +161,23 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(17..38).label("`X` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(31..49)
-                    .label("`Y` is a good letter too"),
-            ),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(17..38)
+                        .label("`X` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(31..49)
+                        .label("`Y` is a good letter too"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -183,18 +205,24 @@ fn foo() {
   X2 Y2 Z2
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..38).label("`X` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(17..41)
-                    .label("`Y` is a good letter too"),
-            )
-            .annotation(Level::Warning.span(20..44).label("`Z` label")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(14..38)
+                        .label("`X` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(17..41)
+                        .label("`Y` is a good letter too"),
+                )
+                .annotation(AnnotationKind::Context.span(20..44).label("`Z` label")),
+        ),
     );
 
     let expected = str![[r#"
@@ -224,18 +252,24 @@ fn foo() {
   X2 Y2 Z2
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..38).label("`X` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(14..38)
-                    .label("`Y` is a good letter too"),
-            )
-            .annotation(Level::Warning.span(14..38).label("`Z` label")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(14..38)
+                        .label("`X` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(14..38)
+                        .label("`Y` is a good letter too"),
+                )
+                .annotation(AnnotationKind::Context.span(14..38).label("`Z` label")),
+        ),
     );
 
     // This should have a `^` but we currently don't support the idea of a
@@ -247,7 +281,7 @@ error: foo
 3 | /   X0 Y0 Z0
 4 | |   X1 Y1 Z1
 5 | |   X2 Y2 Z2
-  | |    -
+  | |    ^
   | |    |
   | |    `X` is a good letter
   | |____`Y` is a good letter too
@@ -266,18 +300,24 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(17..27).label("`X` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(28..44)
-                    .label("`Y` is a good letter too"),
-            )
-            .annotation(Level::Warning.span(36..52).label("`Z`")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(17..27)
+                        .label("`X` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(28..44)
+                        .label("`Y` is a good letter too"),
+                )
+                .annotation(AnnotationKind::Context.span(36..52).label("`Z`")),
+        ),
     );
 
     let expected = str![[r#"
@@ -310,17 +350,23 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..27).label("`X` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(39..55)
-                    .label("`Y` is a good letter too"),
-            ),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(14..27)
+                        .label("`X` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(39..55)
+                        .label("`Y` is a good letter too"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -348,17 +394,23 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(17..27).label("`X` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(31..55)
-                    .label("`Y` is a good letter too"),
-            ),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(17..27)
+                        .label("`X` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(31..55)
+                        .label("`Y` is a good letter too"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -385,19 +437,25 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(18..25).label(""))
-            .annotation(Level::Warning.span(14..27).label("`a` is a good letter"))
-            .annotation(Level::Warning.span(22..23).label("")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(18..25).label(""))
+                .annotation(
+                    AnnotationKind::Context
+                        .span(14..27)
+                        .label("`a` is a good letter"),
+                )
+                .annotation(AnnotationKind::Context.span(22..23).label("")),
+        ),
     );
 
     let expected = str![[r#"
 error: foo
- --> test.rs:3:3
+ --> test.rs:3:7
   |
 3 |   a { b { c } d }
   |   ----^^^^-^^-- `a` is a good letter
@@ -412,13 +470,19 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..27).label("`a` is a good letter"))
-            .annotation(Level::Warning.span(18..25).label("")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(14..27)
+                        .label("`a` is a good letter"),
+                )
+                .annotation(AnnotationKind::Context.span(18..25).label("")),
+        ),
     );
 
     let expected = str![[r#"
@@ -438,19 +502,25 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(18..25).label("`b` is a good letter"))
-            .annotation(Level::Warning.span(14..27).label(""))
-            .annotation(Level::Warning.span(22..23).label("")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(18..25)
+                        .label("`b` is a good letter"),
+                )
+                .annotation(AnnotationKind::Context.span(14..27).label(""))
+                .annotation(AnnotationKind::Context.span(22..23).label("")),
+        ),
     );
 
     let expected = str![[r#"
 error: foo
- --> test.rs:3:3
+ --> test.rs:3:7
   |
 3 |   a { b { c } d }
   |   ----^^^^-^^--
@@ -467,13 +537,19 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..27).label(""))
-            .annotation(Level::Warning.span(18..25).label("`b` is a good letter")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(14..27).label(""))
+                .annotation(
+                    AnnotationKind::Context
+                        .span(18..25)
+                        .label("`b` is a good letter"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -495,13 +571,19 @@ fn foo() {
   a  bc  d
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..18).label("`a` is a good letter"))
-            .annotation(Level::Warning.span(18..22).label("")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(14..18)
+                        .label("`a` is a good letter"),
+                )
+                .annotation(AnnotationKind::Context.span(18..22).label("")),
+        ),
     );
 
     let expected = str![[r#"
@@ -523,13 +605,15 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..27).label(""))
-            .annotation(Level::Warning.span(18..25).label("")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(14..27).label(""))
+                .annotation(AnnotationKind::Context.span(18..25).label("")),
+        ),
     );
 
     let expected = str![[r#"
@@ -549,19 +633,21 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(18..25).label(""))
-            .annotation(Level::Warning.span(14..27).label(""))
-            .annotation(Level::Warning.span(22..23).label("")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(18..25).label(""))
+                .annotation(AnnotationKind::Context.span(14..27).label(""))
+                .annotation(AnnotationKind::Context.span(22..23).label("")),
+        ),
     );
 
     let expected = str![[r#"
 error: foo
- --> test.rs:3:3
+ --> test.rs:3:7
   |
 3 |   a { b { c } d }
   |   ----^^^^-^^--
@@ -576,13 +662,23 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..27).label("`a` is a good letter"))
-            .annotation(Level::Warning.span(18..25).label("`b` is a good letter")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(14..27)
+                        .label("`a` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(18..25)
+                        .label("`b` is a good letter"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -605,12 +701,18 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..27).label("`a` is a good letter")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(14..27)
+                        .label("`a` is a good letter"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -630,12 +732,14 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(14..27).label("")),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(14..27).label("")),
+        ),
     );
 
     let expected = str![[r#"
@@ -668,17 +772,23 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(17..27).label("`X` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(31..76)
-                    .label("`Y` is a good letter too"),
-            ),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(17..27)
+                        .label("`X` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(31..76)
+                        .label("`Y` is a good letter too"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -722,17 +832,23 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.title("foo").snippet(
-        Snippet::source(source)
-            .line_start(1)
-            .origin("test.rs")
-            .fold(true)
-            .annotation(Level::Error.span(17..73).label("`Y` is a good letter"))
-            .annotation(
-                Level::Warning
-                    .span(37..56)
-                    .label("`Z` is a good letter too"),
-            ),
+    let input = Level::Error.message("foo").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("test.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(17..73)
+                        .label("`Y` is a good letter"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(37..56)
+                        .label("`Z` is a good letter too"),
+                ),
+        ),
     );
 
     let expected = str![[r#"
@@ -771,24 +887,34 @@ fn issue_91334() {
 fn f(){||yield(((){),
 "#;
     let input = Level::Error
-        .title("this file contains an unclosed delimiter")
-        .snippet(
-            Snippet::source(source)
-                .line_start(1)
-                .origin("$DIR/issue-91334.rs")
-                .fold(true)
-                .annotation(Level::Warning.span(151..152).label("unclosed delimiter"))
-                .annotation(Level::Warning.span(159..160).label("unclosed delimiter"))
-                .annotation(
-                    Level::Warning
-                        .span(164..164)
-                        .label("missing open `(` for this delimiter"),
-                )
-                .annotation(Level::Error.span(167..167)),
+        .message("this file contains an unclosed delimiter")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .line_start(1)
+                    .origin("$DIR/issue-91334.rs")
+                    .fold(true)
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(151..152)
+                            .label("unclosed delimiter"),
+                    )
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(159..160)
+                            .label("unclosed delimiter"),
+                    )
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(164..164)
+                            .label("missing open `(` for this delimiter"),
+                    )
+                    .annotation(AnnotationKind::Primary.span(167..167)),
+            ),
         );
     let expected = str![[r#"
 error: this file contains an unclosed delimiter
-  --> $DIR/issue-91334.rs:7:7
+  --> $DIR/issue-91334.rs:7:23
    |
 LL | fn f(){||yield(((){),
    |       -       -    - ^
@@ -832,38 +958,43 @@ fn main() {
 }
 "#;
     let input = Level::Error
-        .title("`break` with value from a `while` loop")
+        .message("`break` with value from a `while` loop")
         .id("E0571")
-        .snippet(
-            Snippet::source(source)
-                .line_start(1)
-                .origin("$DIR/issue-114529-illegal-break-with-value.rs")
-                .fold(true)
-                .annotation(
-                    Level::Error
-                        .span(483..581)
-                        .label("can only break with a value inside `loop` or breakable block"),
-                )
-                .annotation(
-                    Level::Warning
-                        .span(462..472)
-                        .label("you can't `break` with a value in a `while` loop"),
-                ),
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .line_start(1)
+                    .origin("$DIR/issue-114529-illegal-break-with-value.rs")
+                    .fold(true)
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(483..581)
+                            .label("can only break with a value inside `loop` or breakable block"),
+                    )
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(462..472)
+                            .label("you can't `break` with a value in a `while` loop"),
+                    ),
+            ),
         )
-        .footer(
-            Level::Help
-                .title("use `break` on its own without a value inside this `while` loop")
-                .snippet(
+        .group(
+            Group::new()
+                .element(
+                    Level::Help
+                        .title("use `break` on its own without a value inside this `while` loop"),
+                )
+                .element(
                     Snippet::source(source)
                         .line_start(1)
                         .origin("$DIR/issue-114529-illegal-break-with-value.rs")
                         .fold(true)
-                        .annotation(Level::Help.span(483..581).label("break")),
+                        .annotation(AnnotationKind::Context.span(483..581).label("break")),
                 ),
         );
     let expected = str![[r#"
 error[E0571]: `break` with value from a `while` loop
-  --> $DIR/issue-114529-illegal-break-with-value.rs:21:5
+  --> $DIR/issue-114529-illegal-break-with-value.rs:22:9
    |
 LL |       while true {
    |       ---------- you can't `break` with a value in a `while` loop
@@ -871,6 +1002,7 @@ LL | /         break (|| { //~ ERROR `break` with value from a `while` loop
 LL | |             let local = 9;
 LL | |         });
    | |__________^ can only break with a value inside `loop` or breakable block
+   |
 help: use `break` on its own without a value inside this `while` loop
   --> $DIR/issue-114529-illegal-break-with-value.rs:22:9
    |
@@ -1034,48 +1166,50 @@ fn nsize() {
     }
 }
 "#;
-    let input = Level::Error
-        .title("`V0usize` cannot be safely transmuted into `[usize; 2]`")
-        .id("E0277")
-        .snippet(
-            Snippet::source(source)
-                .line_start(1)
-                .origin("$DIR/primitive_reprs_should_have_correct_length.rs")
-                .fold(true)
-                .annotation(
-                    Level::Error
-                        .span(4375..4381)
-                        .label("the size of `V0usize` is smaller than the size of `[usize; 2]`"),
-                ),
-        )
-        .footer(
-            Level::Note
-                .title("required by a bound in `is_transmutable`")
-                .snippet(
+    let input =
+        Level::Error
+            .message("`V0usize` cannot be safely transmuted into `[usize; 2]`")
+            .id("E0277")
+            .group(
+                Group::new().element(
                     Snippet::source(source)
                         .line_start(1)
                         .origin("$DIR/primitive_reprs_should_have_correct_length.rs")
                         .fold(true)
-                        .annotation(
-                            Level::Note
-                                .span(225..240)
-                                .label("required by a bound in this function"),
-                        )
-                        .annotation(
-                            Level::Error
-                                .span(276..470)
-                                .label("required by this bound in `is_transmutable`"),
-                        ),
+                        .annotation(AnnotationKind::Primary.span(4375..4381).label(
+                            "the size of `V0usize` is smaller than the size of `[usize; 2]`",
+                        )),
                 ),
-        );
+            )
+            .group(
+                Group::new()
+                    .element(Level::Note.title("required by a bound in `is_transmutable`"))
+                    .element(
+                        Snippet::source(source)
+                            .line_start(1)
+                            .origin("$DIR/primitive_reprs_should_have_correct_length.rs")
+                            .fold(true)
+                            .annotation(
+                                AnnotationKind::Context
+                                    .span(225..240)
+                                    .label("required by a bound in this function"),
+                            )
+                            .annotation(
+                                AnnotationKind::Primary
+                                    .span(276..470)
+                                    .label("required by this bound in `is_transmutable`"),
+                            ),
+                    ),
+            );
     let expected = str![[r#"
 error[E0277]: `V0usize` cannot be safely transmuted into `[usize; 2]`
   --> $DIR/primitive_reprs_should_have_correct_length.rs:144:44
    |
 LL |         assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted
    |                                            ^^^^^^ the size of `V0usize` is smaller than the size of `[usize; 2]`
+   |
 note: required by a bound in `is_transmutable`
-  --> $DIR/primitive_reprs_should_have_correct_length.rs:10:12
+  --> $DIR/primitive_reprs_should_have_correct_length.rs:12:14
    |
 LL |       pub fn is_transmutable<Src, Dst>()
    |              --------------- required by a bound in this function
@@ -1120,20 +1254,23 @@ fn main() {
 }
 "#;
     let input = Level::Error
-        .title("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`").id("E0277")
-        .snippet(
+        .message("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`")
+        .id("E027s7")
+        .group(
+            Group::new().element(
                 Snippet::source(source)
                     .line_start(1)
                     .fold(true)
                     .origin("$DIR/align-fail.rs")
                     .annotation(
-                       Level::Error
+                        AnnotationKind::Primary
                             .span(442..459)
                             .label("the minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2)")
                     ),
+            ),
         );
     let expected = str![[r#"
-error[E0277]: `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`
+error[E027s7]: `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`
   --> $DIR/align-fail.rs:21:55
    |
 LL | ...ic [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`
@@ -1185,38 +1322,39 @@ fn g() {
 }
 fn main() {}
 "#;
-    let input =
-        Level::Error
-            .title("expected function, found `{integer}`")
-            .id("E0618")
-            .snippet(
+    let input = Level::Error
+        .message("expected function, found `{integer}`")
+        .id("E0618")
+        .group(
+            Group::new().element(
                 Snippet::source(source)
                     .line_start(1)
                     .origin("$DIR/missing-semicolon.rs")
                     .fold(true)
                     .annotation(
-                        Level::Warning
+                        AnnotationKind::Context
                             .span(108..144)
                             .label("call expression requires function"),
                     )
                     .annotation(
-                        Level::Warning
+                        AnnotationKind::Context
                             .span(89..90)
                             .label("`x` has type `{integer}`"),
                     )
-                    .annotation(Level::Warning.span(109..109).label(
+                    .annotation(AnnotationKind::Context.span(109..109).label(
                         "help: consider using a semicolon here to finish the statement: `;`",
                     ))
-                    .annotation(Level::Error.span(108..109)),
-            );
+                    .annotation(AnnotationKind::Primary.span(108..109)),
+            ),
+        );
     let expected = str![[r#"
 error[E0618]: expected function, found `{integer}`
-  --> $DIR/missing-semicolon.rs:4:9
+  --> $DIR/missing-semicolon.rs:5:13
    |
 LL |       let x = 5;
    |           - `x` has type `{integer}`
 LL |       let y = x //~ ERROR expected function
-   |               -- help: consider using a semicolon here to finish the statement: `;`
+   |               ^- help: consider using a semicolon here to finish the statement: `;`
    |  _____________|
    | |
 LL | |     () //~ ERROR expected `;`, found `}`
@@ -1276,44 +1414,55 @@ macro_rules! outer_macro {
 outer_macro!(FirstStruct, FirstAttrStruct);
 "#;
     let input = Level::Warning
-        .title("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module")
-        .snippet(
-            Snippet::source(aux_source)
-                .line_start(1)
-                .origin("$DIR/auxiliary/nested-macro-rules.rs")
-                .fold(true)
-                .annotation(
-                    Level::Warning
-                        .span(41..65)
-                        .label("in this expansion of `nested_macro_rules::outer_macro!`"),
+        .message("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module")
+        .group(
+            Group::new()
+                .element(
+                    Snippet::source(aux_source)
+                        .line_start(1)
+                        .origin("$DIR/auxiliary/nested-macro-rules.rs")
+                        .fold(true)
+                        .annotation(
+                            AnnotationKind::Context
+                                .span(41..65)
+                                .label("in this expansion of `nested_macro_rules::outer_macro!`"),
+                        )
+                        .annotation(AnnotationKind::Primary.span(148..350)),
                 )
-                .annotation(Level::Error.span(148..350)),
-        )
-        .snippet(
-            Snippet::source(source)
-                .line_start(1)
-                .origin("$DIR/nested-macro-rules.rs")
-                .fold(true)
-                .annotation(
-                    Level::Warning
-                        .span(510..574)
-                        .label("in this macro invocation"),
+                .element(
+                    Snippet::source(source)
+                        .line_start(1)
+                        .origin("$DIR/nested-macro-rules.rs")
+                        .fold(true)
+                        .annotation(
+                            AnnotationKind::Context
+                                .span(510..574)
+                                .label("in this macro invocation"),
+                        ),
+                )
+                .element(
+                    Level::Help
+                        .title("remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`")
+                )
+                .element(
+                    Level::Note
+                        .title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute")
                 ),
         )
-        .footer(Level::Help.title("remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`"))
-        .footer(Level::Note.title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute"))
-        .footer(
-            Level::Note.title("the lint level is defined here").snippet(
-                Snippet::source(source)
-                    .line_start(1)
-                    .origin("$DIR/nested-macro-rules.rs")
-                    .fold(true)
-                    .annotation(Level::Error.span(224..245)),
-            ),
+        .group(
+            Group::new()
+                .element(Level::Note.title("the lint level is defined here"))
+                .element(
+                    Snippet::source(source)
+                        .line_start(1)
+                        .origin("$DIR/nested-macro-rules.rs")
+                        .fold(true)
+                        .annotation(AnnotationKind::Primary.span(224..245)),
+                ),
         );
     let expected = str![[r#"
 warning: non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module
-  --> $DIR/auxiliary/nested-macro-rules.rs:4:1
+  --> $DIR/auxiliary/nested-macro-rules.rs:7:9
    |
 LL |   macro_rules! outer_macro {
    |   ------------------------ in this expansion of `nested_macro_rules::outer_macro!`
@@ -1328,8 +1477,9 @@ LL | |         }
    |
   ::: $DIR/nested-macro-rules.rs:23:5
    |
-LL |     nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct);
-   |     ---------------------------------------------------------------- in this macro invocation
+LL |       nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct);
+   |       ---------------------------------------------------------------- in this macro invocation
+   |
    = help: remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`
    = note: a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute
 note: the lint level is defined here
@@ -1397,24 +1547,26 @@ macro_rules! inline {
 }
 "#;
     let input = Level::Error
-        .title("can't call method `pow` on ambiguous numeric type `{integer}`")
+        .message("can't call method `pow` on ambiguous numeric type `{integer}`")
         .id("E0689")
-        .snippet(
-            Snippet::source(source)
-                .line_start(1)
-                .origin("$DIR/method-on-ambiguous-numeric-type.rs")
-                .fold(true)
-                .annotation(Level::Error.span(916..919)),
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .line_start(1)
+                    .origin("$DIR/method-on-ambiguous-numeric-type.rs")
+                    .fold(true)
+                    .annotation(AnnotationKind::Primary.span(916..919)),
+            ),
         )
-        .footer(
-            Level::Help
-                .title("you must specify a type for this binding, like `i32`")
-                .snippet(
+        .group(
+            Group::new()
+                .element(Level::Help.title("you must specify a type for this binding, like `i32`"))
+                .element(
                     Snippet::source(aux_source)
                         .line_start(1)
                         .origin("$DIR/auxiliary/macro-in-other-crate.rs")
                         .fold(true)
-                        .annotation(Level::Help.span(69..69).label(": i32")),
+                        .annotation(AnnotationKind::Context.span(69..69).label(": i32")),
                 ),
         );
     let expected = str![[r#"
@@ -1423,6 +1575,7 @@ error[E0689]: can't call method `pow` on ambiguous numeric type `{integer}`
    |
 LL |     bar.pow(2);
    |         ^^^
+   |
 help: you must specify a type for this binding, like `i32`
   --> $DIR/auxiliary/macro-in-other-crate.rs:3:35
    |
@@ -1458,16 +1611,18 @@ fn main() {}
 "#;
 
     let input = Level::Error
-        .title("type annotations needed")
+        .message("type annotations needed")
         .id("E0282")
-        .snippet(
-            Snippet::source(source)
-                .line_start(1)
-                .origin("$DIR/issue-42234-unknown-receiver-type.rs")
-                .fold(true)
-                .annotation(Level::Error.span(536..539).label(
-                    "cannot infer type of the type parameter `S` declared on the method `sum`",
-                )),
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .line_start(1)
+                    .origin("$DIR/issue-42234-unknown-receiver-type.rs")
+                    .fold(true)
+                    .annotation(AnnotationKind::Primary.span(536..539).label(
+                        "cannot infer type of the type parameter `S` declared on the method `sum`",
+                    )),
+            ),
         );
     let expected = str![[r#"
 error[E0282]: type annotations needed
@@ -1562,45 +1717,53 @@ fn main() {}
 "##;
 
     let input = Level::Error
-        .title(
+        .message(
             "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered"
         )
         .id("E0004")
-        .snippet(
-            Snippet::source(source)
-                .line_start(1)
-                .origin("$DIR/empty-match.rs")
-                .fold(true)
-                .annotation(
-                   Level::Error
-                        .span(2911..2928)
-                        .label("patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered")
-                ),
-        )
-        .footer(Level::Note.title("`NonEmptyEnum5` defined here")
-            .snippet(
+        .group(
+            Group::new().element(
                 Snippet::source(source)
                     .line_start(1)
                     .origin("$DIR/empty-match.rs")
                     .fold(true)
-                    .annotation(Level::Error.span(818..831))
-                    .annotation(Level::Warning.span(842..844).label("not covered"))
-                    .annotation(Level::Warning.span(854..856).label("not covered"))
-                    .annotation(Level::Warning.span(866..868).label("not covered"))
-                    .annotation(Level::Warning.span(878..880).label("not covered"))
-                    .annotation(Level::Warning.span(890..892).label("not covered"))
-            ))
-        .footer(Level::Note.title("the matched value is of type `NonEmptyEnum5`"))
-        .footer(Level::Note.title("match arms with guards don't count towards exhaustivity"))
-        .footer(
-            Level::Help
-                .title("ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms")
-                .snippet(
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(2911..2928)
+                            .label("patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered")
+                    ),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(Level::Note.title("`NonEmptyEnum5` defined here"))
+                .element(
+                    Snippet::source(source)
+                        .line_start(1)
+                        .origin("$DIR/empty-match.rs")
+                        .fold(true)
+                        .annotation(AnnotationKind::Primary.span(818..831))
+                        .annotation(AnnotationKind::Context.span(842..844).label("not covered"))
+                        .annotation(AnnotationKind::Context.span(854..856).label("not covered"))
+                        .annotation(AnnotationKind::Context.span(866..868).label("not covered"))
+                        .annotation(AnnotationKind::Context.span(878..880).label("not covered"))
+                        .annotation(AnnotationKind::Context.span(890..892).label("not covered"))
+                )
+                .element(Level::Note.title("the matched value is of type `NonEmptyEnum5`"))
+                .element(Level::Note.title("match arms with guards don't count towards exhaustivity"))
+        )
+        .group(
+            Group::new()
+                .element(
+                    Level::Help
+                        .title("ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms")
+                )
+                .element(
                     Snippet::source(source)
                         .line_start(1)
                         .origin("$DIR/empty-match.rs")
                         .fold(true)
-                        .annotation(Level::Help.span(485..485).label(",\n                _ => todo!()"))
+                        .annotation(AnnotationKind::Context.span(485..485).label(",\n                _ => todo!()"))
                 )
         );
     let expected = str![[r#"
@@ -1609,6 +1772,7 @@ error[E0004]: non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`,
    |
 LL |     match_guarded_arm!(NonEmptyEnum5::V1); //~ ERROR `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered
    |                        ^^^^^^^^^^^^^^^^^ patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered
+   |
 note: `NonEmptyEnum5` defined here
   --> $DIR/empty-match.rs:38:10
    |
@@ -1655,36 +1819,41 @@ fn main() {
 }
 "#;
     let input = Level::Error
-        .title("the trait alias `EqAlias` is not dyn compatible")
+        .message("the trait alias `EqAlias` is not dyn compatible")
         .id("E0038")
-        .snippet(
+        .group(
+            Group::new().element(
                 Snippet::source(source)
                     .line_start(1)
                     .origin("$DIR/object-fail.rs")
                     .fold(true)
                     .annotation(
-                        Level::Error
+                        AnnotationKind::Primary
                             .span(107..114)
                             .label("`EqAlias` is not dyn compatible"),
                     ),
-
+            ),
         )
-        .footer(
+        .group(
+            Group::new()
+                .element(
                     Level::Note
-                        .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>")
-                .snippet(
-                    Snippet::source("")
-                        .line_start(334)
-                        .origin("$SRC_DIR/core/src/cmp.rs")
+                        .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>"))
+                .element(
+                    Origin::new("$SRC_DIR/core/src/cmp.rs")
+                        .line(334)
+                        .char_column(14)
+                        .primary(true)
+                        .label("...because it uses `Self` as a type parameter")
 
                 )
-                .snippet(
+                .element(
                     Snippet::source(source)
                         .line_start(1)
                         .origin("$DIR/object-fail.rs")
                         .fold(true)
                         .annotation(
-                            Level::Warning
+                            AnnotationKind::Context
                                 .span(32..39)
                                 .label("this trait is not dyn compatible..."),
                         ),
@@ -1696,10 +1865,12 @@ error[E0038]: the trait alias `EqAlias` is not dyn compatible
    |
 LL |     let _: &dyn EqAlias = &123;
    |                 ^^^^^^^ `EqAlias` is not dyn compatible
+   |
 note: for a trait to be dyn compatible it needs to allow building a vtable
       for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
-  --> $SRC_DIR/core/src/cmp.rs
+  --> $SRC_DIR/core/src/cmp.rs:334:14
    |
+   = note: ...because it uses `Self` as a type parameter
    |
   ::: $DIR/object-fail.rs:3:7
    |

From 05632ab6702719a7a92393c3b1312db6a2cd472c Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 15 Apr 2025 05:53:45 -0600
Subject: [PATCH 278/302] refactor: Remove dependency on rustc_hash

---
 Cargo.lock          |  7 -------
 Cargo.toml          |  1 -
 src/renderer/mod.rs | 10 +++-------
 3 files changed, 3 insertions(+), 15 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index e8aebe5..7d73c76 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -23,7 +23,6 @@ dependencies = [
  "glob",
  "indexmap",
  "memchr",
- "rustc-hash",
  "serde",
  "snapbox",
  "toml",
@@ -508,12 +507,6 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
-[[package]]
-name = "rustc-hash"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
-
 [[package]]
 name = "rustix"
 version = "0.37.27"
diff --git a/Cargo.toml b/Cargo.toml
index 95067ce..8ed3a6a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -119,7 +119,6 @@ maintenance = { status = "actively-developed" }
 anstyle = "1.0.4"
 indexmap = "2.7.0"
 memchr = { version = "2.7.4", optional = true }
-rustc-hash = "2.1.0"
 unicode-width = "0.2.0"
 
 [dev-dependencies]
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 246afb6..6442ebf 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -46,18 +46,14 @@ use crate::{Annotation, AnnotationKind, Element, Group, Level, Message, Origin,
 pub use anstyle::*;
 use indexmap::IndexMap;
 use margin::Margin;
-use rustc_hash::{FxHashMap, FxHasher};
 use std::borrow::Cow;
 use std::cmp::{max, min, Ordering, Reverse};
-use std::collections::VecDeque;
-use std::hash::BuildHasherDefault;
+use std::collections::{HashMap, VecDeque};
 use stylesheet::Stylesheet;
 
 const ANONYMIZED_LINE_NUM: &str = "LL";
 pub const DEFAULT_TERM_WIDTH: usize = 140;
 
-type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
-
 /// A renderer for [`Message`]s
 #[derive(Clone, Debug)]
 pub struct Renderer {
@@ -642,7 +638,7 @@ impl Renderer {
         self.draw_col_separator_no_space(buffer, buffer_msg_line_offset, max_line_num_len + 1);
 
         // Contains the vertical lines' positions for active multiline annotations
-        let mut multilines = FxIndexMap::default();
+        let mut multilines = IndexMap::new();
 
         // Get the left-side margin to remove it
         let mut whitespace_margin = usize::MAX;
@@ -729,7 +725,7 @@ impl Renderer {
                 margin,
             );
 
-            let mut to_add = FxHashMap::default();
+            let mut to_add = HashMap::new();
 
             for (depth, style) in depths {
                 // FIXME(#120456) - is `swap_remove` correct?

From 1d047720fb8ab17e2b24040710139ac419d483c6 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 15 Apr 2025 05:59:59 -0600
Subject: [PATCH 279/302] refactor: Remove dependency on indexmap

---
 Cargo.lock          |  1 -
 Cargo.toml          |  1 -
 src/renderer/mod.rs | 10 +++++-----
 3 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 7d73c76..8838e31 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,7 +21,6 @@ dependencies = [
  "difference",
  "divan",
  "glob",
- "indexmap",
  "memchr",
  "serde",
  "snapbox",
diff --git a/Cargo.toml b/Cargo.toml
index 8ed3a6a..f30c64f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -117,7 +117,6 @@ maintenance = { status = "actively-developed" }
 
 [dependencies]
 anstyle = "1.0.4"
-indexmap = "2.7.0"
 memchr = { version = "2.7.4", optional = true }
 unicode-width = "0.2.0"
 
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 6442ebf..a956262 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -44,7 +44,6 @@ use crate::renderer::source_map::{AnnotatedLineInfo, Loc, SourceMap};
 use crate::renderer::styled_buffer::StyledBuffer;
 use crate::{Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Snippet, Title};
 pub use anstyle::*;
-use indexmap::IndexMap;
 use margin::Margin;
 use std::borrow::Cow;
 use std::cmp::{max, min, Ordering, Reverse};
@@ -638,7 +637,7 @@ impl Renderer {
         self.draw_col_separator_no_space(buffer, buffer_msg_line_offset, max_line_num_len + 1);
 
         // Contains the vertical lines' positions for active multiline annotations
-        let mut multilines = IndexMap::new();
+        let mut multilines = Vec::new();
 
         // Get the left-side margin to remove it
         let mut whitespace_margin = usize::MAX;
@@ -728,8 +727,9 @@ impl Renderer {
             let mut to_add = HashMap::new();
 
             for (depth, style) in depths {
-                // FIXME(#120456) - is `swap_remove` correct?
-                if multilines.swap_remove(&depth).is_none() {
+                if let Some(index) = multilines.iter().position(|(d, _)| d == &depth) {
+                    multilines.swap_remove(index);
+                } else {
                     to_add.insert(depth, style);
                 }
             }
@@ -834,7 +834,7 @@ impl Renderer {
                 }
             }
 
-            multilines.extend(&to_add);
+            multilines.extend(to_add);
         }
     }
 

From f0a8091673d07d8ab914472940ea9623ee3868b4 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 11 Feb 2025 12:57:48 -0700
Subject: [PATCH 280/302] feat: Add Suggestions

---
 src/renderer/mod.rs           | 615 +++++++++++++++++++++++++++++-
 src/renderer/source_map.rs    | 206 +++++++++-
 src/renderer/styled_buffer.rs |  29 ++
 src/renderer/stylesheet.rs    |   4 +
 src/snippet.rs                | 111 ++++++
 tests/fixtures/deserialize.rs |  46 ++-
 tests/formatter.rs            | 680 +++++++++++++++++++++++++++++++++-
 7 files changed, 1682 insertions(+), 9 deletions(-)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index a956262..fdd2b3e 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -36,18 +36,23 @@
 //! ```
 
 mod margin;
-mod source_map;
+pub(crate) mod source_map;
 mod styled_buffer;
 pub(crate) mod stylesheet;
 
-use crate::renderer::source_map::{AnnotatedLineInfo, Loc, SourceMap};
+use crate::renderer::source_map::{
+    AnnotatedLineInfo, LineInfo, Loc, SourceMap, SubstitutionHighlight,
+};
 use crate::renderer::styled_buffer::StyledBuffer;
-use crate::{Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Snippet, Title};
+use crate::{
+    Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Patch, Snippet, Title,
+};
 pub use anstyle::*;
 use margin::Margin;
 use std::borrow::Cow;
 use std::cmp::{max, min, Ordering, Reverse};
 use std::collections::{HashMap, VecDeque};
+use std::ops::Range;
 use stylesheet::Stylesheet;
 
 const ANONYMIZED_LINE_NUM: &str = "LL";
@@ -103,6 +108,8 @@ impl Renderer {
                 .effects(Effects::BOLD),
                 none: Style::new(),
                 context: BRIGHT_BLUE.effects(Effects::BOLD),
+                addition: AnsiColor::BrightGreen.on_default(),
+                removal: AnsiColor::BrightRed.on_default(),
             },
             ..Self::plain()
         }
@@ -213,6 +220,41 @@ impl Renderer {
         message: Message<'_>,
         max_line_num_len: usize,
     ) {
+        let og_primary_origin = message
+            .groups
+            .iter()
+            .find_map(|group| {
+                group.elements.iter().find_map(|s| match &s {
+                    Element::Cause(cause) => {
+                        if cause.markers.iter().any(|m| m.kind.is_primary()) {
+                            Some(cause.origin)
+                        } else {
+                            None
+                        }
+                    }
+                    Element::Origin(origin) => {
+                        if origin.primary {
+                            Some(Some(origin.origin))
+                        } else {
+                            None
+                        }
+                    }
+                    _ => None,
+                })
+            })
+            .unwrap_or(
+                message
+                    .groups
+                    .iter()
+                    .find_map(|group| {
+                        group.elements.iter().find_map(|s| match &s {
+                            Element::Cause(cause) => Some(cause.origin),
+                            Element::Origin(origin) => Some(Some(origin.origin)),
+                            _ => None,
+                        })
+                    })
+                    .unwrap_or_default(),
+            );
         let group_len = message.groups.len();
         for (g, group) in message.groups.into_iter().enumerate() {
             let primary_origin = group
@@ -258,6 +300,7 @@ impl Renderer {
                 }
             }
             let mut message_iter = group.elements.iter().enumerate().peekable();
+            let mut last_was_suggestion = false;
             while let Some((i, section)) = message_iter.next() {
                 let peek = message_iter.peek().map(|(_, s)| s).copied();
                 match &section {
@@ -276,6 +319,7 @@ impl Renderer {
                                 }
                             }),
                         );
+                        last_was_suggestion = false;
                     }
                     Element::Cause(cause) => {
                         if let Some((source_map, annotated_lines)) =
@@ -312,9 +356,25 @@ impl Renderer {
                                 }
                             }
                         }
+
+                        last_was_suggestion = false;
+                    }
+                    Element::Suggestion(suggestion) => {
+                        let source_map = SourceMap::new(suggestion.source, suggestion.line_start);
+                        self.emit_suggestion_default(
+                            buffer,
+                            suggestion,
+                            max_line_num_len,
+                            &source_map,
+                            primary_origin.or(og_primary_origin),
+                            last_was_suggestion,
+                        );
+                        last_was_suggestion = true;
                     }
+
                     Element::Origin(origin) => {
                         self.render_origin(buffer, max_line_num_len, origin);
+                        last_was_suggestion = false;
                     }
                     Element::ColumnSeparator(_) => {
                         self.draw_col_separator_no_space(
@@ -368,6 +428,7 @@ impl Renderer {
                     cause.markers.iter().any(|m| m.kind.is_primary()),
                     cause.markers.iter().any(|m| m.label.is_some()),
                 ),
+                Element::Suggestion(_) => (true, false),
                 Element::Origin(_) => (false, true),
             });
 
@@ -1389,6 +1450,534 @@ impl Renderer {
             .collect::<Vec<_>>()
     }
 
+    fn emit_suggestion_default(
+        &self,
+        buffer: &mut StyledBuffer,
+        suggestion: &Snippet<'_, Patch<'_>>,
+        max_line_num_len: usize,
+        sm: &SourceMap<'_>,
+        primary_origin: Option<&str>,
+        is_cont: bool,
+    ) {
+        let suggestions = sm.splice_lines(suggestion.markers.clone());
+
+        let buffer_offset = buffer.num_lines();
+        let mut row_num = buffer_offset + usize::from(!is_cont);
+        for (i, (complete, parts, highlights)) in suggestions.iter().enumerate() {
+            let has_deletion = parts
+                .iter()
+                .any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
+            let is_multiline = complete.lines().count() > 1;
+
+            if i == 0 {
+                self.draw_col_separator_no_space_with_style(
+                    buffer,
+                    '|',
+                    row_num - 1,
+                    max_line_num_len + 1,
+                    ElementStyle::LineNumber,
+                );
+            } else {
+                buffer.puts(
+                    row_num - 1,
+                    max_line_num_len + 1,
+                    "|",
+                    ElementStyle::LineNumber,
+                );
+            }
+            if suggestion.origin != primary_origin {
+                if let Some(origin) = suggestion.origin {
+                    let (loc, _) = sm.span_to_locations(parts[0].range.clone());
+                    // --> file.rs:line:col
+                    //  |
+                    let arrow = self.file_start();
+                    buffer.puts(row_num - 1, 0, arrow, ElementStyle::LineNumber);
+                    let message = format!("{}:{}:{}", origin, loc.line, loc.char + 1);
+                    if is_cont {
+                        buffer.append(row_num - 1, &message, ElementStyle::LineAndColumn);
+                    } else {
+                        let col = usize::max(max_line_num_len + 1, arrow.len());
+                        buffer.puts(row_num - 1, col, &message, ElementStyle::LineAndColumn);
+                    }
+                    for _ in 0..max_line_num_len {
+                        buffer.prepend(row_num - 1, " ", ElementStyle::NoStyle);
+                    }
+                    self.draw_col_separator_no_space(buffer, row_num, max_line_num_len + 1);
+                    row_num += 1;
+                }
+            }
+            let show_code_change = if has_deletion && !is_multiline {
+                DisplaySuggestion::Diff
+            } else if parts.len() == 1
+                && parts.first().map_or(false, |p| {
+                    p.replacement.ends_with('\n') && p.replacement.trim() == complete.trim()
+                })
+            {
+                // We are adding a line(s) of code before code that was already there.
+                DisplaySuggestion::Add
+            } else if (parts.len() != 1 || parts[0].replacement.trim() != complete.trim())
+                && !is_multiline
+            {
+                DisplaySuggestion::Underline
+            } else {
+                DisplaySuggestion::None
+            };
+
+            if let DisplaySuggestion::Diff = show_code_change {
+                row_num += 1;
+            }
+
+            let file_lines = sm.span_to_lines(parts[0].range.clone());
+            let (line_start, line_end) = sm.span_to_locations(parts[0].range.clone());
+            let mut lines = complete.lines();
+            if lines.clone().next().is_none() {
+                // Account for a suggestion to completely remove a line(s) with whitespace (#94192).
+                for line in line_start.line..=line_end.line {
+                    buffer.puts(
+                        row_num - 1 + line - line_start.line,
+                        0,
+                        &self.maybe_anonymized(line),
+                        ElementStyle::LineNumber,
+                    );
+                    buffer.puts(
+                        row_num - 1 + line - line_start.line,
+                        max_line_num_len + 1,
+                        "- ",
+                        ElementStyle::Removal,
+                    );
+                    buffer.puts(
+                        row_num - 1 + line - line_start.line,
+                        max_line_num_len + 3,
+                        &normalize_whitespace(sm.get_line(line).unwrap()),
+                        ElementStyle::Removal,
+                    );
+                }
+                row_num += line_end.line - line_start.line;
+            }
+            let mut last_pos = 0;
+            let mut is_item_attribute = false;
+            let mut unhighlighted_lines = Vec::new();
+            for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
+                last_pos = line_pos;
+
+                // Remember lines that are not highlighted to hide them if needed
+                if highlight_parts.is_empty() {
+                    unhighlighted_lines.push((line_pos, line));
+                    continue;
+                }
+                if highlight_parts.len() == 1
+                    && line.trim().starts_with("#[")
+                    && line.trim().ends_with(']')
+                {
+                    is_item_attribute = true;
+                }
+
+                match unhighlighted_lines.len() {
+                    0 => (),
+                    // Since we show first line, "..." line and last line,
+                    // There is no reason to hide if there are 3 or less lines
+                    // (because then we just replace a line with ... which is
+                    // not helpful)
+                    n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
+                        self.draw_code_line(
+                            buffer,
+                            &mut row_num,
+                            &[],
+                            p + line_start.line,
+                            l,
+                            show_code_change,
+                            max_line_num_len,
+                            &file_lines,
+                            is_multiline,
+                        );
+                    }),
+                    // Print first unhighlighted line, "..." and last unhighlighted line, like so:
+                    //
+                    // LL | this line was highlighted
+                    // LL | this line is just for context
+                    // ...
+                    // LL | this line is just for context
+                    // LL | this line was highlighted
+                    _ => {
+                        let last_line = unhighlighted_lines.pop();
+                        let first_line = unhighlighted_lines.drain(..).next();
+
+                        if let Some((p, l)) = first_line {
+                            self.draw_code_line(
+                                buffer,
+                                &mut row_num,
+                                &[],
+                                p + line_start.line,
+                                l,
+                                show_code_change,
+                                max_line_num_len,
+                                &file_lines,
+                                is_multiline,
+                            );
+                        }
+
+                        let placeholder = "...";
+                        let padding = str_width(placeholder);
+                        buffer.puts(
+                            row_num,
+                            max_line_num_len.saturating_sub(padding),
+                            placeholder,
+                            ElementStyle::LineNumber,
+                        );
+                        row_num += 1;
+
+                        if let Some((p, l)) = last_line {
+                            self.draw_code_line(
+                                buffer,
+                                &mut row_num,
+                                &[],
+                                p + line_start.line,
+                                l,
+                                show_code_change,
+                                max_line_num_len,
+                                &file_lines,
+                                is_multiline,
+                            );
+                        }
+                    }
+                }
+                self.draw_code_line(
+                    buffer,
+                    &mut row_num,
+                    highlight_parts,
+                    line_pos + line_start.line,
+                    line,
+                    show_code_change,
+                    max_line_num_len,
+                    &file_lines,
+                    is_multiline,
+                );
+            }
+
+            if matches!(show_code_change, DisplaySuggestion::Add) && is_item_attribute {
+                // The suggestion adds an entire line of code, ending on a newline, so we'll also
+                // print the *following* line, to provide context of what we're advising people to
+                // do. Otherwise you would only see contextless code that can be confused for
+                // already existing code, despite the colors and UI elements.
+                // We special case `#[derive(_)]\n` and other attribute suggestions, because those
+                // are the ones where context is most useful.
+                let file_lines = sm.span_to_lines(parts[0].range.end..parts[0].range.end);
+                let (lo, _) = sm.span_to_locations(parts[0].range.clone());
+                let line_num = lo.line;
+                if let Some(line) = sm.get_line(line_num) {
+                    let line = normalize_whitespace(line);
+                    self.draw_code_line(
+                        buffer,
+                        &mut row_num,
+                        &[],
+                        line_num + last_pos + 1,
+                        &line,
+                        DisplaySuggestion::None,
+                        max_line_num_len,
+                        &file_lines,
+                        is_multiline,
+                    );
+                }
+            }
+            // This offset and the ones below need to be signed to account for replacement code
+            // that is shorter than the original code.
+            let mut offsets: Vec<(usize, isize)> = Vec::new();
+            // Only show an underline in the suggestions if the suggestion is not the
+            // entirety of the code being shown and the displayed code is not multiline.
+            if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
+                show_code_change
+            {
+                for part in parts {
+                    let (span_start, span_end) = sm.span_to_locations(part.range.clone());
+                    let span_start_pos = span_start.display;
+                    let span_end_pos = span_end.display;
+
+                    // If this addition is _only_ whitespace, then don't trim it,
+                    // or else we're just not rendering anything.
+                    let is_whitespace_addition = part.replacement.trim().is_empty();
+
+                    // Do not underline the leading...
+                    let start = if is_whitespace_addition {
+                        0
+                    } else {
+                        part.replacement
+                            .len()
+                            .saturating_sub(part.replacement.trim_start().len())
+                    };
+                    // ...or trailing spaces. Account for substitutions containing unicode
+                    // characters.
+                    let sub_len: usize = str_width(if is_whitespace_addition {
+                        part.replacement
+                    } else {
+                        part.replacement.trim()
+                    });
+
+                    let offset: isize = offsets
+                        .iter()
+                        .filter_map(|(start, v)| {
+                            if span_start_pos < *start {
+                                None
+                            } else {
+                                Some(v)
+                            }
+                        })
+                        .sum();
+                    let underline_start = (span_start_pos + start) as isize + offset;
+                    let underline_end = (span_start_pos + start + sub_len) as isize + offset;
+                    assert!(underline_start >= 0 && underline_end >= 0);
+                    let padding: usize = max_line_num_len + 3;
+                    for p in underline_start..underline_end {
+                        if matches!(show_code_change, DisplaySuggestion::Underline)
+                            && is_different(sm, part.replacement, part.range.clone())
+                        {
+                            // If this is a replacement, underline with `~`, if this is an addition
+                            // underline with `+`.
+                            buffer.putc(
+                                row_num,
+                                (padding as isize + p) as usize,
+                                if part.is_addition(sm) { '+' } else { '~' },
+                                ElementStyle::Addition,
+                            );
+                        }
+                    }
+                    if let DisplaySuggestion::Diff = show_code_change {
+                        // Colorize removal with red in diff format.
+                        buffer.set_style_range(
+                            row_num - 2,
+                            (padding as isize + span_start_pos as isize) as usize,
+                            (padding as isize + span_end_pos as isize) as usize,
+                            ElementStyle::Removal,
+                            true,
+                        );
+                    }
+
+                    // length of the code after substitution
+                    let full_sub_len = str_width(part.replacement) as isize;
+
+                    // length of the code to be substituted
+                    let snippet_len = span_end_pos as isize - span_start_pos as isize;
+                    // For multiple substitutions, use the position *after* the previous
+                    // substitutions have happened, only when further substitutions are
+                    // located strictly after.
+                    offsets.push((span_end_pos, full_sub_len - snippet_len));
+                }
+                row_num += 1;
+            }
+
+            // if we elided some lines, add an ellipsis
+            if lines.next().is_some() {
+                let placeholder = "...";
+                let padding = str_width(placeholder);
+                buffer.puts(
+                    row_num,
+                    max_line_num_len.saturating_sub(padding),
+                    placeholder,
+                    ElementStyle::LineNumber,
+                );
+            } else {
+                let row = match show_code_change {
+                    DisplaySuggestion::Diff
+                    | DisplaySuggestion::Add
+                    | DisplaySuggestion::Underline => row_num - 1,
+                    DisplaySuggestion::None => row_num,
+                };
+                self.draw_col_separator_no_space_with_style(
+                    buffer,
+                    '|',
+                    row,
+                    max_line_num_len + 1,
+                    ElementStyle::LineNumber,
+                );
+                row_num = row + 1;
+            }
+        }
+    }
+
+    #[allow(clippy::too_many_arguments)]
+    fn draw_code_line(
+        &self,
+        buffer: &mut StyledBuffer,
+        row_num: &mut usize,
+        highlight_parts: &[SubstitutionHighlight],
+        line_num: usize,
+        line_to_add: &str,
+        show_code_change: DisplaySuggestion,
+        max_line_num_len: usize,
+        file_lines: &[&LineInfo<'_>],
+        is_multiline: bool,
+    ) {
+        if let DisplaySuggestion::Diff = show_code_change {
+            // We need to print more than one line if the span we need to remove is multiline.
+            // For more info: https://github.com/rust-lang/rust/issues/92741
+            let lines_to_remove = file_lines.iter().take(file_lines.len() - 1);
+            for (index, line_to_remove) in lines_to_remove.enumerate() {
+                buffer.puts(
+                    *row_num - 1,
+                    0,
+                    &self.maybe_anonymized(line_num + index),
+                    ElementStyle::LineNumber,
+                );
+                buffer.puts(
+                    *row_num - 1,
+                    max_line_num_len + 1,
+                    "- ",
+                    ElementStyle::Removal,
+                );
+                let line = normalize_whitespace(line_to_remove.line);
+                buffer.puts(
+                    *row_num - 1,
+                    max_line_num_len + 3,
+                    &line,
+                    ElementStyle::NoStyle,
+                );
+                *row_num += 1;
+            }
+            // If the last line is exactly equal to the line we need to add, we can skip both of
+            // them. This allows us to avoid output like the following:
+            // 2 - &
+            // 2 + if true { true } else { false }
+            // 3 - if true { true } else { false }
+            // If those lines aren't equal, we print their diff
+            let last_line = &file_lines.last().unwrap();
+            if last_line.line == line_to_add {
+                *row_num -= 2;
+            } else {
+                buffer.puts(
+                    *row_num - 1,
+                    0,
+                    &self.maybe_anonymized(line_num + file_lines.len() - 1),
+                    ElementStyle::LineNumber,
+                );
+                buffer.puts(
+                    *row_num - 1,
+                    max_line_num_len + 1,
+                    "- ",
+                    ElementStyle::Removal,
+                );
+                buffer.puts(
+                    *row_num - 1,
+                    max_line_num_len + 3,
+                    &normalize_whitespace(last_line.line),
+                    ElementStyle::NoStyle,
+                );
+                if line_to_add.trim().is_empty() {
+                    *row_num -= 1;
+                } else {
+                    // Check if after the removal, the line is left with only whitespace. If so, we
+                    // will not show an "addition" line, as removing the whole line is what the user
+                    // would really want.
+                    // For example, for the following:
+                    //   |
+                    // 2 -     .await
+                    // 2 +     (note the left over whitespace)
+                    //   |
+                    // We really want
+                    //   |
+                    // 2 -     .await
+                    //   |
+                    // *row_num -= 1;
+                    buffer.puts(
+                        *row_num,
+                        0,
+                        &self.maybe_anonymized(line_num),
+                        ElementStyle::LineNumber,
+                    );
+                    buffer.puts(*row_num, max_line_num_len + 1, "+ ", ElementStyle::Addition);
+                    buffer.append(
+                        *row_num,
+                        &normalize_whitespace(line_to_add),
+                        ElementStyle::NoStyle,
+                    );
+                }
+            }
+        } else if is_multiline {
+            buffer.puts(
+                *row_num,
+                0,
+                &self.maybe_anonymized(line_num),
+                ElementStyle::LineNumber,
+            );
+            match &highlight_parts {
+                [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
+                    buffer.puts(*row_num, max_line_num_len + 1, "+ ", ElementStyle::Addition);
+                }
+                [] => {
+                    // FIXME: needed? Doesn't get exercised in any test.
+                    self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
+                }
+                _ => {
+                    buffer.puts(*row_num, max_line_num_len + 1, "~", ElementStyle::Addition);
+                }
+            }
+            //   LL | line_to_add
+            //   ++^^^
+            //    |  |
+            //    |  magic `3`
+            //    `max_line_num_len`
+            buffer.puts(
+                *row_num,
+                max_line_num_len + 3,
+                &normalize_whitespace(line_to_add),
+                ElementStyle::NoStyle,
+            );
+        } else if let DisplaySuggestion::Add = show_code_change {
+            buffer.puts(
+                *row_num,
+                0,
+                &self.maybe_anonymized(line_num),
+                ElementStyle::LineNumber,
+            );
+            buffer.puts(*row_num, max_line_num_len + 1, "+ ", ElementStyle::Addition);
+            buffer.append(
+                *row_num,
+                &normalize_whitespace(line_to_add),
+                ElementStyle::NoStyle,
+            );
+        } else {
+            buffer.puts(
+                *row_num,
+                0,
+                &self.maybe_anonymized(line_num),
+                ElementStyle::LineNumber,
+            );
+            buffer.puts(
+                *row_num,
+                max_line_num_len + 1,
+                "| ",
+                ElementStyle::LineNumber,
+            );
+            buffer.append(
+                *row_num,
+                &normalize_whitespace(line_to_add),
+                ElementStyle::NoStyle,
+            );
+        }
+
+        // Colorize addition/replacements with green.
+        for &SubstitutionHighlight { start, end } in highlight_parts {
+            // This is a no-op for empty ranges
+            if start != end {
+                // Account for tabs when highlighting (#87972).
+                let tabs: usize = line_to_add
+                    .chars()
+                    .take(start)
+                    .map(|ch| match ch {
+                        '\t' => 3,
+                        _ => 0,
+                    })
+                    .sum();
+                buffer.set_style_range(
+                    *row_num,
+                    max_line_num_len + 3 + start + tabs,
+                    max_line_num_len + 3 + end + tabs,
+                    ElementStyle::Addition,
+                    true,
+                );
+            }
+        }
+        *row_num += 1;
+    }
+
     #[allow(clippy::too_many_arguments)]
     fn draw_line(
         &self,
@@ -1714,6 +2303,14 @@ impl LineAnnotation<'_> {
     }
 }
 
+#[derive(Clone, Copy, Debug)]
+pub(crate) enum DisplaySuggestion {
+    Underline,
+    Diff,
+    None,
+    Add,
+}
+
 // We replace some characters so the CLI output is always consistent and underlines aligned.
 // Keep the following list in sync with `rustc_span::char_width`.
 const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
@@ -1790,11 +2387,15 @@ pub(crate) enum ElementStyle {
     LabelSecondary,
     NoStyle,
     Level(Level),
+    Addition,
+    Removal,
 }
 
 impl ElementStyle {
     fn color_spec(&self, level: Level, stylesheet: &Stylesheet) -> Style {
         match self {
+            ElementStyle::Addition => stylesheet.addition,
+            ElementStyle::Removal => stylesheet.removal,
             ElementStyle::LineAndColumn => stylesheet.none,
             ElementStyle::LineNumber => stylesheet.line_no,
             ElementStyle::Quotation => stylesheet.none,
@@ -1826,6 +2427,14 @@ struct UnderlineParts {
     multiline_bottom_right_with_text: char,
 }
 
+/// Whether the original and suggested code are the same.
+pub(crate) fn is_different(sm: &SourceMap<'_>, suggested: &str, range: Range<usize>) -> bool {
+    match sm.span_to_snippet(range) {
+        Some(s) => s != suggested,
+        None => true,
+    }
+}
+
 #[cfg(test)]
 mod test {
     use super::OUTPUT_REPLACEMENTS;
diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs
index c29774a..416337c 100644
--- a/src/renderer/source_map.rs
+++ b/src/renderer/source_map.rs
@@ -1,12 +1,12 @@
-use crate::renderer::{char_width, num_overlap, LineAnnotation, LineAnnotationType};
-use crate::{Annotation, AnnotationKind};
+use crate::renderer::{char_width, is_different, num_overlap, LineAnnotation, LineAnnotationType};
+use crate::{Annotation, AnnotationKind, Patch};
 use std::cmp::{max, min};
 use std::ops::Range;
 
 #[derive(Debug)]
 pub(crate) struct SourceMap<'a> {
     lines: Vec<LineInfo<'a>>,
-    source: &'a str,
+    pub(crate) source: &'a str,
 }
 
 impl<'a> SourceMap<'a> {
@@ -101,6 +101,26 @@ impl<'a> SourceMap<'a> {
         (start, end)
     }
 
+    pub(crate) fn span_to_snippet(&self, span: Range<usize>) -> Option<&str> {
+        self.source.get(span)
+    }
+
+    pub(crate) fn span_to_lines(&self, span: Range<usize>) -> Vec<&LineInfo<'a>> {
+        let mut lines = vec![];
+        let start = span.start;
+        let end = span.end;
+        for line_info in &self.lines {
+            if start >= line_info.end_byte {
+                continue;
+            }
+            if end <= line_info.start_byte {
+                break;
+            }
+            lines.push(line_info);
+        }
+        lines
+    }
+
     pub(crate) fn annotated_lines(
         &self,
         annotations: Vec<Annotation<'a>>,
@@ -130,7 +150,7 @@ impl<'a> SourceMap<'a> {
         let mut multiline_annotations = vec![];
 
         for Annotation { range, label, kind } in annotations {
-            let (lo, mut hi) = self.span_to_locations(range);
+            let (lo, mut hi) = self.span_to_locations(range.clone());
 
             // Watch out for "empty spans". If we get a span like 6..6, we
             // want to just display a `^` at 6, so convert that to
@@ -302,6 +322,176 @@ impl<'a> SourceMap<'a> {
             annotated_line_infos.sort_by_key(|l| l.line_index);
         }
     }
+
+    pub(crate) fn splice_lines<'b>(
+        &'b self,
+        mut patches: Vec<Patch<'b>>,
+    ) -> Vec<(String, Vec<Patch<'b>>, Vec<Vec<SubstitutionHighlight>>)> {
+        fn push_trailing(
+            buf: &mut String,
+            line_opt: Option<&str>,
+            lo: &Loc,
+            hi_opt: Option<&Loc>,
+        ) -> usize {
+            let mut line_count = 0;
+            // Convert CharPos to Usize, as CharPose is character offset
+            // Extract low index and high index
+            let (lo, hi_opt) = (lo.char, hi_opt.map(|hi| hi.char));
+            if let Some(line) = line_opt {
+                if let Some(lo) = line.char_indices().map(|(i, _)| i).nth(lo) {
+                    // Get high index while account for rare unicode and emoji with char_indices
+                    let hi_opt = hi_opt.and_then(|hi| line.char_indices().map(|(i, _)| i).nth(hi));
+                    match hi_opt {
+                        // If high index exist, take string from low to high index
+                        Some(hi) if hi > lo => {
+                            // count how many '\n' exist
+                            line_count = line[lo..hi].matches('\n').count();
+                            buf.push_str(&line[lo..hi]);
+                        }
+                        Some(_) => (),
+                        // If high index absence, take string from low index till end string.len
+                        None => {
+                            // count how many '\n' exist
+                            line_count = line[lo..].matches('\n').count();
+                            buf.push_str(&line[lo..]);
+                        }
+                    }
+                }
+                // If high index is None
+                if hi_opt.is_none() {
+                    buf.push('\n');
+                }
+            }
+            line_count
+        }
+        // Assumption: all spans are in the same file, and all spans
+        // are disjoint. Sort in ascending order.
+        patches.sort_by_key(|p| p.range.start);
+
+        // Find the bounding span.
+        let Some(lo) = patches.iter().map(|p| p.range.start).min() else {
+            return Vec::new();
+        };
+        let Some(hi) = patches.iter().map(|p| p.range.end).max() else {
+            return Vec::new();
+        };
+
+        let lines = self.span_to_lines(lo..hi);
+
+        let mut highlights = vec![];
+        // To build up the result, we do this for each span:
+        // - push the line segment trailing the previous span
+        //   (at the beginning a "phantom" span pointing at the start of the line)
+        // - push lines between the previous and current span (if any)
+        // - if the previous and current span are not on the same line
+        //   push the line segment leading up to the current span
+        // - splice in the span substitution
+        //
+        // Finally push the trailing line segment of the last span
+        let (mut prev_hi, _) = self.span_to_locations(lo..hi);
+        prev_hi.char = 0;
+        let mut prev_line = lines.first().map(|line| line.line);
+        let mut buf = String::new();
+
+        let mut line_highlight = vec![];
+        // We need to keep track of the difference between the existing code and the added
+        // or deleted code in order to point at the correct column *after* substitution.
+        let mut acc = 0;
+        for part in &mut patches {
+            // If this is a replacement of, e.g. `"a"` into `"ab"`, adjust the
+            // suggestion and snippet to look as if we just suggested to add
+            // `"b"`, which is typically much easier for the user to understand.
+            part.trim_trivial_replacements(self);
+            let (cur_lo, cur_hi) = self.span_to_locations(part.range.clone());
+            if prev_hi.line == cur_lo.line {
+                let mut count = push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo));
+                while count > 0 {
+                    highlights.push(std::mem::take(&mut line_highlight));
+                    acc = 0;
+                    count -= 1;
+                }
+            } else {
+                acc = 0;
+                highlights.push(std::mem::take(&mut line_highlight));
+                let mut count = push_trailing(&mut buf, prev_line, &prev_hi, None);
+                while count > 0 {
+                    highlights.push(std::mem::take(&mut line_highlight));
+                    count -= 1;
+                }
+                // push lines between the previous and current span (if any)
+                for idx in prev_hi.line + 1..(cur_lo.line) {
+                    if let Some(line) = self.get_line(idx) {
+                        buf.push_str(line.as_ref());
+                        buf.push('\n');
+                        highlights.push(std::mem::take(&mut line_highlight));
+                    }
+                }
+                if let Some(cur_line) = self.get_line(cur_lo.line) {
+                    let end = match cur_line.char_indices().nth(cur_lo.char) {
+                        Some((i, _)) => i,
+                        None => cur_line.len(),
+                    };
+                    buf.push_str(&cur_line[..end]);
+                }
+            }
+            // Add a whole line highlight per line in the snippet.
+            let len: isize = part
+                .replacement
+                .split('\n')
+                .next()
+                .unwrap_or(part.replacement)
+                .chars()
+                .map(|c| match c {
+                    '\t' => 4,
+                    _ => 1,
+                })
+                .sum();
+            if !is_different(self, part.replacement, part.range.clone()) {
+                // Account for cases where we are suggesting the same code that's already
+                // there. This shouldn't happen often, but in some cases for multipart
+                // suggestions it's much easier to handle it here than in the origin.
+            } else {
+                line_highlight.push(SubstitutionHighlight {
+                    start: (cur_lo.char as isize + acc) as usize,
+                    end: (cur_lo.char as isize + acc + len) as usize,
+                });
+            }
+            buf.push_str(part.replacement);
+            // Account for the difference between the width of the current code and the
+            // snippet being suggested, so that the *later* suggestions are correctly
+            // aligned on the screen. Note that cur_hi and cur_lo can be on different
+            // lines, so cur_hi.col can be smaller than cur_lo.col
+            acc += len - (cur_hi.char as isize - cur_lo.char as isize);
+            prev_hi = cur_hi;
+            prev_line = self.get_line(prev_hi.line);
+            for line in part.replacement.split('\n').skip(1) {
+                acc = 0;
+                highlights.push(std::mem::take(&mut line_highlight));
+                let end: usize = line
+                    .chars()
+                    .map(|c| match c {
+                        '\t' => 4,
+                        _ => 1,
+                    })
+                    .sum();
+                line_highlight.push(SubstitutionHighlight { start: 0, end });
+            }
+        }
+        highlights.push(std::mem::take(&mut line_highlight));
+        // if the replacement already ends with a newline, don't print the next line
+        if !buf.ends_with('\n') {
+            push_trailing(&mut buf, prev_line, &prev_hi, None);
+        }
+        // remove trailing newlines
+        while buf.ends_with('\n') {
+            buf.pop();
+        }
+        if highlights.iter().all(|parts| parts.is_empty()) {
+            Vec::new()
+        } else {
+            vec![(buf, patches, highlights)]
+        }
+    }
 }
 
 #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
@@ -450,3 +640,11 @@ impl<'a> Iterator for CursorLines<'a> {
         }
     }
 }
+
+/// Used to translate between `Span`s and byte positions within a single output line in highlighted
+/// code of structured suggestions.
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct SubstitutionHighlight {
+    pub(crate) start: usize,
+    pub(crate) end: usize,
+}
diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs
index 3f19242..9a5f72a 100644
--- a/src/renderer/styled_buffer.rs
+++ b/src/renderer/styled_buffer.rs
@@ -118,4 +118,33 @@ impl StyledBuffer {
     pub(crate) fn num_lines(&self) -> usize {
         self.lines.len()
     }
+
+    /// Set `style` for `line`, `col_start..col_end` range if:
+    /// 1. That line and column range exist in `StyledBuffer`
+    /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
+    pub(crate) fn set_style_range(
+        &mut self,
+        line: usize,
+        col_start: usize,
+        col_end: usize,
+        style: ElementStyle,
+        overwrite: bool,
+    ) {
+        for col in col_start..col_end {
+            self.set_style(line, col, style, overwrite);
+        }
+    }
+
+    /// Set `style` for `line`, `col` if:
+    /// 1. That line and column exist in `StyledBuffer`
+    /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
+    pub(crate) fn set_style(&mut self, line: usize, col: usize, style: ElementStyle, overwrite: bool) {
+        if let Some(ref mut line) = self.lines.get_mut(line) {
+            if let Some(StyledChar { style: s, .. }) = line.get_mut(col) {
+                if overwrite || matches!(s, ElementStyle::NoStyle | ElementStyle::Quotation) {
+                    *s = style;
+                }
+            }
+        }
+    }
 }
diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs
index c11a279..4aa21a5 100644
--- a/src/renderer/stylesheet.rs
+++ b/src/renderer/stylesheet.rs
@@ -11,6 +11,8 @@ pub(crate) struct Stylesheet {
     pub(crate) emphasis: Style,
     pub(crate) none: Style,
     pub(crate) context: Style,
+    pub(crate) addition: Style,
+    pub(crate) removal: Style,
 }
 
 impl Default for Stylesheet {
@@ -31,6 +33,8 @@ impl Stylesheet {
             emphasis: Style::new(),
             none: Style::new(),
             context: Style::new(),
+            addition: Style::new(),
+            removal: Style::new(),
         }
     }
 }
diff --git a/src/snippet.rs b/src/snippet.rs
index 1c3efb6..d371ab8 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -1,5 +1,6 @@
 //! Structures used as an input for the library.
 
+use crate::renderer::source_map::SourceMap;
 use crate::renderer::stylesheet::Stylesheet;
 use anstyle::Style;
 use std::ops::Range;
@@ -46,6 +47,17 @@ impl<'a> Message<'a> {
 
                             cause.line_start + newline_count(&cause.source[..end])
                         }
+                        Element::Suggestion(suggestion) => {
+                            let end = suggestion
+                                .markers
+                                .iter()
+                                .map(|a| a.range.end)
+                                .max()
+                                .unwrap_or(suggestion.source.len())
+                                .min(suggestion.source.len());
+
+                            suggestion.line_start + newline_count(&suggestion.source[..end])
+                        }
                     })
                     .max()
                     .unwrap_or(1)
@@ -91,6 +103,7 @@ impl<'a> Group<'a> {
 pub enum Element<'a> {
     Title(Title<'a>),
     Cause(Snippet<'a, Annotation<'a>>),
+    Suggestion(Snippet<'a, Patch<'a>>),
     Origin(Origin<'a>),
     ColumnSeparator(ColumnSeparator),
 }
@@ -107,6 +120,12 @@ impl<'a> From<Snippet<'a, Annotation<'a>>> for Element<'a> {
     }
 }
 
+impl<'a> From<Snippet<'a, Patch<'a>>> for Element<'a> {
+    fn from(value: Snippet<'a, Patch<'a>>) -> Self {
+        Element::Suggestion(value)
+    }
+}
+
 impl<'a> From<Origin<'a>> for Element<'a> {
     fn from(value: Origin<'a>) -> Self {
         Element::Origin(value)
@@ -237,6 +256,18 @@ impl<'a> Snippet<'a, Annotation<'a>> {
     }
 }
 
+impl<'a> Snippet<'a, Patch<'a>> {
+    pub fn patch(mut self, patch: Patch<'a>) -> Snippet<'a, Patch<'a>> {
+        self.markers.push(patch);
+        self
+    }
+
+    pub fn patches(mut self, patches: impl IntoIterator<Item = Patch<'a>>) -> Self {
+        self.markers.extend(patches);
+        self
+    }
+}
+
 #[derive(Clone, Debug)]
 pub struct Annotation<'a> {
     pub(crate) range: Range<usize>,
@@ -273,6 +304,65 @@ impl AnnotationKind {
     }
 }
 
+#[derive(Clone, Debug)]
+pub struct Patch<'a> {
+    pub(crate) range: Range<usize>,
+    pub(crate) replacement: &'a str,
+}
+
+impl<'a> Patch<'a> {
+    pub fn new(range: Range<usize>, replacement: &'a str) -> Self {
+        Self { range, replacement }
+    }
+
+    pub(crate) fn is_addition(&self, sm: &SourceMap<'_>) -> bool {
+        !self.replacement.is_empty() && !self.replaces_meaningful_content(sm)
+    }
+
+    pub(crate) fn is_deletion(&self, sm: &SourceMap<'_>) -> bool {
+        self.replacement.trim().is_empty() && self.replaces_meaningful_content(sm)
+    }
+
+    pub(crate) fn is_replacement(&self, sm: &SourceMap<'_>) -> bool {
+        !self.replacement.is_empty() && self.replaces_meaningful_content(sm)
+    }
+
+    /// Whether this is a replacement that overwrites source with a snippet
+    /// in a way that isn't a superset of the original string. For example,
+    /// replacing "abc" with "abcde" is not destructive, but replacing it
+    /// it with "abx" is, since the "c" character is lost.
+    pub(crate) fn is_destructive_replacement(&self, sm: &SourceMap<'_>) -> bool {
+        self.is_replacement(sm)
+            && !sm
+                .span_to_snippet(self.range.clone())
+                // This should use `is_some_and` when our MSRV is >= 1.70
+                .map_or(false, |s| {
+                    as_substr(s.trim(), self.replacement.trim()).is_some()
+                })
+    }
+
+    fn replaces_meaningful_content(&self, sm: &SourceMap<'_>) -> bool {
+        sm.span_to_snippet(self.range.clone())
+            .map_or(!self.range.is_empty(), |snippet| !snippet.trim().is_empty())
+    }
+
+    /// Try to turn a replacement into an addition when the span that is being
+    /// overwritten matches either the prefix or suffix of the replacement.
+    pub(crate) fn trim_trivial_replacements(&mut self, sm: &'a SourceMap<'a>) {
+        if self.replacement.is_empty() {
+            return;
+        }
+        let Some(snippet) = sm.span_to_snippet(self.range.clone()) else {
+            return;
+        };
+
+        if let Some((prefix, substr, suffix)) = as_substr(snippet, self.replacement) {
+            self.range = self.range.start + prefix..self.range.end.saturating_sub(suffix);
+            self.replacement = substr;
+        }
+    }
+}
+
 #[derive(Clone, Debug)]
 pub struct Origin<'a> {
     pub(crate) origin: &'a str,
@@ -326,3 +416,24 @@ fn newline_count(body: &str) -> usize {
         body.lines().count().saturating_sub(1)
     }
 }
+
+/// Given an original string like `AACC`, and a suggestion like `AABBCC`, try to detect
+/// the case where a substring of the suggestion is "sandwiched" in the original, like
+/// `BB` is. Return the length of the prefix, the "trimmed" suggestion, and the length
+/// of the suffix.
+fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a str, usize)> {
+    let common_prefix = original
+        .chars()
+        .zip(suggestion.chars())
+        .take_while(|(c1, c2)| c1 == c2)
+        .map(|(c, _)| c.len_utf8())
+        .sum();
+    let original = &original[common_prefix..];
+    let suggestion = &suggestion[common_prefix..];
+    if let Some(stripped) = suggestion.strip_suffix(original) {
+        let common_suffix = original.len();
+        Some((common_prefix, stripped, common_suffix))
+    } else {
+        None
+    }
+}
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index fffcf1f..065c395 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -3,7 +3,7 @@ use std::ops::Range;
 
 use annotate_snippets::renderer::DEFAULT_TERM_WIDTH;
 use annotate_snippets::{
-    Annotation, AnnotationKind, Element, Group, Level, Message, Renderer, Snippet,
+    Annotation, AnnotationKind, Element, Group, Level, Message, Patch, Renderer, Snippet,
 };
 
 #[derive(Deserialize)]
@@ -40,6 +40,7 @@ impl<'a> From<&'a MessageDef> for Message<'a> {
         message = message.group(Group::new().elements(sections.iter().map(|s| match s {
             ElementDef::Title(title) => Element::Title(title.level.title(&title.title)),
             ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)),
+            ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)),
         })));
         message
     }
@@ -50,6 +51,7 @@ impl<'a> From<&'a MessageDef> for Message<'a> {
 pub enum ElementDef {
     Title(TitleDef),
     Cause(SnippetAnnotationDef),
+    Suggestion(SnippetPatchDef),
 }
 
 impl<'a> From<&'a ElementDef> for Element<'a> {
@@ -57,6 +59,7 @@ impl<'a> From<&'a ElementDef> for Element<'a> {
         match val {
             ElementDef::Title(title) => Element::Title(title.level.title(&title.title)),
             ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)),
+            ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)),
         }
     }
 }
@@ -119,6 +122,47 @@ enum AnnotationKindDef {
     Context,
 }
 
+#[derive(Deserialize)]
+pub struct SnippetPatchDef {
+    pub(crate) origin: Option<String>,
+    pub(crate) line_start: usize,
+    pub(crate) source: String,
+    pub(crate) patches: Vec<PatchDef>,
+    #[serde(default)]
+    pub(crate) fold: bool,
+}
+
+impl<'a> From<&'a SnippetPatchDef> for Snippet<'a, Patch<'a>> {
+    fn from(val: &'a SnippetPatchDef) -> Self {
+        let SnippetPatchDef {
+            origin,
+            line_start,
+            source,
+            patches,
+            fold,
+        } = val;
+        let mut snippet = Snippet::source(source).line_start(*line_start).fold(*fold);
+        if let Some(origin) = origin {
+            snippet = snippet.origin(origin);
+        }
+        snippet = snippet.patches(patches.iter().map(Into::into));
+        snippet
+    }
+}
+
+#[derive(Deserialize)]
+pub struct PatchDef {
+    pub range: Range<usize>,
+    pub replacement: String,
+}
+
+impl<'a> From<&'a PatchDef> for Patch<'a> {
+    fn from(val: &'a PatchDef) -> Self {
+        let PatchDef { range, replacement } = val;
+        Patch::new(range.start..range.end, replacement)
+    }
+}
+
 #[allow(dead_code)]
 #[derive(Deserialize)]
 #[serde(remote = "Level")]
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 33b998f..61f63d1 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Renderer, Snippet};
+use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Patch, Renderer, Snippet};
 
 use snapbox::{assert_data_eq, str};
 
@@ -1012,3 +1012,681 @@ error: title
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input), expected);
 }
+
+#[test]
+fn two_suggestions_same_span() {
+    let source = r#"    A.foo();"#;
+    let input_new = Level::Error
+        .message("expected value, found enum `A`")
+        .id("E0423")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .fold(true)
+                    .annotation(AnnotationKind::Primary.span(4..5)),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(
+                    Level::Help
+                        .title("you might have meant to use one of the following enum variants"),
+                )
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .patch(Patch::new(4..5, "(A::Tuple())")),
+                )
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .patch(Patch::new(4..5, "A::Unit")),
+                ),
+        );
+
+    let expected = str![[r#"
+error[E0423]: expected value, found enum `A`
+   |
+LL |     A.foo();
+   |     ^
+   |
+help: you might have meant to use one of the following enum variants
+   |
+LL -     A.foo();
+LL +     (A::Tuple()).foo();
+   |
+LL |     A::Unit.foo();
+   |      ++++++
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn two_suggestions_same_span2() {
+    let source = r#"
+mod banana {
+    pub struct Chaenomeles;
+
+    pub trait Apple {
+        fn pick(&self) {}
+    }
+    impl Apple for Chaenomeles {}
+
+    pub trait Peach {
+        fn pick(&self, a: &mut ()) {}
+    }
+    impl<Mango: Peach> Peach for Box<Mango> {}
+    impl Peach for Chaenomeles {}
+}
+
+fn main() {
+    banana::Chaenomeles.pick()
+}"#;
+    let input_new =
+        Level::Error
+            .message("no method named `pick` found for struct `Chaenomeles` in the current scope")
+            .id("E0599")
+            .group(
+                Group::new().element(
+                    Snippet::source(source)
+                        .line_start(1)
+                        .fold(true)
+                        .annotation(
+                            AnnotationKind::Context
+                                .span(18..40)
+                                .label("method `pick` not found for this struct"),
+                        )
+                        .annotation(
+                            AnnotationKind::Primary
+                                .span(318..322)
+                                .label("method not found in `Chaenomeles`"),
+                        ),
+                ),
+            )
+            .group(
+                Group::new()
+                    .element(Level::Help.title(
+                        "the following traits which provide `pick` are implemented but not in scope; perhaps you want to import one of them",
+                    ))
+                    .element(
+                        Snippet::source(source)
+                            .fold(true)
+                            .patch(Patch::new(1..1, "use banana::Apple;\n")),
+                    )
+                    .element(
+                        Snippet::source(source)
+                            .fold(true)
+                            .patch(Patch::new(1..1, "use banana::Peach;\n")),
+                    ),
+            );
+    let expected = str![[r#"
+error[E0599]: no method named `pick` found for struct `Chaenomeles` in the current scope
+   |
+LL |     pub struct Chaenomeles;
+   |     ---------------------- method `pick` not found for this struct
+...
+LL |     banana::Chaenomeles.pick()
+   |                         ^^^^ method not found in `Chaenomeles`
+   |
+help: the following traits which provide `pick` are implemented but not in scope; perhaps you want to import one of them
+   |
+LL + use banana::Apple;
+   |
+LL + use banana::Peach;
+   |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn single_line_non_overlapping_suggestions() {
+    let source = r#"    A.foo();"#;
+
+    let input_new = Level::Error
+        .message("expected value, found enum `A`")
+        .id("E0423")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .fold(true)
+                    .line_start(1)
+                    .annotation(AnnotationKind::Primary.span(4..5)),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(Level::Help.title("make these changes and things will work"))
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .fold(true)
+                        .patch(Patch::new(4..5, "(A::Tuple())"))
+                        .patch(Patch::new(6..9, "bar")),
+                ),
+        );
+
+    let expected = str![[r#"
+error[E0423]: expected value, found enum `A`
+   |
+LL |     A.foo();
+   |     ^
+   |
+help: make these changes and things will work
+   |
+LL -     A.foo();
+LL +     (A::Tuple()).bar();
+   |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn single_line_non_overlapping_suggestions2() {
+    let source = r#"    ThisIsVeryLong.foo();"#;
+    let input_new = Level::Error
+        .message("Found `ThisIsVeryLong`")
+        .id("E0423")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .fold(true)
+                    .line_start(1)
+                    .annotation(AnnotationKind::Primary.span(4..18)),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(Level::Help.title("make these changes and things will work"))
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .fold(true)
+                        .patch(Patch::new(4..18, "(A::Tuple())"))
+                        .patch(Patch::new(19..22, "bar")),
+                ),
+        );
+
+    let expected = str![[r#"
+error[E0423]: Found `ThisIsVeryLong`
+   |
+LL |     ThisIsVeryLong.foo();
+   |     ^^^^^^^^^^^^^^
+   |
+help: make these changes and things will work
+   |
+LL -     ThisIsVeryLong.foo();
+LL +     (A::Tuple()).bar();
+   |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn multiple_replacements() {
+    let source = r#"
+    let y = || {
+        self.bar();
+    };
+    self.qux();
+    y();
+"#;
+
+    let input_new = Level::Error
+        .message("cannot borrow `*self` as mutable because it is also borrowed as immutable")
+        .id("E0502")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .line_start(1)
+                    .fold(true)
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(49..59)
+                            .label("mutable borrow occurs here"),
+                    )
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(13..15)
+                            .label("immutable borrow occurs here"),
+                    )
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(26..30)
+                            .label("first borrow occurs due to use of `*self` in closure"),
+                    )
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(65..66)
+                            .label("immutable borrow later used here"),
+                    ),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(
+                    Level::Help
+                        .title("try explicitly pass `&Self` into the Closure as an argument"),
+                )
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .patch(Patch::new(14..14, "this: &Self"))
+                        .patch(Patch::new(26..30, "this"))
+                        .patch(Patch::new(66..68, "(self)")),
+                ),
+        );
+    let expected = str![[r#"
+error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
+   |
+LL |     let y = || {
+   |             ^^ immutable borrow occurs here
+LL |         self.bar();
+   |         ^^^^ first borrow occurs due to use of `*self` in closure
+LL |     };
+LL |     self.qux();
+   |     ^^^^^^^^^^ mutable borrow occurs here
+LL |     y();
+   |     ^ immutable borrow later used here
+   |
+help: try explicitly pass `&Self` into the Closure as an argument
+   |
+LL ~     let y = |this: &Self| {
+LL ~         this.bar();
+LL |     };
+LL |     self.qux();
+LL ~     y(self);
+   |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn multiple_replacements2() {
+    let source = r#"
+fn test1() {
+    let mut chars = "Hello".chars();
+    for _c in chars.by_ref() {
+        chars.next();
+    }
+}
+
+fn main() {
+    test1();
+}"#;
+
+    let input_new = Level::Error
+        .message("cannot borrow `chars` as mutable more than once at a time")
+        .id("E0499")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .line_start(1)
+                    .fold(true)
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(65..70)
+                            .label("first mutable borrow occurs here"),
+                    )
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(90..95)
+                            .label("second mutable borrow occurs here"),
+                    )
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(65..79)
+                            .label("first borrow later used here"),
+                    ),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(
+                    Level::Help
+                        .title("if you want to call `next` on a iterator within the loop, consider using `while let`")
+                )
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .patch(Patch::new(
+                            55..59,
+                            "let iter = chars.by_ref();\n    while let Some(",
+                        ))
+                        .patch(Patch::new(61..79, ") = iter.next()"))
+                        .patch(Patch::new(90..95, "iter")),
+                ),
+        );
+
+    let expected = str![[r#"
+error[E0499]: cannot borrow `chars` as mutable more than once at a time
+   |
+LL |     for _c in chars.by_ref() {
+   |               --------------
+   |               |
+   |               first mutable borrow occurs here
+   |               first borrow later used here
+LL |         chars.next();
+   |         ^^^^^ second mutable borrow occurs here
+   |
+help: if you want to call `next` on a iterator within the loop, consider using `while let`
+   |
+LL ~     let iter = chars.by_ref();
+LL ~     while let Some(_c) = iter.next() {
+LL ~         iter.next();
+   |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn diff_format() {
+    let source = r#"
+use st::cell::Cell;
+
+mod bar {
+    pub fn bar() { bar::baz(); }
+
+    fn baz() {}
+}
+
+use bas::bar;
+
+struct Foo {
+    bar: st::cell::Cell<bool>
+}
+
+fn main() {}"#;
+
+    let input_new = Level::Error
+        .message("failed to resolve: use of undeclared crate or module `st`")
+        .id("E0433")
+        .group(
+            Group::new().element(
+                Snippet::source(source).line_start(1).fold(true).annotation(
+                    AnnotationKind::Primary
+                        .span(122..124)
+                        .label("use of undeclared crate or module `st`"),
+                ),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(Level::Help.title("there is a crate or module with a similar name"))
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .patch(Patch::new(122..124, "std")),
+                ),
+        )
+        .group(
+            Group::new()
+                .element(Level::Help.title("consider importing this module"))
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .patch(Patch::new(1..1, "use std::cell;\n")),
+                ),
+        )
+        .group(
+            Group::new()
+                .element(Level::Help.title("if you import `cell`, refer to it directly"))
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .patch(Patch::new(122..126, "")),
+                ),
+        );
+    let expected = str![[r#"
+error[E0433]: failed to resolve: use of undeclared crate or module `st`
+   |
+LL |     bar: st::cell::Cell<bool>
+   |          ^^ use of undeclared crate or module `st`
+   |
+help: there is a crate or module with a similar name
+   |
+LL |     bar: std::cell::Cell<bool>
+   |            +
+help: consider importing this module
+   |
+LL + use std::cell;
+   |
+help: if you import `cell`, refer to it directly
+   |
+LL -     bar: st::cell::Cell<bool>
+LL +     bar: cell::Cell<bool>
+   |
+"#]];
+
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn multiline_removal() {
+    let source = r#"
+struct Wrapper<T>(T);
+
+fn foo<T>(foo: Wrapper<T>)
+
+where
+    T
+    :
+    ?
+    Sized
+{
+    //
+}
+
+fn main() {}"#;
+
+    let input_new = Level::Error
+        .message("the size for values of type `T` cannot be known at compilation time")
+        .id("E0277")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .line_start(1)
+                    .fold(true)
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(39..49)
+                            .label("doesn't have a size known at compile-time"),
+                    )
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(31..32)
+                            .label("this type parameter needs to be `Sized`"),
+                    ),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(Level::Help.title(
+                    "consider removing the `?Sized` bound to make the type parameter `Sized`",
+                ))
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .patch(Patch::new(52..86, "")),
+                ),
+        );
+    let expected = str![[r#"
+error[E0277]: the size for values of type `T` cannot be known at compilation time
+   |
+LL | fn foo<T>(foo: Wrapper<T>)
+   |        -       ^^^^^^^^^^ doesn't have a size known at compile-time
+   |        |
+   |        this type parameter needs to be `Sized`
+   |
+help: consider removing the `?Sized` bound to make the type parameter `Sized`
+   |
+LL - where
+LL -     T
+LL -     :
+LL -     ?
+LL -     Sized
+   |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn multiline_replacement() {
+    let source = r#"
+struct Wrapper<T>(T);
+
+fn foo<T>(foo: Wrapper<T>)
+
+and where
+    T
+    :
+    ?
+    Sized
+{
+    //
+}
+
+fn main() {}"#;
+    let input_new = Level::Error
+        .message("the size for values of type `T` cannot be known at compilation time")
+        .id("E0277")
+        .group(Group::new().element(Snippet::source(source)
+            .line_start(1)
+            .origin("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs")
+            .fold(true)
+            .annotation(
+                AnnotationKind::Primary
+                    .span(39..49)
+                    .label("doesn't have a size known at compile-time"),
+            )
+            .annotation(
+                AnnotationKind::Context
+                    .span(31..32)
+                    .label("this type parameter needs to be `Sized`"),
+            )))
+        .group(Group::new().element(
+            Level::Note
+                .title("required by an implicit `Sized` bound in `Wrapper`")
+        ).element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(16..17)
+                        .label("required by the implicit `Sized` requirement on this type parameter in `Wrapper`"),
+                )
+        ))
+        .group(Group::new().element(
+            Level::Help
+                .title("you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`")
+            )
+            .element(
+            Snippet::source(source)
+                .line_start(1)
+                .origin("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(16..17)
+                        .label("this could be changed to `T: ?Sized`..."),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(19..20)
+                        .label("...if indirection were used here: `Box<T>`"),
+                )
+
+        ))
+        .group(Group::new().element(
+            Level::Help
+                .title("consider removing the `?Sized` bound to make the type parameter `Sized`")
+        ).element(
+            Snippet::source(source)
+                .fold(true)
+                .patch(Patch::new(56..90, ""))
+                .patch(Patch::new(90..90, "+ Send"))
+                ,
+        ));
+    let expected = str![[r#"
+error[E0277]: the size for values of type `T` cannot be known at compilation time
+  --> $DIR/removal-of-multiline-trait-bound-in-where-clause.rs:4:16
+   |
+LL | fn foo<T>(foo: Wrapper<T>)
+   |        -       ^^^^^^^^^^ doesn't have a size known at compile-time
+   |        |
+   |        this type parameter needs to be `Sized`
+   |
+note: required by an implicit `Sized` bound in `Wrapper`
+  --> $DIR/removal-of-multiline-trait-bound-in-where-clause.rs:2:16
+   |
+LL | struct Wrapper<T>(T);
+   |                ^ required by the implicit `Sized` requirement on this type parameter in `Wrapper`
+help: you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`
+  --> $DIR/removal-of-multiline-trait-bound-in-where-clause.rs:2:16
+   |
+LL | struct Wrapper<T>(T);
+   |                ^  - ...if indirection were used here: `Box<T>`
+   |                |
+   |                this could be changed to `T: ?Sized`...
+help: consider removing the `?Sized` bound to make the type parameter `Sized`
+   |
+LL ~ and 
+LL ~ + Send{
+   |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn multiline_removal2() {
+    let source = r#"
+cargo
+fuzzy
+pizza
+jumps
+crazy
+quack
+zappy
+"#;
+
+    let input_new = Level::Error
+        .message("the size for values of type `T` cannot be known at compilation time")
+        .id("E0277")
+        .group(
+            Group::new()
+                .element(Level::Help.title(
+                    "consider removing the `?Sized` bound to make the type parameter `Sized`",
+                ))
+                .element(
+                    Snippet::source(source)
+                        .line_start(7)
+                        .fold(true)
+                        .patch(Patch::new(3..21, ""))
+                        .patch(Patch::new(22..40, "")),
+                ),
+        );
+    let expected = str![[r#"
+error[E0277]: the size for values of type `T` cannot be known at compilation time
+   |
+help: consider removing the `?Sized` bound to make the type parameter `Sized`
+   |
+8  - cargo
+9  - fuzzy
+10 - pizza
+11 - jumps
+8  + campy
+   |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input_new), expected);
+}

From ad60b003b1522944cade4b652730ff7bf70160ce Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Fri, 28 Feb 2025 03:54:49 -0700
Subject: [PATCH 281/302] feat: Add unicode support

---
 src/renderer/mod.rs           | 335 ++++++++++++++++++++++-----
 src/renderer/styled_buffer.rs |   8 +-
 tests/formatter.rs            | 422 ++++++++++++++++++++++++++++++++++
 3 files changed, 708 insertions(+), 57 deletions(-)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index fdd2b3e..85de0f5 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -63,6 +63,7 @@ pub const DEFAULT_TERM_WIDTH: usize = 140;
 pub struct Renderer {
     anonymized_line_numbers: bool,
     term_width: usize,
+    theme: OutputTheme,
     stylesheet: Stylesheet,
 }
 
@@ -72,6 +73,7 @@ impl Renderer {
         Self {
             anonymized_line_numbers: false,
             term_width: DEFAULT_TERM_WIDTH,
+            theme: OutputTheme::Ascii,
             stylesheet: Stylesheet::plain(),
         }
     }
@@ -140,6 +142,11 @@ impl Renderer {
         self
     }
 
+    pub const fn theme(mut self, output_theme: OutputTheme) -> Self {
+        self.theme = output_theme;
+        self
+    }
+
     /// Set the output style for `error`
     pub const fn error(mut self, style: Style) -> Self {
         self.stylesheet.error = style;
@@ -318,6 +325,7 @@ impl Renderer {
                                     None
                                 }
                             }),
+                            matches!(peek, Some(Element::Title(_))),
                         );
                         last_was_suggestion = false;
                     }
@@ -333,6 +341,7 @@ impl Renderer {
                                 &source_map,
                                 &annotated_lines,
                                 max_depth,
+                                peek.is_some() || (g == 0 && group_len > 1),
                             );
 
                             if g == 0 && group_len > 1 {
@@ -346,12 +355,10 @@ impl Renderer {
                                 // We want to draw the separator when it is
                                 // requested, or when it is the last element
                                 } else if peek.is_none() {
-                                    self.draw_col_separator_no_space_with_style(
+                                    self.draw_col_separator_end(
                                         buffer,
-                                        '|',
                                         buffer.num_lines(),
                                         max_line_num_len + 1,
-                                        ElementStyle::LineNumber,
                                     );
                                 }
                             }
@@ -390,12 +397,10 @@ impl Renderer {
                         || matches!(section, Element::Title(level) if level.level == Level::None))
                 {
                     if peek.is_none() && group_len > 1 {
-                        self.draw_col_separator_no_space_with_style(
+                        self.draw_col_separator_end(
                             buffer,
-                            '|',
                             buffer.num_lines(),
                             max_line_num_len + 1,
-                            ElementStyle::LineNumber,
                         );
                     } else if matches!(peek, Some(Element::Title(level)) if level.level != Level::None)
                     {
@@ -410,6 +415,7 @@ impl Renderer {
         }
     }
 
+    #[allow(clippy::too_many_arguments)]
     fn render_title(
         &self,
         buffer: &mut StyledBuffer,
@@ -418,6 +424,7 @@ impl Renderer {
         max_line_num_len: usize,
         is_secondary: bool,
         id: Option<&&str>,
+        is_cont: bool,
     ) {
         let line_offset = buffer.num_lines();
 
@@ -437,13 +444,9 @@ impl Renderer {
             for _ in 0..max_line_num_len {
                 buffer.prepend(line_offset, " ", ElementStyle::NoStyle);
             }
+
             if title.level != Level::None {
-                buffer.puts(
-                    line_offset,
-                    max_line_num_len + 1,
-                    "= ",
-                    ElementStyle::LineNumber,
-                );
+                self.draw_note_separator(buffer, line_offset, max_line_num_len + 1, is_cont);
                 buffer.append(
                     line_offset,
                     title.level.as_str(),
@@ -451,7 +454,25 @@ impl Renderer {
                 );
                 buffer.append(line_offset, ": ", ElementStyle::NoStyle);
             }
-            self.msgs_to_buffer(buffer, title.title, max_line_num_len, "note", None);
+
+            let printed_lines =
+                self.msgs_to_buffer(buffer, title.title, max_line_num_len, "note", None);
+            if is_cont && matches!(self.theme, OutputTheme::Unicode) {
+                // There's another note after this one, associated to the subwindow above.
+                // We write additional vertical lines to join them:
+                //   ╭▸ test.rs:3:3
+                //   │
+                // 3 │   code
+                //   │   ━━━━
+                //   │
+                //   ├ note: foo
+                //   │       bar
+                //   ╰ note: foo
+                //           bar
+                for i in line_offset + 1..=printed_lines {
+                    self.draw_col_separator_no_space(buffer, i, max_line_num_len + 1);
+                }
+            }
         } else {
             let mut label_width = 0;
 
@@ -628,7 +649,7 @@ impl Renderer {
                 max_line_num_len + 1,
             );
             let title = Level::Note.title(label);
-            self.render_title(buffer, &title, None, max_line_num_len, true, None);
+            self.render_title(buffer, &title, None, max_line_num_len, true, None, false);
         }
     }
 
@@ -642,6 +663,7 @@ impl Renderer {
         sm: &SourceMap<'_>,
         annotated_lines: &[AnnotatedLineInfo<'_>],
         multiline_depth: usize,
+        is_cont: bool,
     ) {
         if let Some(origin) = snippet.origin {
             let mut origin = Origin::new(origin);
@@ -783,6 +805,7 @@ impl Renderer {
                 code_offset,
                 max_line_num_len,
                 margin,
+                !is_cont && annotated_line_idx + 1 == annotated_lines.len(),
             );
 
             let mut to_add = HashMap::new();
@@ -810,7 +833,8 @@ impl Renderer {
                 match line_idx_delta.cmp(&2) {
                     Ordering::Greater => {
                         let last_buffer_line_num = buffer.num_lines();
-                        buffer.puts(last_buffer_line_num, 0, "...", ElementStyle::LineNumber);
+
+                        self.draw_line_separator(buffer, last_buffer_line_num, width_offset);
 
                         // Set the multiline annotation vertical lines on `...` bridging line.
                         for (depth, style) in &multilines {
@@ -908,6 +932,7 @@ impl Renderer {
         code_offset: usize,
         max_line_num_len: usize,
         margin: Margin,
+        close_window: bool,
     ) -> Vec<(usize, ElementStyle)> {
         // Draw:
         //
@@ -1205,7 +1230,9 @@ impl Renderer {
         for pos in 0..=line_len {
             self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2);
         }
-
+        if close_window {
+            self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2);
+        }
         // Write the horizontal lines for multiline annotations
         // (only the first and last lines need this).
         //
@@ -1470,18 +1497,12 @@ impl Renderer {
             let is_multiline = complete.lines().count() > 1;
 
             if i == 0 {
-                self.draw_col_separator_no_space_with_style(
-                    buffer,
-                    '|',
-                    row_num - 1,
-                    max_line_num_len + 1,
-                    ElementStyle::LineNumber,
-                );
+                self.draw_col_separator_start(buffer, row_num - 1, max_line_num_len + 1);
             } else {
                 buffer.puts(
                     row_num - 1,
                     max_line_num_len + 1,
-                    "|",
+                    self.multi_suggestion_separator(),
                     ElementStyle::LineNumber,
                 );
             }
@@ -1616,7 +1637,7 @@ impl Renderer {
                             );
                         }
 
-                        let placeholder = "...";
+                        let placeholder = self.margin();
                         let padding = str_width(placeholder);
                         buffer.puts(
                             row_num,
@@ -1735,7 +1756,11 @@ impl Renderer {
                             buffer.putc(
                                 row_num,
                                 (padding as isize + p) as usize,
-                                if part.is_addition(sm) { '+' } else { '~' },
+                                if part.is_addition(sm) {
+                                    '+'
+                                } else {
+                                    self.diff()
+                                },
                                 ElementStyle::Addition,
                             );
                         }
@@ -1766,7 +1791,7 @@ impl Renderer {
 
             // if we elided some lines, add an ellipsis
             if lines.next().is_some() {
-                let placeholder = "...";
+                let placeholder = self.margin();
                 let padding = str_width(placeholder);
                 buffer.puts(
                     row_num,
@@ -1781,13 +1806,7 @@ impl Renderer {
                     | DisplaySuggestion::Underline => row_num - 1,
                     DisplaySuggestion::None => row_num,
                 };
-                self.draw_col_separator_no_space_with_style(
-                    buffer,
-                    '|',
-                    row,
-                    max_line_num_len + 1,
-                    ElementStyle::LineNumber,
-                );
+                self.draw_col_separator_end(buffer, row, max_line_num_len + 1);
                 row_num = row + 1;
             }
         }
@@ -1906,7 +1925,13 @@ impl Renderer {
                     self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
                 }
                 _ => {
-                    buffer.puts(*row_num, max_line_num_len + 1, "~", ElementStyle::Addition);
+                    let diff = self.diff();
+                    buffer.puts(
+                        *row_num,
+                        max_line_num_len + 1,
+                        &format!("{diff} "),
+                        ElementStyle::Addition,
+                    );
                 }
             }
             //   LL | line_to_add
@@ -1940,12 +1965,7 @@ impl Renderer {
                 &self.maybe_anonymized(line_num),
                 ElementStyle::LineNumber,
             );
-            buffer.puts(
-                *row_num,
-                max_line_num_len + 1,
-                "| ",
-                ElementStyle::LineNumber,
-            );
+            self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
             buffer.append(
                 *row_num,
                 &normalize_whitespace(line_to_add),
@@ -2014,16 +2034,23 @@ impl Renderer {
             .collect();
 
         buffer.puts(line_offset, code_offset, &code, ElementStyle::Quotation);
+        let placeholder = self.margin();
         if margin.was_cut_left() {
             // We have stripped some code/whitespace from the beginning, make it clear.
-            buffer.puts(line_offset, code_offset, "...", ElementStyle::LineNumber);
+            buffer.puts(
+                line_offset,
+                code_offset,
+                placeholder,
+                ElementStyle::LineNumber,
+            );
         }
         if margin.was_cut_right(line_len) {
+            let padding = str_width(placeholder);
             // We have stripped some code after the rightmost span end, make it clear we did so.
             buffer.puts(
                 line_offset,
-                code_offset + taken - 3,
-                "...",
+                code_offset + taken - padding,
+                placeholder,
                 ElementStyle::LineNumber,
             );
         }
@@ -2059,19 +2086,109 @@ impl Renderer {
         depth: usize,
         style: ElementStyle,
     ) {
-        buffer.putc(line, offset + depth - 1, '|', style);
+        let chr = match (style, self.theme) {
+            (ElementStyle::UnderlinePrimary | ElementStyle::LabelPrimary, OutputTheme::Ascii) => {
+                '|'
+            }
+            (_, OutputTheme::Ascii) => '|',
+            (ElementStyle::UnderlinePrimary | ElementStyle::LabelPrimary, OutputTheme::Unicode) => {
+                '┃'
+            }
+            (_, OutputTheme::Unicode) => '│',
+        };
+        buffer.putc(line, offset + depth - 1, chr, style);
+    }
+
+    fn col_separator(&self) -> char {
+        match self.theme {
+            OutputTheme::Ascii => '|',
+            OutputTheme::Unicode => '│',
+        }
+    }
+
+    fn multi_suggestion_separator(&self) -> &'static str {
+        match self.theme {
+            OutputTheme::Ascii => "|",
+            OutputTheme::Unicode => "├╴",
+        }
+    }
+
+    fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
+        let chr = self.col_separator();
+        buffer.puts(line, col, &format!("{chr} "), ElementStyle::LineNumber);
     }
 
     fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
+        let chr = self.col_separator();
         self.draw_col_separator_no_space_with_style(
             buffer,
-            '|',
+            chr,
             line,
             col,
             ElementStyle::LineNumber,
         );
     }
 
+    fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
+        match self.theme {
+            OutputTheme::Ascii => {
+                self.draw_col_separator_no_space_with_style(
+                    buffer,
+                    '|',
+                    line,
+                    col,
+                    ElementStyle::LineNumber,
+                );
+            }
+            OutputTheme::Unicode => {
+                self.draw_col_separator_no_space_with_style(
+                    buffer,
+                    '╭',
+                    line,
+                    col,
+                    ElementStyle::LineNumber,
+                );
+                self.draw_col_separator_no_space_with_style(
+                    buffer,
+                    '╴',
+                    line,
+                    col + 1,
+                    ElementStyle::LineNumber,
+                );
+            }
+        }
+    }
+
+    fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
+        match self.theme {
+            OutputTheme::Ascii => {
+                self.draw_col_separator_no_space_with_style(
+                    buffer,
+                    '|',
+                    line,
+                    col,
+                    ElementStyle::LineNumber,
+                );
+            }
+            OutputTheme::Unicode => {
+                self.draw_col_separator_no_space_with_style(
+                    buffer,
+                    '╰',
+                    line,
+                    col,
+                    ElementStyle::LineNumber,
+                );
+                self.draw_col_separator_no_space_with_style(
+                    buffer,
+                    '╴',
+                    line,
+                    col + 1,
+                    ElementStyle::LineNumber,
+                );
+            }
+        }
+    }
+
     fn draw_col_separator_no_space_with_style(
         &self,
         buffer: &mut StyledBuffer,
@@ -2091,17 +2208,84 @@ impl Renderer {
         }
     }
 
-    fn file_start(&self) -> &str {
-        "--> "
+    fn file_start(&self) -> &'static str {
+        match self.theme {
+            OutputTheme::Ascii => "--> ",
+            OutputTheme::Unicode => " ╭▸ ",
+        }
+    }
+
+    fn secondary_file_start(&self) -> &'static str {
+        match self.theme {
+            OutputTheme::Ascii => "::: ",
+            OutputTheme::Unicode => " ⸬  ",
+        }
+    }
+
+    fn draw_note_separator(
+        &self,
+        buffer: &mut StyledBuffer,
+        line: usize,
+        col: usize,
+        is_cont: bool,
+    ) {
+        let chr = match self.theme {
+            OutputTheme::Ascii => "= ",
+            OutputTheme::Unicode if is_cont => "├ ",
+            OutputTheme::Unicode => "╰ ",
+        };
+        buffer.puts(line, col, chr, ElementStyle::LineNumber);
+    }
+
+    fn diff(&self) -> char {
+        match self.theme {
+            OutputTheme::Ascii => '~',
+            OutputTheme::Unicode => '±',
+        }
+    }
+
+    fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
+        let (column, dots) = match self.theme {
+            OutputTheme::Ascii => (0, "..."),
+            OutputTheme::Unicode => (col - 2, "‡"),
+        };
+        buffer.puts(line, column, dots, ElementStyle::LineNumber);
     }
 
-    fn secondary_file_start(&self) -> &str {
-        "::: "
+    fn margin(&self) -> &'static str {
+        match self.theme {
+            OutputTheme::Ascii => "...",
+            OutputTheme::Unicode => "…",
+        }
     }
 
     fn underline(&self, is_primary: bool) -> UnderlineParts {
-        if is_primary {
-            UnderlineParts {
+        //               X0 Y0
+        // label_start > ┯━━━━ < underline
+        //               │ < vertical_text_line
+        //               text
+
+        //    multiline_start_down ⤷ X0 Y0
+        //            top_left > ┌───╿──┘ < top_right_flat
+        //           top_left > ┏│━━━┙ < top_right
+        // multiline_vertical > ┃│
+        //                      ┃│   X1 Y1
+        //                      ┃│   X2 Y2
+        //                      ┃└────╿──┘ < multiline_end_same_line
+        //        bottom_left > ┗━━━━━┥ < bottom_right_with_text
+        //   multiline_horizontal ^   `X` is a good letter
+
+        // multiline_whole_line > ┏ X0 Y0
+        //                        ┃   X1 Y1
+        //                        ┗━━━━┛ < multiline_end_same_line
+
+        // multiline_whole_line > ┏ X0 Y0
+        //                        ┃ X1 Y1
+        //                        ┃  ╿ < multiline_end_up
+        //                        ┗━━┛ < bottom_right
+
+        match (self.theme, is_primary) {
+            (OutputTheme::Ascii, true) => UnderlineParts {
                 style: ElementStyle::UnderlinePrimary,
                 underline: '^',
                 label_start: '^',
@@ -2117,9 +2301,8 @@ impl Renderer {
                 multiline_end_up: '^',
                 multiline_end_same_line: '^',
                 multiline_bottom_right_with_text: '|',
-            }
-        } else {
-            UnderlineParts {
+            },
+            (OutputTheme::Ascii, false) => UnderlineParts {
                 style: ElementStyle::UnderlineSecondary,
                 underline: '-',
                 label_start: '-',
@@ -2135,7 +2318,41 @@ impl Renderer {
                 multiline_end_up: '-',
                 multiline_end_same_line: '-',
                 multiline_bottom_right_with_text: '|',
-            }
+            },
+            (OutputTheme::Unicode, true) => UnderlineParts {
+                style: ElementStyle::UnderlinePrimary,
+                underline: '━',
+                label_start: '┯',
+                vertical_text_line: '│',
+                multiline_vertical: '┃',
+                multiline_horizontal: '━',
+                multiline_whole_line: '┏',
+                multiline_start_down: '╿',
+                bottom_right: '┙',
+                top_left: '┏',
+                top_right_flat: '┛',
+                bottom_left: '┗',
+                multiline_end_up: '╿',
+                multiline_end_same_line: '┛',
+                multiline_bottom_right_with_text: '┥',
+            },
+            (OutputTheme::Unicode, false) => UnderlineParts {
+                style: ElementStyle::UnderlineSecondary,
+                underline: '─',
+                label_start: '┬',
+                vertical_text_line: '│',
+                multiline_vertical: '│',
+                multiline_horizontal: '─',
+                multiline_whole_line: '┌',
+                multiline_start_down: '│',
+                bottom_right: '┘',
+                top_left: '┌',
+                top_right_flat: '┘',
+                bottom_left: '└',
+                multiline_end_up: '│',
+                multiline_end_same_line: '┘',
+                multiline_bottom_right_with_text: '┤',
+            },
         }
     }
 }
@@ -2435,6 +2652,12 @@ pub(crate) fn is_different(sm: &SourceMap<'_>, suggested: &str, range: Range<usi
     }
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum OutputTheme {
+    Ascii,
+    Unicode,
+}
+
 #[cfg(test)]
 mod test {
     use super::OUTPUT_REPLACEMENTS;
diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs
index 9a5f72a..8a4cc67 100644
--- a/src/renderer/styled_buffer.rs
+++ b/src/renderer/styled_buffer.rs
@@ -138,7 +138,13 @@ impl StyledBuffer {
     /// Set `style` for `line`, `col` if:
     /// 1. That line and column exist in `StyledBuffer`
     /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
-    pub(crate) fn set_style(&mut self, line: usize, col: usize, style: ElementStyle, overwrite: bool) {
+    pub(crate) fn set_style(
+        &mut self,
+        line: usize,
+        col: usize,
+        style: ElementStyle,
+        overwrite: bool,
+    ) {
         if let Some(ref mut line) = self.lines.get_mut(line) {
             if let Some(StyledChar { style: s, .. }) = line.get_mut(col) {
                 if overwrite || matches!(s, ElementStyle::NoStyle | ElementStyle::Quotation) {
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 61f63d1..6304cf4 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,5 +1,6 @@
 use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Patch, Renderer, Snippet};
 
+use annotate_snippets::renderer::OutputTheme;
 use snapbox::{assert_data_eq, str};
 
 #[test]
@@ -1690,3 +1691,424 @@ help: consider removing the `?Sized` bound to make the type parameter `Sized`
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input_new), expected);
 }
+
+#[test]
+fn e0271() {
+    let source = r#"
+trait Future {
+    type Error;
+}
+
+impl<T, E> Future for Result<T, E> {
+    type Error = E;
+}
+
+impl<T> Future for Option<T> {
+    type Error = ();
+}
+
+struct Foo;
+
+fn foo() -> Box<dyn Future<Error=Foo>> {
+    Box::new(
+        Ok::<_, ()>(
+            Err::<(), _>(
+                Ok::<_, ()>(
+                    Err::<(), _>(
+                        Ok::<_, ()>(
+                            Err::<(), _>(Some(5))
+                        )
+                    )
+                )
+            )
+        )
+    )
+}
+fn main() {
+}
+"#;
+
+    let input_new = Level::Error
+        .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`")
+        .id("E0271")
+        .group(Group::new().element(Snippet::source(source)
+            .line_start(4)
+            .origin("$DIR/E0271.rs")
+            .fold(true)
+            .annotation(
+                AnnotationKind::Primary
+                    .span(208..510)
+                    .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"),
+            )))
+        .group(Group::new().element(
+            Level::Note.title("expected this to be `Foo`")
+        ).element(
+            Snippet::source(source)
+                .line_start(4)
+                .origin("$DIR/E0271.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(89..90))
+        ).element(
+            Level::Note
+                .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`")
+                ,
+        ));
+
+    let expected = str![[r#"
+error[E0271]: type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`
+   ╭▸ $DIR/E0271.rs:20:5
+   │
+LL │ ┏     Box::new(
+LL │ ┃         Ok::<_, ()>(
+LL │ ┃             Err::<(), _>(
+LL │ ┃                 Ok::<_, ()>(
+   ‡ ┃
+LL │ ┃     )
+   │ ┗━━━━━┛ type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`
+   ╰╴
+note: expected this to be `Foo`
+   ╭▸ $DIR/E0271.rs:10:18
+   │
+LL │     type Error = E;
+   │                  ━
+   ╰ note: required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`
+"#]];
+    let renderer = Renderer::plain()
+        .term_width(40)
+        .theme(OutputTheme::Unicode)
+        .anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn e0271_2() {
+    let source = r#"
+trait Future {
+    type Error;
+}
+
+impl<T, E> Future for Result<T, E> {
+    type Error = E;
+}
+
+impl<T> Future for Option<T> {
+    type Error = ();
+}
+
+struct Foo;
+
+fn foo() -> Box<dyn Future<Error=Foo>> {
+    Box::new(
+        Ok::<_, ()>(
+            Err::<(), _>(
+                Ok::<_, ()>(
+                    Err::<(), _>(
+                        Ok::<_, ()>(
+                            Err::<(), _>(Some(5))
+                        )
+                    )
+                )
+            )
+        )
+    )
+}
+fn main() {
+}
+"#;
+
+    let input_new = Level::Error
+        .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`")
+        .id("E0271")
+        .group(Group::new().element(Snippet::source(source)
+            .line_start(4)
+            .origin("$DIR/E0271.rs")
+            .fold(true)
+            .annotation(
+                AnnotationKind::Primary
+                    .span(208..510)
+                    .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"),
+            )))
+        .group(Group::new().element(
+            Level::Note.title("expected this to be `Foo`")
+        ).element(
+            Snippet::source(source)
+                .line_start(4)
+                .origin("$DIR/E0271.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(89..90))
+        ).element(
+            Level::Note
+                .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`")
+        ).element(
+            Level::Note.title("a second note"),
+        ));
+
+    let expected = str![[r#"
+error[E0271]: type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`
+   ╭▸ $DIR/E0271.rs:20:5
+   │
+LL │ ┏     Box::new(
+LL │ ┃         Ok::<_, ()>(
+LL │ ┃             Err::<(), _>(
+LL │ ┃                 Ok::<_, ()>(
+   ‡ ┃
+LL │ ┃     )
+   │ ┗━━━━━┛ type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`
+   ╰╴
+note: expected this to be `Foo`
+   ╭▸ $DIR/E0271.rs:10:18
+   │
+LL │     type Error = E;
+   │                  ━
+   ├ note: required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`
+   ╰ note: a second note
+"#]];
+    let renderer = Renderer::plain()
+        .term_width(40)
+        .theme(OutputTheme::Unicode)
+        .anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn long_e0308() {
+    let source = r#"
+mod a {
+    // Force the "short path for unique types" machinery to trip up
+    pub struct Atype;
+    pub struct Btype;
+    pub struct Ctype;
+}
+
+mod b {
+    pub struct Atype<T, K>(T, K);
+    pub struct Btype<T, K>(T, K);
+    pub struct Ctype<T, K>(T, K);
+}
+
+use b::*;
+
+fn main() {
+    let x: Atype<
+      Btype<
+        Ctype<
+          Atype<
+            Btype<
+              Ctype<
+                Atype<
+                  Btype<
+                    Ctype<i32, i32>,
+                    i32
+                  >,
+                  i32
+                >,
+                i32
+              >,
+              i32
+            >,
+            i32
+          >,
+          i32
+        >,
+        i32
+      >,
+      i32
+    > = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(
+        Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(
+            Ok("")
+        ))))))))))))))))))))))))))))))
+    ))))))))))))))))))))))))))))));
+    //~^^^^^ ERROR E0308
+
+    let _ = Some(Ok(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(
+        Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(
+            Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(
+                Some(Some(Some(Some(Some(Some(Some(Some(Some("")))))))))
+            )))))))))))))))))
+        ))))))))))))))))))
+    ))))))))))))))))) == Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(
+        Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(
+            Ok(Ok(Ok(Ok(Ok(Ok(Ok("")))))))
+        ))))))))))))))))))))))))))))))
+    ))))))))))))))))))))))));
+    //~^^^^^ ERROR E0308
+
+    let x: Atype<
+      Btype<
+        Ctype<
+          Atype<
+            Btype<
+              Ctype<
+                Atype<
+                  Btype<
+                    Ctype<i32, i32>,
+                    i32
+                  >,
+                  i32
+                >,
+                i32
+              >,
+              i32
+            >,
+            i32
+          >,
+          i32
+        >,
+        i32
+      >,
+      i32
+    > = ();
+    //~^ ERROR E0308
+
+    let _: () = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(
+        Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(
+            Ok(Ok(Ok(Ok(Ok(Ok(Ok("")))))))
+        ))))))))))))))))))))))))))))))
+    ))))))))))))))))))))))));
+    //~^^^^^ ERROR E0308
+}
+"#;
+
+    let input_new = Level::Error
+        .message("mismatched types")
+        .id("E0308")
+        .group(Group::new().element(
+            Snippet::source(source)
+                .line_start(7)
+                .origin("$DIR/long-E0308.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(719..1001)
+                        .label("expected `Atype<Btype<Ctype<..., i32>, i32>, i32>`, found `Result<Result<Result<..., _>, _>, _>`"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(293..716)
+                        .label("expected due to this"),
+                )
+        ).element(
+            Level::Note
+                .title("expected struct `Atype<Btype<..., i32>, i32>`\n     found enum `Result<Result<..., _>, _>`")
+        ).element(
+            Level::Note
+                .title("the full name for the type has been written to '$TEST_BUILD_DIR/$FILE.long-type-hash.txt'")
+        ).element(
+            Level::Note
+                .title("consider using `--verbose` to print the full type name to the console")
+                ,
+        ));
+
+    let expected = str![[r#"
+error[E0308]: mismatched types
+   ╭▸ $DIR/long-E0308.rs:48:9
+   │
+LL │        let x: Atype<
+   │ ┌─────────────┘
+LL │ │        Btype<
+LL │ │          Ctype<
+LL │ │            Atype<
+   ‡ │
+LL │ │        i32
+LL │ │      > = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(O…
+   │ │┏━━━━━│━━━┛
+   │ └┃─────┤
+   │  ┃     expected due to this
+LL │  ┃         Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(O…
+LL │  ┃             Ok("")
+LL │  ┃         ))))))))))))))))))))))))))))))
+LL │  ┃     ))))))))))))))))))))))))))))));
+   │  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ expected `Atype<Btype<Ctype<..., i32>, i32>, i32>`, found `Result<Result<Result<..., _>, _>, _>`
+   ├ note: expected struct `Atype<Btype<..., i32>, i32>`
+   │            found enum `Result<Result<..., _>, _>`
+   ├ note: the full name for the type has been written to '$TEST_BUILD_DIR/$FILE.long-type-hash.txt'
+   ╰ note: consider using `--verbose` to print the full type name to the console
+"#]];
+    let renderer = Renderer::plain()
+        .term_width(60)
+        .theme(OutputTheme::Unicode)
+        .anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}
+
+#[test]
+fn highlighting() {
+    let source = r#"
+use core::pin::Pin;
+use core::future::Future;
+use core::any::Any;
+
+fn query(_: fn(Box<(dyn Any + Send + '_)>) -> Pin<Box<(
+    dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static
+)>>) {}
+
+fn wrapped_fn<'a>(_: Box<(dyn Any + Send)>) -> Pin<Box<(
+    dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static
+)>> {
+    Box::pin(async { Err("nope".into()) })
+}
+
+fn main() {
+    query(wrapped_fn);
+}
+"#;
+
+    let input_new = Level::Error
+        .message("mismatched types")
+        .id("E0308")
+        .group(Group::new().element(
+            Snippet::source(source)
+                .line_start(7)
+                .origin("$DIR/unicode-output.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(430..440)
+                        .label("one type is more general than the other"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(424..429)
+                        .label("arguments to this function are incorrect"),
+                ),
+        ).element(
+            Level::Note
+                .title("expected fn pointer `for<'a> fn(Box<(dyn Any + Send + 'a)>) -> Pin<_>`\n      found fn item `fn(Box<(dyn Any + Send + 'static)>) -> Pin<_> {wrapped_fn}`")
+                ,
+        ))
+        .group(Group::new().element(
+            Level::Note.title("function defined here"),
+        ).element(
+            Snippet::source(source)
+                .line_start(7)
+                .origin("$DIR/unicode-output.rs")
+                .fold(true)
+                .annotation(AnnotationKind::Primary.span(77..210))
+                .annotation(AnnotationKind::Context.span(71..76)),
+        ));
+
+    let expected = str![[r#"
+error[E0308]: mismatched types
+   ╭▸ $DIR/unicode-output.rs:23:11
+   │
+LL │     query(wrapped_fn);
+   │     ┬──── ━━━━━━━━━━ one type is more general than the other
+   │     │
+   │     arguments to this function are incorrect
+   │
+   ╰ note: expected fn pointer `for<'a> fn(Box<(dyn Any + Send + 'a)>) -> Pin<_>`
+                 found fn item `fn(Box<(dyn Any + Send + 'static)>) -> Pin<_> {wrapped_fn}`
+note: function defined here
+   ╭▸ $DIR/unicode-output.rs:12:10
+   │
+LL │   fn query(_: fn(Box<(dyn Any + Send + '_)>) -> Pin<Box<(
+   │ ┏━━━━─────━┛
+LL │ ┃     dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static
+LL │ ┃ )>>) {}
+   ╰╴┗━━━┛
+"#]];
+    let renderer = Renderer::plain()
+        .theme(OutputTheme::Unicode)
+        .anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input_new), expected);
+}

From 57340c8c00e0b238df11f27fe4f116c561f9be8f Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sun, 9 Mar 2025 05:03:55 -0600
Subject: [PATCH 282/302] test: Add a test for source highlighting

---
 examples/highlight_source.rs  | 34 +++++++++++++++++++++++++++++++
 examples/highlight_source.svg | 38 +++++++++++++++++++++++++++++++++++
 tests/examples.rs             |  7 +++++++
 3 files changed, 79 insertions(+)
 create mode 100644 examples/highlight_source.rs
 create mode 100644 examples/highlight_source.svg

diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs
new file mode 100644
index 0000000..d49ff76
--- /dev/null
+++ b/examples/highlight_source.rs
@@ -0,0 +1,34 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+fn main() {
+    let source = r#"//@ compile-flags: -Z teach
+
+#![allow(warnings)]
+
+const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010
+//~| ERROR cannot call non-const method
+fn main() {}
+"#;
+    let message = Level::Error
+        .message("allocations are not allowed in constants")
+        .id("E0010")
+        .group(
+            Group::new()
+                .element(
+                    Snippet::source(source)
+                        .fold(true)
+                        .origin("$DIR/E0010-teach.rs")
+                        .annotation(
+                            AnnotationKind::Primary
+                                .span(72..85)
+                                .label("allocation not allowed in constants")
+                        ),
+                )
+                .element(
+                    Level::Note.title("The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created."),
+                ),
+        );
+
+    let renderer = Renderer::styled().anonymized_line_numbers(true);
+    anstream::println!("{}", renderer.render(message));
+}
diff --git a/examples/highlight_source.svg b/examples/highlight_source.svg
new file mode 100644
index 0000000..b6f9e82
--- /dev/null
+++ b/examples/highlight_source.svg
@@ -0,0 +1,38 @@
+<svg width="961px" height="146px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    .bold { font-weight: bold; }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0010]</tspan><tspan class="bold">: allocations are not allowed in constants</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/E0010-teach.rs:5:23</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> const CON: Vec&lt;i32&gt; = vec![1, 2, 3]; //~ ERROR E0010</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                       </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">allocation not allowed in constants</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created.</tspan>
+</tspan>
+    <tspan x="10px" y="136px">
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/examples.rs b/tests/examples.rs
index b657662..7708732 100644
--- a/tests/examples.rs
+++ b/tests/examples.rs
@@ -19,6 +19,13 @@ fn format() {
     assert_example(target, expected);
 }
 
+#[test]
+fn highlight_source() {
+    let target = "highlight_source";
+    let expected = snapbox::file!["../examples/highlight_source.svg": TermSvg];
+    assert_example(target, expected);
+}
+
 #[test]
 fn multislice() {
     let target = "multislice";

From 222c013d9cb828d1171b26811e4530dc8cb6e068 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sun, 9 Mar 2025 05:03:55 -0600
Subject: [PATCH 283/302] feat: Add ability to highlight source code

---
 examples/highlight_source.rs  |  1 +
 examples/highlight_source.svg |  2 +-
 src/renderer/mod.rs           | 12 ++++++++++++
 src/renderer/source_map.rs    | 14 +++++++++++++-
 src/snippet.rs                |  7 +++++++
 5 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs
index d49ff76..2bb4ec2 100644
--- a/examples/highlight_source.rs
+++ b/examples/highlight_source.rs
@@ -22,6 +22,7 @@ fn main() {}
                             AnnotationKind::Primary
                                 .span(72..85)
                                 .label("allocation not allowed in constants")
+                                .highlight_source(true),
                         ),
                 )
                 .element(
diff --git a/examples/highlight_source.svg b/examples/highlight_source.svg
index b6f9e82..391a097 100644
--- a/examples/highlight_source.svg
+++ b/examples/highlight_source.svg
@@ -25,7 +25,7 @@
 </tspan>
     <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
 </tspan>
-    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> const CON: Vec&lt;i32&gt; = vec![1, 2, 3]; //~ ERROR E0010</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> const CON: Vec&lt;i32&gt; = </tspan><tspan class="fg-bright-red bold">vec![1, 2, 3]</tspan><tspan>; //~ ERROR E0010</tspan>
 </tspan>
     <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>                       </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">allocation not allowed in constants</tspan>
 </tspan>
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 85de0f5..10f2b59 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1260,6 +1260,15 @@ impl Renderer {
                         underline.style,
                     );
                 }
+                _ if annotation.highlight_source => {
+                    buffer.set_style_range(
+                        line_offset,
+                        (code_offset + annotation.start.display).saturating_sub(left),
+                        (code_offset + annotation.end.display).saturating_sub(left),
+                        underline.style,
+                        annotation.is_primary(),
+                    );
+                }
                 _ => {}
             }
         }
@@ -2471,6 +2480,9 @@ pub(crate) struct LineAnnotation<'a> {
     /// Is this a single line, multiline or multiline span minimized down to a
     /// smaller span.
     pub annotation_type: LineAnnotationType,
+
+    /// Whether the source code should be highlighted
+    pub highlight_source: bool,
 }
 
 impl LineAnnotation<'_> {
diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs
index 416337c..33fe189 100644
--- a/src/renderer/source_map.rs
+++ b/src/renderer/source_map.rs
@@ -149,7 +149,13 @@ impl<'a> SourceMap<'a> {
             .collect::<Vec<_>>();
         let mut multiline_annotations = vec![];
 
-        for Annotation { range, label, kind } in annotations {
+        for Annotation {
+            range,
+            label,
+            kind,
+            highlight_source,
+        } in annotations
+        {
             let (lo, mut hi) = self.span_to_locations(range.clone());
 
             // Watch out for "empty spans". If we get a span like 6..6, we
@@ -169,6 +175,7 @@ impl<'a> SourceMap<'a> {
                     kind,
                     label,
                     annotation_type: LineAnnotationType::Singleline,
+                    highlight_source,
                 };
                 self.add_annotation_to_file(&mut annotated_line_infos, lo.line, line_ann);
             } else {
@@ -179,6 +186,7 @@ impl<'a> SourceMap<'a> {
                     kind,
                     label,
                     overlaps_exactly: false,
+                    highlight_source,
                 });
             }
         }
@@ -502,6 +510,7 @@ pub(crate) struct MultilineAnnotation<'a> {
     pub kind: AnnotationKind,
     pub label: Option<&'a str>,
     pub overlaps_exactly: bool,
+    pub highlight_source: bool,
 }
 
 impl<'a> MultilineAnnotation<'a> {
@@ -526,6 +535,7 @@ impl<'a> MultilineAnnotation<'a> {
             kind: self.kind,
             label: None,
             annotation_type: LineAnnotationType::MultilineStart(self.depth),
+            highlight_source: self.highlight_source,
         }
     }
 
@@ -541,6 +551,7 @@ impl<'a> MultilineAnnotation<'a> {
             kind: self.kind,
             label: self.label,
             annotation_type: LineAnnotationType::MultilineEnd(self.depth),
+            highlight_source: self.highlight_source,
         }
     }
 
@@ -551,6 +562,7 @@ impl<'a> MultilineAnnotation<'a> {
             kind: self.kind,
             label: None,
             annotation_type: LineAnnotationType::MultilineLine(self.depth),
+            highlight_source: self.highlight_source,
         }
     }
 }
diff --git a/src/snippet.rs b/src/snippet.rs
index d371ab8..6400284 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -273,6 +273,7 @@ pub struct Annotation<'a> {
     pub(crate) range: Range<usize>,
     pub(crate) label: Option<&'a str>,
     pub(crate) kind: AnnotationKind,
+    pub(crate) highlight_source: bool,
 }
 
 impl<'a> Annotation<'a> {
@@ -280,6 +281,11 @@ impl<'a> Annotation<'a> {
         self.label = Some(label);
         self
     }
+
+    pub fn highlight_source(mut self, highlight_source: bool) -> Self {
+        self.highlight_source = highlight_source;
+        self
+    }
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -296,6 +302,7 @@ impl AnnotationKind {
             range: span,
             label: None,
             kind: self,
+            highlight_source: false,
         }
     }
 

From 481920d56fd1b573dfac210ab32bd255b8f2ad84 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 15 Apr 2025 22:11:46 -0600
Subject: [PATCH 284/302] test: Add a test for passing precolored text

---
 examples/highlight_title.rs  | 68 ++++++++++++++++++++++++++++++++++++
 examples/highlight_title.svg | 44 +++++++++++++++++++++++
 tests/examples.rs            |  7 ++++
 3 files changed, 119 insertions(+)
 create mode 100644 examples/highlight_title.rs
 create mode 100644 examples/highlight_title.svg

diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs
new file mode 100644
index 0000000..bb09049
--- /dev/null
+++ b/examples/highlight_title.rs
@@ -0,0 +1,68 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+use anstyle::Effects;
+
+fn main() {
+    let source = r#"// Make sure "highlighted" code is colored purple
+
+//@ compile-flags: --error-format=human --color=always
+//@ error-pattern:for<'a> 
+//@ edition:2018
+
+use core::pin::Pin;
+use core::future::Future;
+use core::any::Any;
+
+fn query(_: fn(Box<(dyn Any + Send + '_)>) -> Pin<Box<(
+    dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static
+)>>) {}
+
+fn wrapped_fn<'a>(_: Box<(dyn Any + Send)>) -> Pin<Box<(
+    dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static
+)>> {
+    Box::pin(async { Err("nope".into()) })
+}
+
+fn main() {
+    query(wrapped_fn);
+}
+"#;
+
+    let magenta = annotate_snippets::renderer::AnsiColor::Magenta
+        .on_default()
+        .effects(Effects::BOLD);
+    let title = format!(
+        "expected fn pointer `{}for<'a>{} fn(Box<{}(dyn Any + Send + 'a){}>) -> Pin<_>`
+      found fn item `fn(Box<{}(dyn Any + Send + 'static){}>) -> Pin<_> {}{{wrapped_fn}}{}`",
+        magenta.render(),
+        magenta.render_reset(),
+        magenta.render(),
+        magenta.render_reset(),
+        magenta.render(),
+        magenta.render_reset(),
+        magenta.render(),
+        magenta.render_reset()
+    );
+
+    let message = Level::Error.message("mismatched types").id("E0308").group(
+        Group::new()
+            .element(
+                Snippet::source(source)
+                    .fold(true)
+                    .origin("$DIR/highlighting.rs")
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(589..599)
+                            .label("one type is more general than the other"),
+                    )
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(583..588)
+                            .label("arguments to this function are incorrect"),
+                    ),
+            )
+            .element(Level::Note.title(&title)),
+    );
+
+    let renderer = Renderer::styled().anonymized_line_numbers(true);
+    anstream::println!("{}", renderer.render(message));
+}
diff --git a/examples/highlight_title.svg b/examples/highlight_title.svg
new file mode 100644
index 0000000..763d33b
--- /dev/null
+++ b/examples/highlight_title.svg
@@ -0,0 +1,44 @@
+<svg width="1003px" height="200px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    .bold { font-weight: bold; }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/highlighting.rs:22:11</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     query(wrapped_fn);</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-blue bold">-----</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">one type is more general than the other</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-blue bold">|</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-blue bold">arguments to this function are incorrect</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected fn pointer `␛[1m␛[35mfor&lt;'a&gt;␛[0m fn(Box&lt;␛[1m␛[35m(dyn Any + Send + 'a)␛[0m&gt;) -&gt; Pin&lt;_&gt;`</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan>                 found fn item `fn(Box&lt;␛[1m␛[35m(dyn Any + Send + 'static)␛[0m&gt;) -&gt; Pin&lt;_&gt; ␛[1m␛[35m{wrapped_fn}␛[0m`</tspan>
+</tspan>
+    <tspan x="10px" y="190px">
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/examples.rs b/tests/examples.rs
index 7708732..a0504c1 100644
--- a/tests/examples.rs
+++ b/tests/examples.rs
@@ -26,6 +26,13 @@ fn highlight_source() {
     assert_example(target, expected);
 }
 
+#[test]
+fn highlight_title() {
+    let target = "highlight_title";
+    let expected = snapbox::file!["../examples/highlight_title.svg": TermSvg];
+    assert_example(target, expected);
+}
+
 #[test]
 fn multislice() {
     let target = "multislice";

From 19f692d1f9b6c3e089556af2cd29811dfe9795cb Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 16 Apr 2025 09:56:43 -0600
Subject: [PATCH 285/302] feat: Don't normailze title text

---
 examples/highlight_title.svg | 7 ++++---
 src/lib.rs                   | 7 +++++++
 src/renderer/mod.rs          | 7 +++----
 src/snippet.rs               | 4 ++++
 4 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/examples/highlight_title.svg b/examples/highlight_title.svg
index 763d33b..ad35876 100644
--- a/examples/highlight_title.svg
+++ b/examples/highlight_title.svg
@@ -1,9 +1,10 @@
-<svg width="1003px" height="200px" xmlns="http://www.w3.org/2000/svg">
+<svg width="785px" height="200px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
     .fg-bright-red { fill: #FF5555 }
+    .fg-magenta { fill: #AA00AA }
     .container {
       padding: 0 10px;
       line-height: 18px;
@@ -33,9 +34,9 @@
 </tspan>
     <tspan x="10px" y="136px"><tspan>   </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan>     </tspan><tspan class="fg-bright-blue bold">arguments to this function are incorrect</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected fn pointer `␛[1m␛[35mfor&lt;'a&gt;␛[0m fn(Box&lt;␛[1m␛[35m(dyn Any + Send + 'a)␛[0m&gt;) -&gt; Pin&lt;_&gt;`</tspan>
+    <tspan x="10px" y="154px"><tspan>   </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected fn pointer `</tspan><tspan class="fg-magenta bold">for&lt;'a&gt;</tspan><tspan> fn(Box&lt;</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'a)</tspan><tspan>&gt;) -&gt; Pin&lt;_&gt;`</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>                 found fn item `fn(Box&lt;␛[1m␛[35m(dyn Any + Send + 'static)␛[0m&gt;) -&gt; Pin&lt;_&gt; ␛[1m␛[35m{wrapped_fn}␛[0m`</tspan>
+    <tspan x="10px" y="172px"><tspan>                 found fn item `fn(Box&lt;</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'static)</tspan><tspan>&gt;) -&gt; Pin&lt;_&gt; </tspan><tspan class="fg-magenta bold">{wrapped_fn}</tspan><tspan>`</tspan>
 </tspan>
     <tspan x="10px" y="190px">
 </tspan>
diff --git a/src/lib.rs b/src/lib.rs
index 533e8f7..e6c49c6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -45,6 +45,13 @@
 pub mod renderer;
 mod snippet;
 
+/// Normalize the string to avoid any unicode control characters.
+/// This is important for untrusted input, as it can contain
+/// invalid unicode sequences.
+pub fn normalize_untrusted_str(s: &str) -> String {
+    renderer::normalize_whitespace(s)
+}
+
 #[doc(inline)]
 pub use renderer::Renderer;
 pub use snippet::ColumnSeparator;
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 10f2b59..ab3db82 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -573,8 +573,7 @@ impl Renderer {
         } else {
             ElementStyle::NoStyle
         };
-        let text = &normalize_whitespace(title);
-        let lines = text.split('\n').collect::<Vec<_>>();
+        let lines = title.split('\n').collect::<Vec<_>>();
         if lines.len() > 1 {
             for (i, line) in lines.iter().enumerate() {
                 if i != 0 {
@@ -584,7 +583,7 @@ impl Renderer {
                 buffer.append(line_number, line, style);
             }
         } else {
-            buffer.append(line_number, text, style);
+            buffer.append(line_number, title, style);
         }
         line_number
     }
@@ -2590,7 +2589,7 @@ const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
     ('\u{2069}', "�"),
 ];
 
-fn normalize_whitespace(s: &str) -> String {
+pub(crate) fn normalize_whitespace(s: &str) -> String {
     // Scan the input string for a character in the ordered table above.
     // If it's present, replace it with its alternative string (it can be more than 1 char!).
     // Otherwise, retain the input char.
diff --git a/src/snippet.rs b/src/snippet.rs
index 6400284..f71c392 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -177,6 +177,10 @@ impl Level {
         }
     }
 
+    /// Text passed to this function is allowed to be pre-styled, as such all
+    /// text is considered "trusted input" and has no normalizations applied to
+    /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be
+    /// used to normalize untrusted text before it is passed to this function.
     pub fn title(self, title: &str) -> Title<'_> {
         Title {
             level: self,

From b4373f3effd8a0899c496393f87eb6802d546f23 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Wed, 16 Apr 2025 10:02:22 -0600
Subject: [PATCH 286/302] docs: Add when text is normalized

---
 src/snippet.rs | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/src/snippet.rs b/src/snippet.rs
index f71c392..e8fa18c 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -166,6 +166,9 @@ pub enum Level {
 }
 
 impl Level {
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
     pub fn message(self, title: &str) -> Message<'_> {
         Message {
             id: None,
@@ -222,6 +225,9 @@ pub struct Snippet<'a, T> {
 }
 
 impl<'a, T: Clone> Snippet<'a, T> {
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
     pub fn source(source: &'a str) -> Self {
         Self {
             origin: None,
@@ -237,6 +243,9 @@ impl<'a, T: Clone> Snippet<'a, T> {
         self
     }
 
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
     pub fn origin(mut self, origin: &'a str) -> Self {
         self.origin = Some(origin);
         self
@@ -281,6 +290,9 @@ pub struct Annotation<'a> {
 }
 
 impl<'a> Annotation<'a> {
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
     pub fn label(mut self, label: &'a str) -> Self {
         self.label = Some(label);
         self
@@ -322,6 +334,9 @@ pub struct Patch<'a> {
 }
 
 impl<'a> Patch<'a> {
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
     pub fn new(range: Range<usize>, replacement: &'a str) -> Self {
         Self { range, replacement }
     }
@@ -384,6 +399,9 @@ pub struct Origin<'a> {
 }
 
 impl<'a> Origin<'a> {
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
     pub fn new(origin: &'a str) -> Self {
         Self {
             origin,
@@ -409,6 +427,9 @@ impl<'a> Origin<'a> {
         self
     }
 
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
     pub fn label(mut self, label: &'a str) -> Self {
         self.label = Some(label);
         self

From 15fbf20cbc84c822378fa5b5cf8d33dd9aeb9e59 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 29 Mar 2025 22:31:44 -0600
Subject: [PATCH 287/302] test: Add tests margin cutting

---
 tests/formatter.rs | 273 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 273 insertions(+)

diff --git a/tests/formatter.rs b/tests/formatter.rs
index 6304cf4..df6f965 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -2112,3 +2112,276 @@ LL │ ┃ )>>) {}
         .anonymized_line_numbers(true);
     assert_data_eq!(renderer.render(input_new), expected);
 }
+
+// This tests that an ellipsis is not inserted into Unicode text when a line
+// wasn't actually trimmed.
+//
+// This is a regression test where `...` was inserted because the code wasn't
+// properly accounting for the *rendered* length versus the length in bytes in
+// all cases.
+#[test]
+fn unicode_cut_handling() {
+    let source = "version = \"0.1.0\"\n# Ensure that the spans from toml handle utf-8 correctly\nauthors = [\n    { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }\n]\n";
+    let input = Level::Error.message("title").group(
+        Group::new().element(
+            Snippet::source(source)
+                .fold(false)
+                .annotation(AnnotationKind::Primary.span(85..228).label("annotation")),
+        ),
+    );
+    let expected = str![[r#"
+error: title
+  |
+1 |   version = "0.1.0"
+2 |   # Ensure that the spans from toml handle utf-8 correctly
+3 |   authors = [
+  |  ___________^
+4 | |     { name = "Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯...A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘", email = 1 }
+5 | | ]
+  | |_^ annotation
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input), expected);
+}
+
+#[test]
+fn unicode_cut_handling2() {
+    let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
+    let input = Level::Error
+        .message("expected item, found `?`")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .fold(false)
+                    .annotation(AnnotationKind::Primary.span(499..500).label("expected item"))
+            ).element(
+                Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
+            )
+        );
+
+    let expected = str![[r#"
+error: expected item, found `?`
+  |
+1.|....
+  |^ expected item
+  = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
+"#]];
+
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input), expected);
+}
+
+#[test]
+fn unicode_cut_handling3() {
+    let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
+    let input = Level::Error
+        .message("expected item, found `?`")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .fold(false)
+                    .annotation(AnnotationKind::Primary.span(251..254).label("expected item"))
+            ).element(
+                Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
+            )
+        );
+
+    let expected = str![[r#"
+error: expected item, found `?`
+  |
+1 | ...的。这是宽的。*/?       ...
+^ | expected item
+  = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
+"#]];
+
+    let renderer = Renderer::plain().term_width(43);
+    assert_data_eq!(renderer.render(input), expected);
+}
+
+#[test]
+fn unicode_cut_handling4() {
+    let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?";
+    let input = Level::Error
+        .message("expected item, found `?`")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .fold(false)
+                    .annotation(AnnotationKind::Primary.span(334..335).label("expected item"))
+            ).element(
+                Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
+            )
+        );
+
+    let expected = str![[r#"
+error: expected item, found `?`
+  |
+1 | ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?
+  |                                                             ^ expected item
+  = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
+"#]];
+
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input), expected);
+}
+
+#[test]
+fn diagnostic_width() {
+    let source = r##"// ignore-tidy-linelength
+
+fn main() {
+    let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓  ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀🦀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42;  let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓  ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀🦀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4";
+//~^ ERROR mismatched types
+}
+"##;
+    let input = Level::Error.message("mismatched types").id("E0308").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("$DIR/non-whitespace-trimming-unicode.rs")
+                .fold(true)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(1207..1209)
+                        .label("expected `()`, found integer"),
+                )
+                .annotation(
+                    AnnotationKind::Context
+                        .span(1202..1204)
+                        .label("expected due to this"),
+                ),
+        ),
+    );
+
+    let expected = str![[r#"
+error[E0308]: mismatched types
+  --> $DIR/non-whitespace-trimming-unicode.rs:4:415
+   |
+LL | ...♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42;  let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓  ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂...
+   |                                         --   ^^ expected `()`, found integer
+   |                                         |
+   |                                         expected due to this
+"#]];
+
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input), expected);
+}
+
+#[test]
+fn diagnostic_width2() {
+    let source = r##"//@ revisions: ascii unicode
+//@[unicode] compile-flags: -Zunstable-options --error-format=human-unicode
+// ignore-tidy-linelength
+
+fn main() {
+    let unicode_is_fun = "؁‱ஹ௸௵꧄.ဪ꧅⸻𒈙𒐫﷽𒌄𒈟𒍼𒁎𒀱𒌧𒅃 𒈓𒍙𒊎𒄡𒅌𒁏𒀰𒐪𒐩𒈙𒐫𪚥";
+    let _ = "ༀ༁༂༃༄༅༆༇༈༉༊་༌།༎༏༐༑༒༓༔༕༖༗༘༙༚༛༜༝༞༟༠༡༢༣༤༥༦༧༨༩༪༫༬༭༮༯༰༱༲༳༴༵༶༷༸༹༺༻༼༽༾༿ཀཁགགྷངཅཆཇ཈ཉཊཋཌཌྷཎཏཐདདྷནཔཕབབྷམཙཚཛཛྷཝཞཟའཡརལཤཥསཧཨཀྵཪཫཬ཭཮཯཰ཱཱཱིིུུྲྀཷླྀཹེཻོཽཾཿ྄ཱྀྀྂྃ྅྆྇ྈྉྊྋྌྍྎྏྐྑྒྒྷྔྕྖྗ྘ྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼ྽྾྿࿀࿁࿂࿃࿄࿅࿆࿇࿈࿉࿊࿋࿌࿍࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!";
+    //[ascii]~^ ERROR cannot add `&str` to `&str`
+}
+"##;
+    let input = Level::Error
+        .message("cannot add `&str` to `&str`")
+        .id("E0369")
+        .group(
+            Group::new()
+                .element(
+                    Snippet::source(source)
+                        .origin("$DIR/non-1-width-unicode-multiline-label.rs")
+                        .fold(true)
+                        .annotation(AnnotationKind::Context.span(970..984).label("&str"))
+                        .annotation(AnnotationKind::Context.span(987..1001).label("&str"))
+                        .annotation(
+                            AnnotationKind::Primary
+                                .span(985..986)
+                                .label("`+` cannot be used to concatenate two `&str` strings"),
+                        ),
+                )
+                .element(
+                    Level::Note
+                        .title("string concatenation requires an owned `String` on the left"),
+                ),
+        )
+        .group(
+            Group::new()
+                .element(Level::Help.title("create an owned `String` from a string reference"))
+                .element(
+                    Snippet::source(source)
+                        .origin("$DIR/non-1-width-unicode-multiline-label.rs")
+                        .fold(true)
+                        .patch(Patch::new(984..984, ".to_owned()")),
+                ),
+        );
+
+    let expected = str![[r#"
+error[E0369]: cannot add `&str` to `&str`
+   ╭▸ $DIR/non-1-width-unicode-multiline-label.rs:7:260
+   │
+LL │ …ཽཾཿ྄ཱྀྀྂྃ྅྆྇ྈྉྊྋྌྍྎྏྐྑྒྒྷྔྕྖྗ྘ྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼ྽྾྿࿀࿁࿂࿃࿄࿅࿆࿇࿈࿉࿊࿋…࿍࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!";
+   │                                                  ┬───────────── ┯ ────────────── &str
+   │                                                  │              │
+   │                                                  │              `+` cannot be used to concatenate two `&str` strings
+   │                                                  &str
+   │
+   ╰ note: string concatenation requires an owned `String` on the left
+help: create an owned `String` from a string reference
+   ╭╴
+LL │     let _ = "ༀ༁༂༃༄༅༆༇༈༉༊་༌།༎༏༐༑༒༓༔༕༖༗༘༙༚༛༜༝༞༟༠༡༢༣༤༥༦༧༨༩༪༫༬༭༮༯༰༱༲༳༴༵༶༷༸༹༺༻༼༽༾༿ཀཁགགྷངཅཆཇ཈ཉཊཋཌཌྷཎཏཐདདྷནཔཕབབྷམཙཚཛཛྷཝཞཟའཡརལཤཥསཧཨཀྵཪཫཬ཭཮཯཰ཱཱཱིིུུྲྀཷླྀཹེཻོཽཾཿ྄ཱྀྀྂྃ྅྆྇ྈྉྊྋྌྍྎྏྐྑྒྒྷྔྕྖྗ྘ྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼ྽྾྿࿀࿁࿂࿃࿄࿅࿆࿇࿈࿉࿊࿋࿌࿍࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun.to_owned() + " really fun!";
+   ╰╴                                                                                                                                                                                        +++++++++++
+"#]];
+
+    let renderer = Renderer::plain()
+        .anonymized_line_numbers(true)
+        .theme(OutputTheme::Unicode);
+    assert_data_eq!(renderer.render(input), expected);
+}
+
+#[test]
+fn macros_not_utf8() {
+    let source = r##"//@ error-pattern: did not contain valid UTF-8
+//@ reference: input.encoding.utf8
+//@ reference: input.encoding.invalid
+
+fn foo() {
+    include!("not-utf8.bin");
+}
+"##;
+    let bin_source = "�|�\u{0002}!5�cc\u{0015}\u{0002}�Ӻi��WWj�ȥ�'�}�\u{0012}�J�ȉ��W�\u{001e}O�@����\u{001c}w�V���LO����\u{0014}[ \u{0003}_�'���SQ�~ذ��ų&��-\t��lN~��!@␌ _#���kQ��h�\u{001d}�:�\u{001c}\u{0007}�";
+    let input = Level::Error
+        .message("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .origin("$DIR/not-utf8.rs")
+                    .fold(true)
+                    .annotation(AnnotationKind::Primary.span(136..160)),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(Level::Note.title("byte `193` is not valid utf-8"))
+                .element(
+                    Snippet::source(bin_source)
+                        .origin("$DIR/not-utf8.bin")
+                        .fold(true)
+                        .annotation(AnnotationKind::Primary.span(0..0)),
+                )
+                .element(Level::Note.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")),
+        );
+
+    let expected = str![[r#"
+error: couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8
+  --> $DIR/not-utf8.rs:6:5
+   |
+LL |     include!("not-utf8.bin");
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: byte `193` is not valid utf-8
+  --> $DIR/not-utf8.bin:1:1
+   |
+LL | �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W�␞O�@����␜w�V���LO����␔[ ␃_�'���SQ�~ذ��ų&��-    ��lN~��!@␌ _#���kQ��h�␝�:�...
+   | ^
+   = note: this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)
+"#]];
+
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input), expected);
+}

From 604b6f95a979ef1b2273d3e13d96907638f1f585 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 29 Mar 2025 22:31:44 -0600
Subject: [PATCH 288/302] fix: Handle margin cutting when encountering
 multibyte chars

---
 src/renderer/mod.rs | 72 +++++++++++++++++++++++++++++++++++----------
 tests/formatter.rs  | 30 +++++++++----------
 2 files changed, 71 insertions(+), 31 deletions(-)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index ab3db82..7d4fd0f 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -956,12 +956,19 @@ impl Renderer {
         let line_offset = buffer.num_lines();
 
         // Left trim
-        let left = margin.left(source_string.len());
+        let left = margin.left(str_width(&source_string));
 
         // FIXME: This looks fishy. See #132860.
         // Account for unicode characters of width !=0 that were removed.
-        let left = source_string.chars().take(left).map(char_width).sum();
+        let mut taken = 0;
+        source_string.chars().for_each(|ch| {
+            let next = char_width(ch);
+            if taken + next <= left {
+                taken += next;
+            }
+        });
 
+        let left = taken;
         self.draw_line(
             buffer,
             &source_string,
@@ -2020,48 +2027,81 @@ impl Renderer {
     ) {
         // Tabs are assumed to have been replaced by spaces in calling code.
         debug_assert!(!source_string.contains('\t'));
-        let line_len = source_string.len();
+        let line_len = str_width(source_string);
         // Create the source line we will highlight.
         let left = margin.left(line_len);
         let right = margin.right(line_len);
         // FIXME: The following code looks fishy. See #132860.
         // On long lines, we strip the source line, accounting for unicode.
         let mut taken = 0;
+        let mut skipped = 0;
         let code: String = source_string
             .chars()
-            .skip(left)
+            .skip_while(|ch| {
+                skipped += char_width(*ch);
+                skipped <= left
+            })
             .take_while(|ch| {
                 // Make sure that the trimming on the right will fall within the terminal width.
-                let next = char_width(*ch);
-                if taken + next > right - left {
-                    return false;
-                }
-                taken += next;
-                true
+                taken += char_width(*ch);
+                taken <= (right - left)
             })
             .collect();
 
         buffer.puts(line_offset, code_offset, &code, ElementStyle::Quotation);
         let placeholder = self.margin();
-        if margin.was_cut_left() {
+        let padding = str_width(placeholder);
+        let (width_taken, bytes_taken) = if margin.was_cut_left() {
             // We have stripped some code/whitespace from the beginning, make it clear.
+            let mut bytes_taken = 0;
+            let mut width_taken = 0;
+            for ch in code.chars() {
+                width_taken += char_width(ch);
+                bytes_taken += ch.len_utf8();
+
+                if width_taken >= padding {
+                    break;
+                }
+            }
             buffer.puts(
                 line_offset,
                 code_offset,
-                placeholder,
+                &format!("{placeholder:>width_taken$}"),
                 ElementStyle::LineNumber,
             );
-        }
+            (width_taken, bytes_taken)
+        } else {
+            (0, 0)
+        };
+
+        buffer.puts(
+            line_offset,
+            code_offset + width_taken,
+            &code[bytes_taken..],
+            ElementStyle::Quotation,
+        );
+
         if margin.was_cut_right(line_len) {
-            let padding = str_width(placeholder);
-            // We have stripped some code after the rightmost span end, make it clear we did so.
+            // We have stripped some code/whitespace from the beginning, make it clear.
+            let mut char_taken = 0;
+            let mut width_taken_inner = 0;
+            for ch in code.chars().rev() {
+                width_taken_inner += char_width(ch);
+                char_taken += 1;
+
+                if width_taken_inner >= padding {
+                    break;
+                }
+            }
+
             buffer.puts(
                 line_offset,
-                code_offset + taken - padding,
+                code_offset + width_taken + code[bytes_taken..].chars().count() - char_taken,
                 placeholder,
                 ElementStyle::LineNumber,
             );
         }
+
         buffer.puts(
             line_offset,
             0,
diff --git a/tests/formatter.rs b/tests/formatter.rs
index df6f965..5deb21a 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -2136,7 +2136,7 @@ error: title
 2 |   # Ensure that the spans from toml handle utf-8 correctly
 3 |   authors = [
   |  ___________^
-4 | |     { name = "Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯...A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘", email = 1 }
+4 | |     { name = "Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘", email = 1 }
 5 | | ]
   | |_^ annotation
 "#]];
@@ -2162,8 +2162,8 @@ fn unicode_cut_handling2() {
     let expected = str![[r#"
 error: expected item, found `?`
   |
-1.|....
-  |^ expected item
+1 |  ...的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?
+  |                                                             ^ expected item
   = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
 "#]];
 
@@ -2189,8 +2189,8 @@ fn unicode_cut_handling3() {
     let expected = str![[r#"
 error: expected item, found `?`
   |
-1 | ...的。这是宽的。*/?       ...
-^ | expected item
+1 |  ...。这是宽的。这是宽的。这是宽的...
+  |            ^^ expected item
   = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
 "#]];
 
@@ -2256,10 +2256,10 @@ fn main() {
 error[E0308]: mismatched types
   --> $DIR/non-whitespace-trimming-unicode.rs:4:415
    |
-LL | ...♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42;  let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓  ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂...
-   |                                         --   ^^ expected `()`, found integer
-   |                                         |
-   |                                         expected due to this
+LL | ...♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42;  let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓  ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷...
+   |                                                  --   ^^ expected `()`, found integer
+   |                                                  |
+   |                                                  expected due to this
 "#]];
 
     let renderer = Renderer::plain().anonymized_line_numbers(true);
@@ -2315,11 +2315,11 @@ fn main() {
 error[E0369]: cannot add `&str` to `&str`
    ╭▸ $DIR/non-1-width-unicode-multiline-label.rs:7:260
    │
-LL │ …ཽཾཿ྄ཱྀྀྂྃ྅྆྇ྈྉྊྋྌྍྎྏྐྑྒྒྷྔྕྖྗ྘ྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼ྽྾྿࿀࿁࿂࿃࿄࿅࿆࿇࿈࿉࿊࿋…࿍࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!";
-   │                                                  ┬───────────── ┯ ────────────── &str
-   │                                                  │              │
-   │                                                  │              `+` cannot be used to concatenate two `&str` strings
-   │                                                  &str
+LL │ …࿆࿇࿈࿉࿊࿋࿌࿍࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!";
+   │                                  ┬───────────── ┯ ────────────── &str
+   │                                  │              │
+   │                                  │              `+` cannot be used to concatenate two `&str` strings
+   │                                  &str
    │
    ╰ note: string concatenation requires an owned `String` on the left
 help: create an owned `String` from a string reference
@@ -2377,7 +2377,7 @@ LL |     include!("not-utf8.bin");
 note: byte `193` is not valid utf-8
   --> $DIR/not-utf8.bin:1:1
    |
-LL | �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W�␞O�@����␜w�V���LO����␔[ ␃_�'���SQ�~ذ��ų&��-    ��lN~��!@␌ _#���kQ��h�␝�:�...
+LL | �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W�␞O�@����␜w�V���LO����␔[ ␃_�'���SQ�~ذ��ų&��-    ��lN~��!@␌ _#���kQ��h�␝�:�␜␇�
    | ^
    = note: this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)
 "#]];

From ab5ca1802efc7d34a861da156133ff0ba57f0878 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 15 Apr 2025 02:17:40 -0600
Subject: [PATCH 289/302] test: Add tests for custom Levels

---
 examples/custom_error.rs  | 35 ++++++++++++++++++++
 examples/custom_error.svg | 35 ++++++++++++++++++++
 examples/custom_level.rs  | 69 +++++++++++++++++++++++++++++++++++++++
 examples/custom_level.svg | 61 ++++++++++++++++++++++++++++++++++
 tests/examples.rs         | 14 ++++++++
 5 files changed, 214 insertions(+)
 create mode 100644 examples/custom_error.rs
 create mode 100644 examples/custom_error.svg
 create mode 100644 examples/custom_level.rs
 create mode 100644 examples/custom_level.svg

diff --git a/examples/custom_error.rs b/examples/custom_error.rs
new file mode 100644
index 0000000..3ba234d
--- /dev/null
+++ b/examples/custom_error.rs
@@ -0,0 +1,35 @@
+use annotate_snippets::renderer::OutputTheme;
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+fn main() {
+    let source = r#"//@ compile-flags: -Ztreat-err-as-bug
+//@ failure-status: 101
+//@ error-pattern: aborting due to `-Z treat-err-as-bug=1`
+//@ error-pattern: [eval_static_initializer] evaluating initializer of static `C`
+//@ normalize-stderr: "note: .*\n\n" -> ""
+//@ normalize-stderr: "thread 'rustc' panicked.*:\n.*\n" -> ""
+//@ rustc-env:RUST_BACKTRACE=0
+
+#![crate_type = "rlib"]
+
+pub static C: u32 = 0 - 1;
+//~^ ERROR could not evaluate static initializer
+"#;
+    let message = Level::None
+        .message("error: internal compiler error[E0080]: could not evaluate static initializer")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .origin("$DIR/err.rs")
+                    .fold(true)
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(386..391)
+                            .label("attempt to compute `0_u32 - 1_u32`, which would overflow"),
+                    ),
+            ),
+        );
+
+    let renderer = Renderer::styled().theme(OutputTheme::Unicode);
+    anstream::println!("{}", renderer.render(message));
+}
diff --git a/examples/custom_error.svg b/examples/custom_error.svg
new file mode 100644
index 0000000..5cb0c72
--- /dev/null
+++ b/examples/custom_error.svg
@@ -0,0 +1,35 @@
+<svg width="751px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    .bold { font-weight: bold; }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan class="bold">error: internal compiler error[E0080]: could not evaluate static initializer</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/err.rs:11:21</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">│</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> pub static C: u32 = 0 - 1;</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan>                    ━━━━━ attempt to compute `0_u32 - 1_u32`, which would overflow</tspan>
+</tspan>
+    <tspan x="10px" y="118px">
+</tspan>
+  </text>
+
+</svg>
diff --git a/examples/custom_level.rs b/examples/custom_level.rs
new file mode 100644
index 0000000..c19cdc4
--- /dev/null
+++ b/examples/custom_level.rs
@@ -0,0 +1,69 @@
+use annotate_snippets::renderer::OutputTheme;
+use annotate_snippets::{AnnotationKind, Group, Level, Patch, Renderer, Snippet};
+
+fn main() {
+    let source = r#"// Regression test for issue #114529
+// Tests that we do not ICE during const eval for a
+// break-with-value in contexts where it is illegal
+
+#[allow(while_true)]
+fn main() {
+    [(); {
+        while true {
+            break 9; //~ ERROR `break` with value from a `while` loop
+        };
+        51
+    }];
+
+    [(); {
+        while let Some(v) = Some(9) {
+            break v; //~ ERROR `break` with value from a `while` loop
+        };
+        51
+    }];
+
+    while true {
+        break (|| { //~ ERROR `break` with value from a `while` loop
+            let local = 9;
+        });
+    }
+}
+"#;
+    let message = Level::Error
+        .message("`break` with value from a `while` loop")
+        .id("E0571")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .line_start(1)
+                    .origin("$DIR/issue-114529-illegal-break-with-value.rs")
+                    .fold(true)
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(483..581)
+                            .label("can only break with a value inside `loop` or breakable block"),
+                    )
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(462..472)
+                            .label("you can't `break` with a value in a `while` loop"),
+                    ),
+            ),
+        )
+        .group(
+            Group::new()
+                .element(Level::None.title(
+                    "suggestion: use `break` on its own without a value inside this `while` loop",
+                ))
+                .element(
+                    Snippet::source(source)
+                        .line_start(1)
+                        .origin("$DIR/issue-114529-illegal-break-with-value.rs")
+                        .fold(true)
+                        .patch(Patch::new(483..581, "break")),
+                ),
+        );
+
+    let renderer = Renderer::styled().theme(OutputTheme::Unicode);
+    anstream::println!("{}", renderer.render(message));
+}
diff --git a/examples/custom_level.svg b/examples/custom_level.svg
new file mode 100644
index 0000000..1f31e65
--- /dev/null
+++ b/examples/custom_level.svg
@@ -0,0 +1,61 @@
+<svg width="740px" height="344px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .fg-bright-blue { fill: #5555FF }
+    .fg-bright-green { fill: #55FF55 }
+    .fg-bright-red { fill: #FF5555 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    .bold { font-weight: bold; }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0571]</tspan><tspan class="bold">: `break` with value from a `while` loop</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/issue-114529-illegal-break-with-value.rs:22:9</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>   </tspan><tspan class="fg-bright-blue bold">│</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">21</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan>       while true {</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan>       </tspan><tspan class="fg-bright-blue bold">──────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">you can't `break` with a value in a `while` loop</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┏</tspan><tspan>         break (|| { //~ ERROR `break` with value from a `while` loop</tspan>
+</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan>             let local = 9;</tspan>
+</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan>         });</tspan>
+</tspan>
+    <tspan x="10px" y="172px"><tspan>   </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┗━━━━━━━━━━┛</tspan><tspan> </tspan><tspan class="fg-bright-red bold">can only break with a value inside `loop` or breakable block</tspan>
+</tspan>
+    <tspan x="10px" y="190px"><tspan>   </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
+</tspan>
+    <tspan x="10px" y="208px"><tspan class="bold">suggestion: use `break` on its own without a value inside this `while` loop</tspan>
+</tspan>
+    <tspan x="10px" y="226px"><tspan>   </tspan><tspan class="fg-bright-blue bold">╭╴</tspan>
+</tspan>
+    <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan>        break (|| { //~ ERROR `break` with value from a `while` loop</tspan>
+</tspan>
+    <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan>            let local = 9;</tspan>
+</tspan>
+    <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan>        </tspan><tspan class="fg-bright-red">})</tspan><tspan>;</tspan>
+</tspan>
+    <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan>        </tspan><tspan class="fg-bright-green">break</tspan><tspan>;</tspan>
+</tspan>
+    <tspan x="10px" y="316px"><tspan>   </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
+</tspan>
+    <tspan x="10px" y="334px">
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/examples.rs b/tests/examples.rs
index a0504c1..66dd94b 100644
--- a/tests/examples.rs
+++ b/tests/examples.rs
@@ -1,3 +1,17 @@
+#[test]
+fn custom_error() {
+    let target = "custom_error";
+    let expected = snapbox::file!["../examples/custom_error.svg": TermSvg];
+    assert_example(target, expected);
+}
+
+#[test]
+fn custom_level() {
+    let target = "custom_level";
+    let expected = snapbox::file!["../examples/custom_level.svg": TermSvg];
+    assert_example(target, expected);
+}
+
 #[test]
 fn expected_type() {
     let target = "expected_type";

From 353a040447d6284df6711c7b9cb8ed86302971d8 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Tue, 1 Apr 2025 18:27:45 -0600
Subject: [PATCH 290/302] feat: Add custom Levels

---
 benches/bench.rs              |   6 +-
 examples/custom_error.rs      |   8 +-
 examples/custom_error.svg     |   5 +-
 examples/custom_level.rs      |  12 ++-
 examples/custom_level.svg     |   3 +-
 examples/expected_type.rs     |   4 +-
 examples/footer.rs            |   6 +-
 examples/format.rs            |   4 +-
 examples/highlight_source.rs  |   6 +-
 examples/highlight_title.rs   |   6 +-
 examples/multislice.rs        |   4 +-
 src/level.rs                  | 128 +++++++++++++++++++++++
 src/lib.rs                    |   1 +
 src/renderer/mod.rs           |  36 +++----
 src/renderer/styled_buffer.rs |   6 +-
 src/snippet.rs                |  65 +-----------
 tests/fixtures/deserialize.rs |  35 +++++--
 tests/formatter.rs            | 192 +++++++++++++++++-----------------
 tests/rustc_tests.rs          |  88 ++++++++--------
 19 files changed, 352 insertions(+), 263 deletions(-)
 create mode 100644 src/level.rs

diff --git a/benches/bench.rs b/benches/bench.rs
index 01364af..d50cf43 100644
--- a/benches/bench.rs
+++ b/benches/bench.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
 
 #[divan::bench]
 fn simple() -> String {
@@ -24,7 +24,7 @@ fn simple() -> String {
             _ => continue,
         }
     }"#;
-    let message = Level::Error.message("mismatched types").id("E0308").group(
+    let message = Level::ERROR.message("mismatched types").id("E0308").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(51)
@@ -69,7 +69,7 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) {
             (input, span)
         })
         .bench_values(|(input, span)| {
-            let message = Level::Error.message("mismatched types").id("E0308").group(
+            let message = Level::ERROR.message("mismatched types").id("E0308").group(
                 Group::new().element(
                     Snippet::source(&input)
                         .fold(true)
diff --git a/examples/custom_error.rs b/examples/custom_error.rs
index 3ba234d..4050d40 100644
--- a/examples/custom_error.rs
+++ b/examples/custom_error.rs
@@ -1,5 +1,5 @@
 use annotate_snippets::renderer::OutputTheme;
-use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
 
 fn main() {
     let source = r#"//@ compile-flags: -Ztreat-err-as-bug
@@ -15,8 +15,10 @@ fn main() {
 pub static C: u32 = 0 - 1;
 //~^ ERROR could not evaluate static initializer
 "#;
-    let message = Level::None
-        .message("error: internal compiler error[E0080]: could not evaluate static initializer")
+    let message = Level::ERROR
+        .text(Some("error: internal compiler error"))
+        .message("could not evaluate static initializer")
+        .id("E0080")
         .group(
             Group::new().element(
                 Snippet::source(source)
diff --git a/examples/custom_error.svg b/examples/custom_error.svg
index 5cb0c72..af3611a 100644
--- a/examples/custom_error.svg
+++ b/examples/custom_error.svg
@@ -3,6 +3,7 @@
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
+    .fg-bright-red { fill: #FF5555 }
     .container {
       padding: 0 10px;
       line-height: 18px;
@@ -18,7 +19,7 @@
   <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
 
   <text xml:space="preserve" class="container fg">
-    <tspan x="10px" y="28px"><tspan class="bold">error: internal compiler error[E0080]: could not evaluate static initializer</tspan>
+    <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error: internal compiler error[E0080]</tspan><tspan class="bold">: could not evaluate static initializer</tspan>
 </tspan>
     <tspan x="10px" y="46px"><tspan>  </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/err.rs:11:21</tspan>
 </tspan>
@@ -26,7 +27,7 @@
 </tspan>
     <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> pub static C: u32 = 0 - 1;</tspan>
 </tspan>
-    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan>                    ━━━━━ attempt to compute `0_u32 - 1_u32`, which would overflow</tspan>
+    <tspan x="10px" y="100px"><tspan>   </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan>                    </tspan><tspan class="fg-bright-red bold">━━━━━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">attempt to compute `0_u32 - 1_u32`, which would overflow</tspan>
 </tspan>
     <tspan x="10px" y="118px">
 </tspan>
diff --git a/examples/custom_level.rs b/examples/custom_level.rs
index c19cdc4..804f774 100644
--- a/examples/custom_level.rs
+++ b/examples/custom_level.rs
@@ -1,5 +1,5 @@
 use annotate_snippets::renderer::OutputTheme;
-use annotate_snippets::{AnnotationKind, Group, Level, Patch, Renderer, Snippet};
+use annotate_snippets::{level::Level, AnnotationKind, Group, Patch, Renderer, Snippet};
 
 fn main() {
     let source = r#"// Regression test for issue #114529
@@ -29,7 +29,7 @@ fn main() {
     }
 }
 "#;
-    let message = Level::Error
+    let message = Level::ERROR
         .message("`break` with value from a `while` loop")
         .id("E0571")
         .group(
@@ -52,9 +52,11 @@ fn main() {
         )
         .group(
             Group::new()
-                .element(Level::None.title(
-                    "suggestion: use `break` on its own without a value inside this `while` loop",
-                ))
+                .element(
+                    Level::HELP
+                        .text(Some("suggestion"))
+                        .title("use `break` on its own without a value inside this `while` loop"),
+                )
                 .element(
                     Snippet::source(source)
                         .line_start(1)
diff --git a/examples/custom_level.svg b/examples/custom_level.svg
index 1f31e65..eebff28 100644
--- a/examples/custom_level.svg
+++ b/examples/custom_level.svg
@@ -3,6 +3,7 @@
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
     .fg-bright-blue { fill: #5555FF }
+    .fg-bright-cyan { fill: #55FFFF }
     .fg-bright-green { fill: #55FF55 }
     .fg-bright-red { fill: #FF5555 }
     .container {
@@ -40,7 +41,7 @@
 </tspan>
     <tspan x="10px" y="190px"><tspan>   </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan class="bold">suggestion: use `break` on its own without a value inside this `while` loop</tspan>
+    <tspan x="10px" y="208px"><tspan class="fg-bright-cyan bold">suggestion</tspan><tspan class="bold">: use `break` on its own without a value inside this `while` loop</tspan>
 </tspan>
     <tspan x="10px" y="226px"><tspan>   </tspan><tspan class="fg-bright-blue bold">╭╴</tspan>
 </tspan>
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index f61999d..9a51dce 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
 
 fn main() {
     let source = r#"                annotations: vec![SourceAnnotation {
@@ -6,7 +6,7 @@ fn main() {
                     ,
                 range: <22, 25>,"#;
     let message =
-        Level::Error.message("expected type, found `22`").group(
+        Level::ERROR.message("expected type, found `22`").group(
             Group::new().element(
                 Snippet::source(source)
                     .line_start(26)
diff --git a/examples/footer.rs b/examples/footer.rs
index 29b27c1..d61e84b 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,7 +1,7 @@
-use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
 
 fn main() {
-    let message = Level::Error
+    let message = Level::ERROR
         .message("mismatched types")
         .id("E0308")
         .group(
@@ -14,7 +14,7 @@ fn main() {
                     )),
             ),
         )
-        .group(Group::new().element(Level::Note.title(
+        .group(Group::new().element(Level::NOTE.title(
             "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
         )));
 
diff --git a/examples/format.rs b/examples/format.rs
index df6f927..5324413 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
 
 fn main() {
     let source = r#") -> Option<String> {
@@ -23,7 +23,7 @@ fn main() {
             _ => continue,
         }
     }"#;
-    let message = Level::Error.message("mismatched types").id("E0308").group(
+    let message = Level::ERROR.message("mismatched types").id("E0308").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(51)
diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs
index 2bb4ec2..9962542 100644
--- a/examples/highlight_source.rs
+++ b/examples/highlight_source.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
 
 fn main() {
     let source = r#"//@ compile-flags: -Z teach
@@ -9,7 +9,7 @@ const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010
 //~| ERROR cannot call non-const method
 fn main() {}
 "#;
-    let message = Level::Error
+    let message = Level::ERROR
         .message("allocations are not allowed in constants")
         .id("E0010")
         .group(
@@ -26,7 +26,7 @@ fn main() {}
                         ),
                 )
                 .element(
-                    Level::Note.title("The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created."),
+                    Level::NOTE.title("The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created."),
                 ),
         );
 
diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs
index bb09049..e2da54f 100644
--- a/examples/highlight_title.rs
+++ b/examples/highlight_title.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
 use anstyle::Effects;
 
 fn main() {
@@ -43,7 +43,7 @@ fn main() {
         magenta.render_reset()
     );
 
-    let message = Level::Error.message("mismatched types").id("E0308").group(
+    let message = Level::ERROR.message("mismatched types").id("E0308").group(
         Group::new()
             .element(
                 Snippet::source(source)
@@ -60,7 +60,7 @@ fn main() {
                             .label("arguments to this function are incorrect"),
                     ),
             )
-            .element(Level::Note.title(&title)),
+            .element(Level::NOTE.title(&title)),
     );
 
     let renderer = Renderer::styled().anonymized_line_numbers(true);
diff --git a/examples/multislice.rs b/examples/multislice.rs
index d1ad72a..ddd938e 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,7 +1,7 @@
-use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet};
+use annotate_snippets::{level::Level, Annotation, Group, Renderer, Snippet};
 
 fn main() {
-    let message = Level::Error.message("mismatched types").group(
+    let message = Level::ERROR.message("mismatched types").group(
         Group::new()
             .element(
                 Snippet::<Annotation<'_>>::source("Foo")
diff --git a/src/level.rs b/src/level.rs
new file mode 100644
index 0000000..7f92024
--- /dev/null
+++ b/src/level.rs
@@ -0,0 +1,128 @@
+use crate::renderer::stylesheet::Stylesheet;
+use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT};
+use crate::{Element, Group, Message, Title};
+use anstyle::Style;
+
+pub const ERROR: Level<'_> = Level {
+    name: None,
+    level: LevelInner::Error,
+};
+
+pub const WARNING: Level<'_> = Level {
+    name: None,
+    level: LevelInner::Warning,
+};
+
+pub const INFO: Level<'_> = Level {
+    name: None,
+    level: LevelInner::Info,
+};
+
+pub const NOTE: Level<'_> = Level {
+    name: None,
+    level: LevelInner::Note,
+};
+
+pub const HELP: Level<'_> = Level {
+    name: None,
+    level: LevelInner::Help,
+};
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Level<'a> {
+    pub(crate) name: Option<Option<&'a str>>,
+    pub(crate) level: LevelInner,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum Level2<'a> {
+    Builtin(LevelInner),
+    Custom {
+        name: Option<&'a str>,
+        level: LevelInner,
+    },
+    None,
+}
+
+impl<'a> Level<'a> {
+    pub const ERROR: Level<'a> = ERROR;
+    pub const WARNING: Level<'a> = WARNING;
+    pub const INFO: Level<'a> = INFO;
+    pub const NOTE: Level<'a> = NOTE;
+    pub const HELP: Level<'a> = HELP;
+
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
+    pub fn text(self, text: Option<&'a str>) -> Level<'a> {
+        Level {
+            name: Some(text),
+            level: self.level,
+        }
+    }
+}
+
+impl<'a> Level<'a> {
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
+    pub fn message(self, title: &'a str) -> Message<'a> {
+        Message {
+            id: None,
+            groups: vec![Group::new().element(Element::Title(Title {
+                level: self,
+                title,
+                primary: true,
+            }))],
+        }
+    }
+
+    /// Text passed to this function is allowed to be pre-styled, as such all
+    /// text is considered "trusted input" and has no normalizations applied to
+    /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be
+    /// used to normalize untrusted text before it is passed to this function.
+    pub fn title(self, title: &'a str) -> Title<'a> {
+        Title {
+            level: self,
+            title,
+            primary: false,
+        }
+    }
+
+    pub(crate) fn as_str(&self) -> &'a str {
+        match (self.name, self.level) {
+            (Some(Some(name)), _) => name,
+            (Some(None), _) => "",
+            (None, LevelInner::Error) => ERROR_TXT,
+            (None, LevelInner::Warning) => WARNING_TXT,
+            (None, LevelInner::Info) => INFO_TXT,
+            (None, LevelInner::Note) => NOTE_TXT,
+            (None, LevelInner::Help) => HELP_TXT,
+        }
+    }
+
+    pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style {
+        self.level.style(stylesheet)
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum LevelInner {
+    Error,
+    Warning,
+    Info,
+    Note,
+    Help,
+}
+
+impl LevelInner {
+    pub(crate) fn style(self, stylesheet: &Stylesheet) -> Style {
+        match self {
+            LevelInner::Error => stylesheet.error,
+            LevelInner::Warning => stylesheet.warning,
+            LevelInner::Info => stylesheet.info,
+            LevelInner::Note => stylesheet.note,
+            LevelInner::Help => stylesheet.help,
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index e6c49c6..eee2b6f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -42,6 +42,7 @@
 #![warn(clippy::print_stdout)]
 #![warn(missing_debug_implementations)]
 
+pub mod level;
 pub mod renderer;
 mod snippet;
 
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 7d4fd0f..8efcff8 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -5,6 +5,7 @@
 //! # Example
 //! ```
 //! use annotate_snippets::*;
+//! use annotate_snippets::level::Level;
 //!
 //! let source = r#"
 //! use baz::zed::bar;
@@ -17,7 +18,7 @@
 //!     bar();
 //! }
 //! "#;
-//! Level::Error
+//! Level::ERROR
 //!     .message("unresolved import `baz::zed`")
 //!     .id("E0432")
 //!     .group(
@@ -40,13 +41,12 @@ pub(crate) mod source_map;
 mod styled_buffer;
 pub(crate) mod stylesheet;
 
+use crate::level::{Level, LevelInner};
 use crate::renderer::source_map::{
     AnnotatedLineInfo, LineInfo, Loc, SourceMap, SubstitutionHighlight,
 };
 use crate::renderer::styled_buffer::StyledBuffer;
-use crate::{
-    Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Patch, Snippet, Title,
-};
+use crate::{Annotation, AnnotationKind, Element, Group, Message, Origin, Patch, Snippet, Title};
 pub use anstyle::*;
 use margin::Margin;
 use std::borrow::Cow;
@@ -207,7 +207,7 @@ impl Renderer {
         };
         let title = message.groups.remove(0).elements.remove(0);
         let level = if let Element::Title(title) = &title {
-            title.level
+            title.level.clone()
         } else {
             panic!("Expected a title as the first element of the message")
         };
@@ -345,7 +345,7 @@ impl Renderer {
                             );
 
                             if g == 0 && group_len > 1 {
-                                if matches!(peek, Some(Element::Title(level)) if level.level != Level::None)
+                                if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None))
                                 {
                                     self.draw_col_separator_no_space(
                                         buffer,
@@ -394,7 +394,7 @@ impl Renderer {
                 if g == 0
                     && (matches!(section, Element::Origin(_))
                         || (matches!(section, Element::Title(_)) && i == 0)
-                        || matches!(section, Element::Title(level) if level.level == Level::None))
+                        || matches!(section, Element::Title(level) if level.level.name == Some(None)))
                 {
                     if peek.is_none() && group_len > 1 {
                         self.draw_col_separator_end(
@@ -402,7 +402,7 @@ impl Renderer {
                             buffer.num_lines(),
                             max_line_num_len + 1,
                         );
-                    } else if matches!(peek, Some(Element::Title(level)) if level.level != Level::None)
+                    } else if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None))
                     {
                         self.draw_col_separator_no_space(
                             buffer,
@@ -445,7 +445,7 @@ impl Renderer {
                 buffer.prepend(line_offset, " ", ElementStyle::NoStyle);
             }
 
-            if title.level != Level::None {
+            if title.level.name != Some(None) {
                 self.draw_note_separator(buffer, line_offset, max_line_num_len + 1, is_cont);
                 buffer.append(
                     line_offset,
@@ -476,18 +476,18 @@ impl Renderer {
         } else {
             let mut label_width = 0;
 
-            if title.level != Level::None {
+            if title.level.name != Some(None) {
                 buffer.append(
                     line_offset,
                     title.level.as_str(),
-                    ElementStyle::Level(title.level),
+                    ElementStyle::Level(title.level.level),
                 );
             }
             label_width += title.level.as_str().len();
             if let Some(id) = id {
-                buffer.append(line_offset, "[", ElementStyle::Level(title.level));
-                buffer.append(line_offset, id, ElementStyle::Level(title.level));
-                buffer.append(line_offset, "]", ElementStyle::Level(title.level));
+                buffer.append(line_offset, "[", ElementStyle::Level(title.level.level));
+                buffer.append(line_offset, id, ElementStyle::Level(title.level.level));
+                buffer.append(line_offset, "]", ElementStyle::Level(title.level.level));
                 label_width += 2 + id.len();
             }
             let header_style = if is_secondary {
@@ -495,7 +495,7 @@ impl Renderer {
             } else {
                 ElementStyle::MainHeaderMsg
             };
-            if title.level != Level::None {
+            if title.level.name != Some(None) {
                 buffer.append(line_offset, ": ", header_style);
                 label_width += 2;
             }
@@ -647,7 +647,7 @@ impl Renderer {
                 buffer_msg_line_offset + 1,
                 max_line_num_len + 1,
             );
-            let title = Level::Note.title(label);
+            let title = Level::NOTE.title(label);
             self.render_title(buffer, &title, None, max_line_num_len, true, None, false);
         }
     }
@@ -2654,13 +2654,13 @@ pub(crate) enum ElementStyle {
     LabelPrimary,
     LabelSecondary,
     NoStyle,
-    Level(Level),
+    Level(LevelInner),
     Addition,
     Removal,
 }
 
 impl ElementStyle {
-    fn color_spec(&self, level: Level, stylesheet: &Stylesheet) -> Style {
+    fn color_spec(&self, level: &Level<'_>, stylesheet: &Stylesheet) -> Style {
         match self {
             ElementStyle::Addition => stylesheet.addition,
             ElementStyle::Removal => stylesheet.removal,
diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs
index 8a4cc67..7114683 100644
--- a/src/renderer/styled_buffer.rs
+++ b/src/renderer/styled_buffer.rs
@@ -2,9 +2,9 @@
 //!
 //! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs
 
+use crate::level::Level;
 use crate::renderer::stylesheet::Stylesheet;
 use crate::renderer::ElementStyle;
-use crate::Level;
 
 use std::fmt;
 use std::fmt::Write;
@@ -41,14 +41,14 @@ impl StyledBuffer {
 
     pub(crate) fn render(
         &self,
-        level: Level,
+        level: Level<'_>,
         stylesheet: &Stylesheet,
     ) -> Result<String, fmt::Error> {
         let mut str = String::new();
         for (i, line) in self.lines.iter().enumerate() {
             let mut current_style = stylesheet.none;
             for StyledChar { ch, style } in line {
-                let ch_style = style.color_spec(level, stylesheet);
+                let ch_style = style.color_spec(&level, stylesheet);
                 if ch_style != current_style {
                     if !line.is_empty() {
                         write!(str, "{}", current_style.render_reset())?;
diff --git a/src/snippet.rs b/src/snippet.rs
index e8fa18c..fe1239d 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -1,8 +1,7 @@
 //! Structures used as an input for the library.
 
+use crate::level::Level;
 use crate::renderer::source_map::SourceMap;
-use crate::renderer::stylesheet::Stylesheet;
-use anstyle::Style;
 use std::ops::Range;
 
 pub(crate) const ERROR_TXT: &str = "error";
@@ -143,7 +142,7 @@ pub struct ColumnSeparator;
 
 #[derive(Debug)]
 pub struct Title<'a> {
-    pub(crate) level: Level,
+    pub(crate) level: Level<'a>,
     pub(crate) title: &'a str,
     pub(crate) primary: bool,
 }
@@ -155,66 +154,6 @@ impl Title<'_> {
     }
 }
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum Level {
-    Error,
-    Warning,
-    Info,
-    Note,
-    Help,
-    None,
-}
-
-impl Level {
-    /// Text passed to this function is considered "untrusted input", as such
-    /// all text is passed through a normalization function. Pre-styled text is
-    /// not allowed to be passed to this function.
-    pub fn message(self, title: &str) -> Message<'_> {
-        Message {
-            id: None,
-            groups: vec![Group::new().element(Element::Title(Title {
-                level: self,
-                title,
-                primary: true,
-            }))],
-        }
-    }
-
-    /// Text passed to this function is allowed to be pre-styled, as such all
-    /// text is considered "trusted input" and has no normalizations applied to
-    /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be
-    /// used to normalize untrusted text before it is passed to this function.
-    pub fn title(self, title: &str) -> Title<'_> {
-        Title {
-            level: self,
-            title,
-            primary: false,
-        }
-    }
-
-    pub(crate) fn as_str(self) -> &'static str {
-        match self {
-            Level::Error => ERROR_TXT,
-            Level::Warning => WARNING_TXT,
-            Level::Info => INFO_TXT,
-            Level::Note => NOTE_TXT,
-            Level::Help => HELP_TXT,
-            Level::None => "",
-        }
-    }
-
-    pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style {
-        match self {
-            Level::Error => stylesheet.error,
-            Level::Warning => stylesheet.warning,
-            Level::Info => stylesheet.info,
-            Level::Note => stylesheet.note,
-            Level::Help => stylesheet.help,
-            Level::None => stylesheet.none,
-        }
-    }
-}
-
 #[derive(Debug)]
 pub struct Snippet<'a, T> {
     pub(crate) origin: Option<&'a str>,
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 065c395..8f45b36 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -3,7 +3,7 @@ use std::ops::Range;
 
 use annotate_snippets::renderer::DEFAULT_TERM_WIDTH;
 use annotate_snippets::{
-    Annotation, AnnotationKind, Element, Group, Level, Message, Patch, Renderer, Snippet,
+    level::Level, Annotation, AnnotationKind, Element, Group, Message, Patch, Renderer, Snippet,
 };
 
 #[derive(Deserialize)]
@@ -15,8 +15,7 @@ pub(crate) struct Fixture {
 
 #[derive(Deserialize)]
 pub struct MessageDef {
-    #[serde(with = "LevelDef")]
-    pub level: Level,
+    pub level: LevelDef,
     pub title: String,
     #[serde(default)]
     pub id: Option<String>,
@@ -32,13 +31,15 @@ impl<'a> From<&'a MessageDef> for Message<'a> {
             id,
             sections,
         } = val;
-        let mut message = level.message(title);
+        let mut message = Level::from(level).message(title);
         if let Some(id) = id {
             message = message.id(id);
         }
 
         message = message.group(Group::new().elements(sections.iter().map(|s| match s {
-            ElementDef::Title(title) => Element::Title(title.level.title(&title.title)),
+            ElementDef::Title(title) => {
+                Element::Title(Level::from(&title.level).title(&title.title))
+            }
             ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)),
             ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)),
         })));
@@ -57,7 +58,9 @@ pub enum ElementDef {
 impl<'a> From<&'a ElementDef> for Element<'a> {
     fn from(val: &'a ElementDef) -> Self {
         match val {
-            ElementDef::Title(title) => Element::Title(title.level.title(&title.title)),
+            ElementDef::Title(title) => {
+                Element::Title(Level::from(&title.level).title(&title.title))
+            }
             ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)),
             ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)),
         }
@@ -67,8 +70,7 @@ impl<'a> From<&'a ElementDef> for Element<'a> {
 #[derive(Deserialize)]
 pub struct TitleDef {
     pub title: String,
-    #[serde(with = "LevelDef")]
-    pub level: Level,
+    pub level: LevelDef,
 }
 
 #[derive(Deserialize)]
@@ -164,9 +166,8 @@ impl<'a> From<&'a PatchDef> for Patch<'a> {
 }
 
 #[allow(dead_code)]
-#[derive(Deserialize)]
-#[serde(remote = "Level")]
-enum LevelDef {
+#[derive(Clone, Copy, Deserialize)]
+pub enum LevelDef {
     Error,
     Warning,
     Info,
@@ -174,6 +175,18 @@ enum LevelDef {
     Help,
 }
 
+impl<'a> From<&'a LevelDef> for Level<'a> {
+    fn from(val: &'a LevelDef) -> Self {
+        match val {
+            LevelDef::Error => Level::ERROR,
+            LevelDef::Warning => Level::WARNING,
+            LevelDef::Info => Level::INFO,
+            LevelDef::Note => Level::NOTE,
+            LevelDef::Help => Level::HELP,
+        }
+    }
+}
+
 #[derive(Default, Deserialize)]
 pub struct RendererDef {
     #[serde(default)]
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 5deb21a..3f0e7cd 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,11 +1,13 @@
-use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Patch, Renderer, Snippet};
+use annotate_snippets::{
+    level::Level, Annotation, AnnotationKind, Group, Patch, Renderer, Snippet,
+};
 
 use annotate_snippets::renderer::OutputTheme;
 use snapbox::{assert_data_eq, str};
 
 #[test]
 fn test_i_29() {
-    let snippets = Level::Error.message("oops").group(
+    let snippets = Level::ERROR.message("oops").group(
         Group::new().element(
             Snippet::source("First line\r\nSecond oops line")
                 .origin("<current file>")
@@ -27,7 +29,7 @@ error: oops
 
 #[test]
 fn test_point_to_double_width_characters() {
-    let snippets = Level::Error.message("").group(
+    let snippets = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source("こんにちは、世界")
                 .origin("<current file>")
@@ -49,7 +51,7 @@ error:
 
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
-    let snippets = Level::Error.message("").group(
+    let snippets = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source("おはよう\nございます")
                 .origin("<current file>")
@@ -73,7 +75,7 @@ error:
 
 #[test]
 fn test_point_to_double_width_characters_multiple() {
-    let snippets = Level::Error.message("").group(
+    let snippets = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source("お寿司\n食べたい🍣")
                 .origin("<current file>")
@@ -98,7 +100,7 @@ error:
 
 #[test]
 fn test_point_to_double_width_characters_mixed() {
-    let snippets = Level::Error.message("").group(
+    let snippets = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source("こんにちは、新しいWorld!")
                 .origin("<current file>")
@@ -120,7 +122,7 @@ error:
 
 #[test]
 fn test_format_title() {
-    let input = Level::Error.message("This is a title").id("E0001");
+    let input = Level::ERROR.message("This is a title").id("E0001");
 
     let expected = str![r#"error[E0001]: This is a title"#];
     let renderer = Renderer::plain();
@@ -130,7 +132,7 @@ fn test_format_title() {
 #[test]
 fn test_format_snippet_only() {
     let source = "This is line 1\nThis is line 2";
-    let input = Level::Error
+    let input = Level::ERROR
         .message("")
         .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(5402)));
 
@@ -148,7 +150,7 @@ error:
 fn test_format_snippets_continuation() {
     let src_0 = "This is slice 1";
     let src_1 = "This is slice 2";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new()
             .element(
                 Snippet::<Annotation<'_>>::source(src_0)
@@ -182,7 +184,7 @@ fn test_format_snippet_annotation_standalone() {
     let source = [line_1, line_2].join("\n");
     // In line 2
     let range = 22..24;
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(&source).line_start(5402).annotation(
                 AnnotationKind::Context
@@ -204,9 +206,9 @@ error:
 
 #[test]
 fn test_format_footer_title() {
-    let input = Level::Error
+    let input = Level::ERROR
         .message("")
-        .group(Group::new().element(Level::Error.title("This __is__ a title")));
+        .group(Group::new().element(Level::ERROR.title("This __is__ a title")));
     let expected = str![[r#"
 error: 
   |
@@ -221,7 +223,7 @@ error:
 fn test_i26() {
     let source = "short";
     let label = "label";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source).line_start(0).annotation(
                 AnnotationKind::Primary
@@ -237,7 +239,7 @@ fn test_i26() {
 #[test]
 fn test_source_content() {
     let source = "This is an example\nof content lines";
-    let input = Level::Error
+    let input = Level::ERROR
         .message("")
         .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56)));
     let expected = str![[r#"
@@ -253,7 +255,7 @@ error:
 #[test]
 fn test_source_annotation_standalone_singleline() {
     let source = "tests";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -273,7 +275,7 @@ error:
 #[test]
 fn test_source_annotation_standalone_multiline() {
     let source = "tests";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -296,7 +298,7 @@ error:
 
 #[test]
 fn test_only_source() {
-    let input = Level::Error
+    let input = Level::ERROR
         .message("")
         .group(Group::new().element(Snippet::<Annotation<'_>>::source("").origin("file.rs")));
     let expected = str![[r#"
@@ -311,7 +313,7 @@ error:
 #[test]
 fn test_anon_lines() {
     let source = "This is an example\nof content lines\n\nabc";
-    let input = Level::Error
+    let input = Level::ERROR
         .message("")
         .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56)));
     let expected = str![[r#"
@@ -328,7 +330,7 @@ LL | abc
 
 #[test]
 fn issue_130() {
-    let input = Level::Error.message("dummy").group(
+    let input = Level::ERROR.message("dummy").group(
         Group::new().element(
             Snippet::source("foo\nbar\nbaz")
                 .origin("file/path")
@@ -356,7 +358,7 @@ fn unterminated_string_multiline() {
 a\"
 // ...
 ";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -380,7 +382,7 @@ error:
 #[test]
 fn char_and_nl_annotate_char() {
     let source = "a\r\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -403,7 +405,7 @@ error:
 #[test]
 fn char_eol_annotate_char() {
     let source = "a\r\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -425,7 +427,7 @@ error:
 
 #[test]
 fn char_eol_annotate_char_double_width() {
-    let snippets = Level::Error.message("").group(
+    let snippets = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source("こん\r\nにちは\r\n世界")
                 .origin("<current file>")
@@ -450,7 +452,7 @@ error:
 #[test]
 fn annotate_eol() {
     let source = "a\r\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -473,7 +475,7 @@ error:
 #[test]
 fn annotate_eol2() {
     let source = "a\r\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -496,7 +498,7 @@ error:
 #[test]
 fn annotate_eol3() {
     let source = "a\r\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -519,7 +521,7 @@ error:
 #[test]
 fn annotate_eol4() {
     let source = "a\r\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -541,7 +543,7 @@ error:
 
 #[test]
 fn annotate_eol_double_width() {
-    let snippets = Level::Error.message("").group(
+    let snippets = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source("こん\r\nにちは\r\n世界")
                 .origin("<current file>")
@@ -566,7 +568,7 @@ error:
 #[test]
 fn multiline_eol_start() {
     let source = "a\r\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -590,7 +592,7 @@ error:
 #[test]
 fn multiline_eol_start2() {
     let source = "a\r\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -614,7 +616,7 @@ error:
 #[test]
 fn multiline_eol_start3() {
     let source = "a\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -637,7 +639,7 @@ error:
 
 #[test]
 fn multiline_eol_start_double_width() {
-    let snippets = Level::Error.message("").group(
+    let snippets = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source("こん\r\nにちは\r\n世界")
                 .origin("<current file>")
@@ -663,7 +665,7 @@ error:
 #[test]
 fn multiline_eol_start_eol_end() {
     let source = "a\nb\nc";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -688,7 +690,7 @@ error:
 #[test]
 fn multiline_eol_start_eol_end2() {
     let source = "a\r\nb\r\nc";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -713,7 +715,7 @@ error:
 #[test]
 fn multiline_eol_start_eol_end3() {
     let source = "a\r\nb\r\nc";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -738,7 +740,7 @@ error:
 #[test]
 fn multiline_eol_start_eof_end() {
     let source = "a\r\nb";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -762,7 +764,7 @@ error:
 #[test]
 fn multiline_eol_start_eof_end_double_width() {
     let source = "ん\r\nに";
-    let input = Level::Error.message("").group(
+    let input = Level::ERROR.message("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -786,7 +788,7 @@ error:
 #[test]
 fn two_single_line_same_line() {
     let source = r#"bar = { version = "0.1.0", optional = true }"#;
-    let input = Level::Error.message("unused optional dependency").group(
+    let input = Level::ERROR.message("unused optional dependency").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("Cargo.toml")
@@ -823,7 +825,7 @@ this is another line
 so is this
 bar = { version = "0.1.0", optional = true }
 "#;
-    let input = Level::Error.message("unused optional dependency").group(
+    let input = Level::ERROR.message("unused optional dependency").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(4)
@@ -862,7 +864,7 @@ this is another line
 so is this
 bar = { version = "0.1.0", optional = true }
 "#;
-    let input = Level::Error.message("unused optional dependency").group(
+    let input = Level::ERROR.message("unused optional dependency").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(4)
@@ -910,7 +912,7 @@ so is this
 bar = { version = "0.1.0", optional = true }
 this is another line
 "#;
-    let input = Level::Error.message("unused optional dependency").group(
+    let input = Level::ERROR.message("unused optional dependency").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(4)
@@ -961,7 +963,7 @@ error: unused optional dependency
 #[test]
 fn origin_correct_start_line() {
     let source = "aaa\nbbb\nccc\nddd\n";
-    let input = Level::Error.message("title").group(
+    let input = Level::ERROR.message("title").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("origin.txt")
@@ -987,7 +989,7 @@ error: title
 #[test]
 fn origin_correct_mid_line() {
     let source = "aaa\nbbb\nccc\nddd\n";
-    let input = Level::Error.message("title").group(
+    let input = Level::ERROR.message("title").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("origin.txt")
@@ -1017,7 +1019,7 @@ error: title
 #[test]
 fn two_suggestions_same_span() {
     let source = r#"    A.foo();"#;
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("expected value, found enum `A`")
         .id("E0423")
         .group(
@@ -1030,7 +1032,7 @@ fn two_suggestions_same_span() {
         .group(
             Group::new()
                 .element(
-                    Level::Help
+                    Level::HELP
                         .title("you might have meant to use one of the following enum variants"),
                 )
                 .element(
@@ -1085,7 +1087,7 @@ fn main() {
     banana::Chaenomeles.pick()
 }"#;
     let input_new =
-        Level::Error
+        Level::ERROR
             .message("no method named `pick` found for struct `Chaenomeles` in the current scope")
             .id("E0599")
             .group(
@@ -1107,7 +1109,7 @@ fn main() {
             )
             .group(
                 Group::new()
-                    .element(Level::Help.title(
+                    .element(Level::HELP.title(
                         "the following traits which provide `pick` are implemented but not in scope; perhaps you want to import one of them",
                     ))
                     .element(
@@ -1145,7 +1147,7 @@ LL + use banana::Peach;
 fn single_line_non_overlapping_suggestions() {
     let source = r#"    A.foo();"#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("expected value, found enum `A`")
         .id("E0423")
         .group(
@@ -1158,7 +1160,7 @@ fn single_line_non_overlapping_suggestions() {
         )
         .group(
             Group::new()
-                .element(Level::Help.title("make these changes and things will work"))
+                .element(Level::HELP.title("make these changes and things will work"))
                 .element(
                     Snippet::source(source)
                         .fold(true)
@@ -1187,7 +1189,7 @@ LL +     (A::Tuple()).bar();
 #[test]
 fn single_line_non_overlapping_suggestions2() {
     let source = r#"    ThisIsVeryLong.foo();"#;
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("Found `ThisIsVeryLong`")
         .id("E0423")
         .group(
@@ -1200,7 +1202,7 @@ fn single_line_non_overlapping_suggestions2() {
         )
         .group(
             Group::new()
-                .element(Level::Help.title("make these changes and things will work"))
+                .element(Level::HELP.title("make these changes and things will work"))
                 .element(
                     Snippet::source(source)
                         .fold(true)
@@ -1236,7 +1238,7 @@ fn multiple_replacements() {
     y();
 "#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("cannot borrow `*self` as mutable because it is also borrowed as immutable")
         .id("E0502")
         .group(
@@ -1269,7 +1271,7 @@ fn multiple_replacements() {
         .group(
             Group::new()
                 .element(
-                    Level::Help
+                    Level::HELP
                         .title("try explicitly pass `&Self` into the Closure as an argument"),
                 )
                 .element(
@@ -1320,7 +1322,7 @@ fn main() {
     test1();
 }"#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("cannot borrow `chars` as mutable more than once at a time")
         .id("E0499")
         .group(
@@ -1348,7 +1350,7 @@ fn main() {
         .group(
             Group::new()
                 .element(
-                    Level::Help
+                    Level::HELP
                         .title("if you want to call `next` on a iterator within the loop, consider using `while let`")
                 )
                 .element(
@@ -1404,7 +1406,7 @@ struct Foo {
 
 fn main() {}"#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("failed to resolve: use of undeclared crate or module `st`")
         .id("E0433")
         .group(
@@ -1418,7 +1420,7 @@ fn main() {}"#;
         )
         .group(
             Group::new()
-                .element(Level::Help.title("there is a crate or module with a similar name"))
+                .element(Level::HELP.title("there is a crate or module with a similar name"))
                 .element(
                     Snippet::source(source)
                         .fold(true)
@@ -1427,7 +1429,7 @@ fn main() {}"#;
         )
         .group(
             Group::new()
-                .element(Level::Help.title("consider importing this module"))
+                .element(Level::HELP.title("consider importing this module"))
                 .element(
                     Snippet::source(source)
                         .fold(true)
@@ -1436,7 +1438,7 @@ fn main() {}"#;
         )
         .group(
             Group::new()
-                .element(Level::Help.title("if you import `cell`, refer to it directly"))
+                .element(Level::HELP.title("if you import `cell`, refer to it directly"))
                 .element(
                     Snippet::source(source)
                         .fold(true)
@@ -1486,7 +1488,7 @@ where
 
 fn main() {}"#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("the size for values of type `T` cannot be known at compilation time")
         .id("E0277")
         .group(
@@ -1508,7 +1510,7 @@ fn main() {}"#;
         )
         .group(
             Group::new()
-                .element(Level::Help.title(
+                .element(Level::HELP.title(
                     "consider removing the `?Sized` bound to make the type parameter `Sized`",
                 ))
                 .element(
@@ -1555,7 +1557,7 @@ and where
 }
 
 fn main() {}"#;
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("the size for values of type `T` cannot be known at compilation time")
         .id("E0277")
         .group(Group::new().element(Snippet::source(source)
@@ -1573,7 +1575,7 @@ fn main() {}"#;
                     .label("this type parameter needs to be `Sized`"),
             )))
         .group(Group::new().element(
-            Level::Note
+            Level::NOTE
                 .title("required by an implicit `Sized` bound in `Wrapper`")
         ).element(
             Snippet::source(source)
@@ -1587,7 +1589,7 @@ fn main() {}"#;
                 )
         ))
         .group(Group::new().element(
-            Level::Help
+            Level::HELP
                 .title("you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`")
             )
             .element(
@@ -1608,7 +1610,7 @@ fn main() {}"#;
 
         ))
         .group(Group::new().element(
-            Level::Help
+            Level::HELP
                 .title("consider removing the `?Sized` bound to make the type parameter `Sized`")
         ).element(
             Snippet::source(source)
@@ -1660,12 +1662,12 @@ quack
 zappy
 "#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("the size for values of type `T` cannot be known at compilation time")
         .id("E0277")
         .group(
             Group::new()
-                .element(Level::Help.title(
+                .element(Level::HELP.title(
                     "consider removing the `?Sized` bound to make the type parameter `Sized`",
                 ))
                 .element(
@@ -1728,7 +1730,7 @@ fn main() {
 }
 "#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`")
         .id("E0271")
         .group(Group::new().element(Snippet::source(source)
@@ -1741,7 +1743,7 @@ fn main() {
                     .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"),
             )))
         .group(Group::new().element(
-            Level::Note.title("expected this to be `Foo`")
+            Level::NOTE.title("expected this to be `Foo`")
         ).element(
             Snippet::source(source)
                 .line_start(4)
@@ -1749,7 +1751,7 @@ fn main() {
                 .fold(true)
                 .annotation(AnnotationKind::Primary.span(89..90))
         ).element(
-            Level::Note
+            Level::NOTE
                 .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`")
                 ,
         ));
@@ -1816,7 +1818,7 @@ fn main() {
 }
 "#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`")
         .id("E0271")
         .group(Group::new().element(Snippet::source(source)
@@ -1829,7 +1831,7 @@ fn main() {
                     .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"),
             )))
         .group(Group::new().element(
-            Level::Note.title("expected this to be `Foo`")
+            Level::NOTE.title("expected this to be `Foo`")
         ).element(
             Snippet::source(source)
                 .line_start(4)
@@ -1837,10 +1839,10 @@ fn main() {
                 .fold(true)
                 .annotation(AnnotationKind::Primary.span(89..90))
         ).element(
-            Level::Note
+            Level::NOTE
                 .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`")
         ).element(
-            Level::Note.title("a second note"),
+            Level::NOTE.title("a second note"),
         ));
 
     let expected = str![[r#"
@@ -1969,7 +1971,7 @@ fn main() {
 }
 "#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("mismatched types")
         .id("E0308")
         .group(Group::new().element(
@@ -1988,13 +1990,13 @@ fn main() {
                         .label("expected due to this"),
                 )
         ).element(
-            Level::Note
+            Level::NOTE
                 .title("expected struct `Atype<Btype<..., i32>, i32>`\n     found enum `Result<Result<..., _>, _>`")
         ).element(
-            Level::Note
+            Level::NOTE
                 .title("the full name for the type has been written to '$TEST_BUILD_DIR/$FILE.long-type-hash.txt'")
         ).element(
-            Level::Note
+            Level::NOTE
                 .title("consider using `--verbose` to print the full type name to the console")
                 ,
         ));
@@ -2053,7 +2055,7 @@ fn main() {
 }
 "#;
 
-    let input_new = Level::Error
+    let input_new = Level::ERROR
         .message("mismatched types")
         .id("E0308")
         .group(Group::new().element(
@@ -2072,12 +2074,12 @@ fn main() {
                         .label("arguments to this function are incorrect"),
                 ),
         ).element(
-            Level::Note
+            Level::NOTE
                 .title("expected fn pointer `for<'a> fn(Box<(dyn Any + Send + 'a)>) -> Pin<_>`\n      found fn item `fn(Box<(dyn Any + Send + 'static)>) -> Pin<_> {wrapped_fn}`")
                 ,
         ))
         .group(Group::new().element(
-            Level::Note.title("function defined here"),
+            Level::NOTE.title("function defined here"),
         ).element(
             Snippet::source(source)
                 .line_start(7)
@@ -2122,7 +2124,7 @@ LL │ ┃ )>>) {}
 #[test]
 fn unicode_cut_handling() {
     let source = "version = \"0.1.0\"\n# Ensure that the spans from toml handle utf-8 correctly\nauthors = [\n    { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }\n]\n";
-    let input = Level::Error.message("title").group(
+    let input = Level::ERROR.message("title").group(
         Group::new().element(
             Snippet::source(source)
                 .fold(false)
@@ -2147,7 +2149,7 @@ error: title
 #[test]
 fn unicode_cut_handling2() {
     let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
-    let input = Level::Error
+    let input = Level::ERROR
         .message("expected item, found `?`")
         .group(
             Group::new().element(
@@ -2155,7 +2157,7 @@ fn unicode_cut_handling2() {
                     .fold(false)
                     .annotation(AnnotationKind::Primary.span(499..500).label("expected item"))
             ).element(
-                Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
+                Level::NOTE.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
             )
         );
 
@@ -2174,7 +2176,7 @@ error: expected item, found `?`
 #[test]
 fn unicode_cut_handling3() {
     let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
-    let input = Level::Error
+    let input = Level::ERROR
         .message("expected item, found `?`")
         .group(
             Group::new().element(
@@ -2182,7 +2184,7 @@ fn unicode_cut_handling3() {
                     .fold(false)
                     .annotation(AnnotationKind::Primary.span(251..254).label("expected item"))
             ).element(
-                Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
+                Level::NOTE.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
             )
         );
 
@@ -2201,7 +2203,7 @@ error: expected item, found `?`
 #[test]
 fn unicode_cut_handling4() {
     let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?";
-    let input = Level::Error
+    let input = Level::ERROR
         .message("expected item, found `?`")
         .group(
             Group::new().element(
@@ -2209,7 +2211,7 @@ fn unicode_cut_handling4() {
                     .fold(false)
                     .annotation(AnnotationKind::Primary.span(334..335).label("expected item"))
             ).element(
-                Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
+                Level::NOTE.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
             )
         );
 
@@ -2234,7 +2236,7 @@ fn main() {
 //~^ ERROR mismatched types
 }
 "##;
-    let input = Level::Error.message("mismatched types").id("E0308").group(
+    let input = Level::ERROR.message("mismatched types").id("E0308").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("$DIR/non-whitespace-trimming-unicode.rs")
@@ -2278,7 +2280,7 @@ fn main() {
     //[ascii]~^ ERROR cannot add `&str` to `&str`
 }
 "##;
-    let input = Level::Error
+    let input = Level::ERROR
         .message("cannot add `&str` to `&str`")
         .id("E0369")
         .group(
@@ -2296,13 +2298,13 @@ fn main() {
                         ),
                 )
                 .element(
-                    Level::Note
+                    Level::NOTE
                         .title("string concatenation requires an owned `String` on the left"),
                 ),
         )
         .group(
             Group::new()
-                .element(Level::Help.title("create an owned `String` from a string reference"))
+                .element(Level::HELP.title("create an owned `String` from a string reference"))
                 .element(
                     Snippet::source(source)
                         .origin("$DIR/non-1-width-unicode-multiline-label.rs")
@@ -2345,7 +2347,7 @@ fn foo() {
 }
 "##;
     let bin_source = "�|�\u{0002}!5�cc\u{0015}\u{0002}�Ӻi��WWj�ȥ�'�}�\u{0012}�J�ȉ��W�\u{001e}O�@����\u{001c}w�V���LO����\u{0014}[ \u{0003}_�'���SQ�~ذ��ų&��-\t��lN~��!@␌ _#���kQ��h�\u{001d}�:�\u{001c}\u{0007}�";
-    let input = Level::Error
+    let input = Level::ERROR
         .message("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8")
         .group(
             Group::new().element(
@@ -2357,14 +2359,14 @@ fn foo() {
         )
         .group(
             Group::new()
-                .element(Level::Note.title("byte `193` is not valid utf-8"))
+                .element(Level::NOTE.title("byte `193` is not valid utf-8"))
                 .element(
                     Snippet::source(bin_source)
                         .origin("$DIR/not-utf8.bin")
                         .fold(true)
                         .annotation(AnnotationKind::Primary.span(0..0)),
                 )
-                .element(Level::Note.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")),
+                .element(Level::NOTE.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")),
         );
 
     let expected = str![[r#"
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index 47a3399..7a795db 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -2,7 +2,7 @@
 //!
 //! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs
 
-use annotate_snippets::{AnnotationKind, Group, Level, Origin, Renderer, Snippet};
+use annotate_snippets::{level::Level, AnnotationKind, Group, Origin, Renderer, Snippet};
 
 use snapbox::{assert_data_eq, str};
 
@@ -12,7 +12,7 @@ fn ends_on_col0() {
 fn foo() {
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -42,7 +42,7 @@ fn foo() {
 
   }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -74,7 +74,7 @@ fn foo() {
   X2 Y2
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -117,7 +117,7 @@ fn foo() {
   Y1 X1
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -161,7 +161,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -205,7 +205,7 @@ fn foo() {
   X2 Y2 Z2
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -252,7 +252,7 @@ fn foo() {
   X2 Y2 Z2
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -300,7 +300,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -350,7 +350,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -394,7 +394,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -437,7 +437,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -470,7 +470,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -502,7 +502,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -537,7 +537,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -571,7 +571,7 @@ fn foo() {
   a  bc  d
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -605,7 +605,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -633,7 +633,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -662,7 +662,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -701,7 +701,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -732,7 +732,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -772,7 +772,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -832,7 +832,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::Error.message("foo").group(
+    let input = Level::ERROR.message("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -886,7 +886,7 @@ fn issue_91334() {
 
 fn f(){||yield(((){),
 "#;
-    let input = Level::Error
+    let input = Level::ERROR
         .message("this file contains an unclosed delimiter")
         .group(
             Group::new().element(
@@ -957,7 +957,7 @@ fn main() {
     }
 }
 "#;
-    let input = Level::Error
+    let input = Level::ERROR
         .message("`break` with value from a `while` loop")
         .id("E0571")
         .group(
@@ -981,7 +981,7 @@ fn main() {
         .group(
             Group::new()
                 .element(
-                    Level::Help
+                    Level::HELP
                         .title("use `break` on its own without a value inside this `while` loop"),
                 )
                 .element(
@@ -1167,7 +1167,7 @@ fn nsize() {
 }
 "#;
     let input =
-        Level::Error
+        Level::ERROR
             .message("`V0usize` cannot be safely transmuted into `[usize; 2]`")
             .id("E0277")
             .group(
@@ -1183,7 +1183,7 @@ fn nsize() {
             )
             .group(
                 Group::new()
-                    .element(Level::Note.title("required by a bound in `is_transmutable`"))
+                    .element(Level::NOTE.title("required by a bound in `is_transmutable`"))
                     .element(
                         Snippet::source(source)
                             .line_start(1)
@@ -1253,7 +1253,7 @@ fn main() {
     assert::is_maybe_transmutable::<&'static [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`
 }
 "#;
-    let input = Level::Error
+    let input = Level::ERROR
         .message("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`")
         .id("E027s7")
         .group(
@@ -1322,7 +1322,7 @@ fn g() {
 }
 fn main() {}
 "#;
-    let input = Level::Error
+    let input = Level::ERROR
         .message("expected function, found `{integer}`")
         .id("E0618")
         .group(
@@ -1413,7 +1413,7 @@ macro_rules! outer_macro {
 
 outer_macro!(FirstStruct, FirstAttrStruct);
 "#;
-    let input = Level::Warning
+    let input = Level::WARNING
         .message("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module")
         .group(
             Group::new()
@@ -1441,17 +1441,17 @@ outer_macro!(FirstStruct, FirstAttrStruct);
                         ),
                 )
                 .element(
-                    Level::Help
+                    Level::HELP
                         .title("remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`")
                 )
                 .element(
-                    Level::Note
+                    Level::NOTE
                         .title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute")
                 ),
         )
         .group(
             Group::new()
-                .element(Level::Note.title("the lint level is defined here"))
+                .element(Level::NOTE.title("the lint level is defined here"))
                 .element(
                     Snippet::source(source)
                         .line_start(1)
@@ -1546,7 +1546,7 @@ macro_rules! inline {
     () => ()
 }
 "#;
-    let input = Level::Error
+    let input = Level::ERROR
         .message("can't call method `pow` on ambiguous numeric type `{integer}`")
         .id("E0689")
         .group(
@@ -1560,7 +1560,7 @@ macro_rules! inline {
         )
         .group(
             Group::new()
-                .element(Level::Help.title("you must specify a type for this binding, like `i32`"))
+                .element(Level::HELP.title("you must specify a type for this binding, like `i32`"))
                 .element(
                     Snippet::source(aux_source)
                         .line_start(1)
@@ -1610,7 +1610,7 @@ fn courier_to_des_moines_and_points_west(data: &[u32]) -> String {
 fn main() {}
 "#;
 
-    let input = Level::Error
+    let input = Level::ERROR
         .message("type annotations needed")
         .id("E0282")
         .group(
@@ -1716,7 +1716,7 @@ fn nonempty<const N: usize>(arrayN_of_empty: [!; N]) {
 fn main() {}
 "##;
 
-    let input = Level::Error
+    let input = Level::ERROR
         .message(
             "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered"
         )
@@ -1736,7 +1736,7 @@ fn main() {}
         )
         .group(
             Group::new()
-                .element(Level::Note.title("`NonEmptyEnum5` defined here"))
+                .element(Level::NOTE.title("`NonEmptyEnum5` defined here"))
                 .element(
                     Snippet::source(source)
                         .line_start(1)
@@ -1749,13 +1749,13 @@ fn main() {}
                         .annotation(AnnotationKind::Context.span(878..880).label("not covered"))
                         .annotation(AnnotationKind::Context.span(890..892).label("not covered"))
                 )
-                .element(Level::Note.title("the matched value is of type `NonEmptyEnum5`"))
-                .element(Level::Note.title("match arms with guards don't count towards exhaustivity"))
+                .element(Level::NOTE.title("the matched value is of type `NonEmptyEnum5`"))
+                .element(Level::NOTE.title("match arms with guards don't count towards exhaustivity"))
         )
         .group(
             Group::new()
                 .element(
-                    Level::Help
+                    Level::HELP
                         .title("ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms")
                 )
                 .element(
@@ -1818,7 +1818,7 @@ fn main() {
     //~^ ERROR must be specified
 }
 "#;
-    let input = Level::Error
+    let input = Level::ERROR
         .message("the trait alias `EqAlias` is not dyn compatible")
         .id("E0038")
         .group(
@@ -1837,7 +1837,7 @@ fn main() {
         .group(
             Group::new()
                 .element(
-                    Level::Note
+                    Level::NOTE
                         .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>"))
                 .element(
                     Origin::new("$SRC_DIR/core/src/cmp.rs")

From a1a11a53235087853a98c9404627abf90a8d383d Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 16 Apr 2025 15:09:13 -0500
Subject: [PATCH 291/302] fix: Remove unused Level2

---
 src/level.rs | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/src/level.rs b/src/level.rs
index 7f92024..81aea8c 100644
--- a/src/level.rs
+++ b/src/level.rs
@@ -34,16 +34,6 @@ pub struct Level<'a> {
     pub(crate) level: LevelInner,
 }
 
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum Level2<'a> {
-    Builtin(LevelInner),
-    Custom {
-        name: Option<&'a str>,
-        level: LevelInner,
-    },
-    None,
-}
-
 impl<'a> Level<'a> {
     pub const ERROR: Level<'a> = ERROR;
     pub const WARNING: Level<'a> = WARNING;

From a792c392cd720e11a3a03e2fa2d1fa609979821c Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 16 Apr 2025 15:09:46 -0500
Subject: [PATCH 292/302] fix: Hide LevelInner

---
 src/level.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/level.rs b/src/level.rs
index 81aea8c..46eaef1 100644
--- a/src/level.rs
+++ b/src/level.rs
@@ -97,7 +97,7 @@ impl<'a> Level<'a> {
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum LevelInner {
+pub(crate) enum LevelInner {
     Error,
     Warning,
     Info,

From 1d802c3480aedbebff07ad950c51b86422635b42 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 16 Apr 2025 15:12:03 -0500
Subject: [PATCH 293/302] fix: Re-export Level at the top-level

---
 benches/bench.rs              | 2 +-
 examples/custom_error.rs      | 2 +-
 examples/custom_level.rs      | 2 +-
 examples/expected_type.rs     | 2 +-
 examples/footer.rs            | 2 +-
 examples/format.rs            | 2 +-
 examples/highlight_source.rs  | 2 +-
 examples/highlight_title.rs   | 2 +-
 examples/multislice.rs        | 2 +-
 src/lib.rs                    | 2 ++
 src/renderer/mod.rs           | 2 +-
 src/renderer/styled_buffer.rs | 2 +-
 src/snippet.rs                | 2 +-
 tests/fixtures/deserialize.rs | 2 +-
 tests/formatter.rs            | 4 +---
 tests/rustc_tests.rs          | 2 +-
 16 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/benches/bench.rs b/benches/bench.rs
index d50cf43..a6cdb37 100644
--- a/benches/bench.rs
+++ b/benches/bench.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 #[divan::bench]
 fn simple() -> String {
diff --git a/examples/custom_error.rs b/examples/custom_error.rs
index 4050d40..418d342 100644
--- a/examples/custom_error.rs
+++ b/examples/custom_error.rs
@@ -1,5 +1,5 @@
 use annotate_snippets::renderer::OutputTheme;
-use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 fn main() {
     let source = r#"//@ compile-flags: -Ztreat-err-as-bug
diff --git a/examples/custom_level.rs b/examples/custom_level.rs
index 804f774..87cad9a 100644
--- a/examples/custom_level.rs
+++ b/examples/custom_level.rs
@@ -1,5 +1,5 @@
 use annotate_snippets::renderer::OutputTheme;
-use annotate_snippets::{level::Level, AnnotationKind, Group, Patch, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Patch, Renderer, Snippet};
 
 fn main() {
     let source = r#"// Regression test for issue #114529
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 9a51dce..5c801c7 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 fn main() {
     let source = r#"                annotations: vec![SourceAnnotation {
diff --git a/examples/footer.rs b/examples/footer.rs
index d61e84b..95de15c 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 fn main() {
     let message = Level::ERROR
diff --git a/examples/format.rs b/examples/format.rs
index 5324413..eb68d82 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 fn main() {
     let source = r#") -> Option<String> {
diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs
index 9962542..165dd86 100644
--- a/examples/highlight_source.rs
+++ b/examples/highlight_source.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 fn main() {
     let source = r#"//@ compile-flags: -Z teach
diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs
index e2da54f..ea74612 100644
--- a/examples/highlight_title.rs
+++ b/examples/highlight_title.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 use anstyle::Effects;
 
 fn main() {
diff --git a/examples/multislice.rs b/examples/multislice.rs
index ddd938e..9be82e9 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{level::Level, Annotation, Group, Renderer, Snippet};
+use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet};
 
 fn main() {
     let message = Level::ERROR.message("mismatched types").group(
diff --git a/src/lib.rs b/src/lib.rs
index eee2b6f..0d23b29 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -53,6 +53,8 @@ pub fn normalize_untrusted_str(s: &str) -> String {
     renderer::normalize_whitespace(s)
 }
 
+#[doc(inline)]
+pub use level::Level;
 #[doc(inline)]
 pub use renderer::Renderer;
 pub use snippet::ColumnSeparator;
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 8efcff8..12fc5d9 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -5,7 +5,7 @@
 //! # Example
 //! ```
 //! use annotate_snippets::*;
-//! use annotate_snippets::level::Level;
+//! use annotate_snippets::Level;
 //!
 //! let source = r#"
 //! use baz::zed::bar;
diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs
index 7114683..c9b805a 100644
--- a/src/renderer/styled_buffer.rs
+++ b/src/renderer/styled_buffer.rs
@@ -2,9 +2,9 @@
 //!
 //! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs
 
-use crate::level::Level;
 use crate::renderer::stylesheet::Stylesheet;
 use crate::renderer::ElementStyle;
+use crate::Level;
 
 use std::fmt;
 use std::fmt::Write;
diff --git a/src/snippet.rs b/src/snippet.rs
index fe1239d..aa5f9ff 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -1,7 +1,7 @@
 //! Structures used as an input for the library.
 
-use crate::level::Level;
 use crate::renderer::source_map::SourceMap;
+use crate::Level;
 use std::ops::Range;
 
 pub(crate) const ERROR_TXT: &str = "error";
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 8f45b36..55374df 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -3,7 +3,7 @@ use std::ops::Range;
 
 use annotate_snippets::renderer::DEFAULT_TERM_WIDTH;
 use annotate_snippets::{
-    level::Level, Annotation, AnnotationKind, Element, Group, Message, Patch, Renderer, Snippet,
+    Annotation, AnnotationKind, Element, Group, Level, Message, Patch, Renderer, Snippet,
 };
 
 #[derive(Deserialize)]
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 3f0e7cd..22b517b 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,6 +1,4 @@
-use annotate_snippets::{
-    level::Level, Annotation, AnnotationKind, Group, Patch, Renderer, Snippet,
-};
+use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Patch, Renderer, Snippet};
 
 use annotate_snippets::renderer::OutputTheme;
 use snapbox::{assert_data_eq, str};
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index 7a795db..93860d0 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -2,7 +2,7 @@
 //!
 //! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs
 
-use annotate_snippets::{level::Level, AnnotationKind, Group, Origin, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Origin, Renderer, Snippet};
 
 use snapbox::{assert_data_eq, str};
 

From 4c113cca128ffe8e1d62625daddd58ed6181a7db Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 16 Apr 2025 15:23:22 -0500
Subject: [PATCH 294/302] fix!: Rename `Renderer::line_no` to
 `Renderer::line_num`

Fixes #175
---
 src/renderer/mod.rs        | 8 ++++----
 src/renderer/stylesheet.rs | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 12fc5d9..f421c2d 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -101,7 +101,7 @@ impl Renderer {
                 info: BRIGHT_BLUE.effects(Effects::BOLD),
                 note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD),
                 help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
-                line_no: BRIGHT_BLUE.effects(Effects::BOLD),
+                line_num: BRIGHT_BLUE.effects(Effects::BOLD),
                 emphasis: if USE_WINDOWS_COLORS {
                     AnsiColor::BrightWhite.on_default()
                 } else {
@@ -178,8 +178,8 @@ impl Renderer {
     }
 
     /// Set the output style for line numbers
-    pub const fn line_no(mut self, style: Style) -> Self {
-        self.stylesheet.line_no = style;
+    pub const fn line_num(mut self, style: Style) -> Self {
+        self.stylesheet.line_num = style;
         self
     }
 
@@ -2665,7 +2665,7 @@ impl ElementStyle {
             ElementStyle::Addition => stylesheet.addition,
             ElementStyle::Removal => stylesheet.removal,
             ElementStyle::LineAndColumn => stylesheet.none,
-            ElementStyle::LineNumber => stylesheet.line_no,
+            ElementStyle::LineNumber => stylesheet.line_num,
             ElementStyle::Quotation => stylesheet.none,
             ElementStyle::MainHeaderMsg => stylesheet.emphasis,
             ElementStyle::UnderlinePrimary | ElementStyle::LabelPrimary => level.style(stylesheet),
diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs
index 4aa21a5..075cad4 100644
--- a/src/renderer/stylesheet.rs
+++ b/src/renderer/stylesheet.rs
@@ -7,7 +7,7 @@ pub(crate) struct Stylesheet {
     pub(crate) info: Style,
     pub(crate) note: Style,
     pub(crate) help: Style,
-    pub(crate) line_no: Style,
+    pub(crate) line_num: Style,
     pub(crate) emphasis: Style,
     pub(crate) none: Style,
     pub(crate) context: Style,
@@ -29,7 +29,7 @@ impl Stylesheet {
             info: Style::new(),
             note: Style::new(),
             help: Style::new(),
-            line_no: Style::new(),
+            line_num: Style::new(),
             emphasis: Style::new(),
             none: Style::new(),
             context: Style::new(),

From f6d191440268c5309c5c70755f1eaf49d2e1db33 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 16 Apr 2025 15:36:57 -0500
Subject: [PATCH 295/302] fix: Clarify ColumnSeparator is Padding

---
 src/lib.rs          |  2 +-
 src/renderer/mod.rs |  4 ++--
 src/snippet.rs      | 12 ++++++------
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 0d23b29..021ed52 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -57,5 +57,5 @@ pub fn normalize_untrusted_str(s: &str) -> String {
 pub use level::Level;
 #[doc(inline)]
 pub use renderer::Renderer;
-pub use snippet::ColumnSeparator;
+pub use snippet::Padding;
 pub use snippet::*;
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index f421c2d..1e88bca 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -383,7 +383,7 @@ impl Renderer {
                         self.render_origin(buffer, max_line_num_len, origin);
                         last_was_suggestion = false;
                     }
-                    Element::ColumnSeparator(_) => {
+                    Element::Padding(_) => {
                         self.draw_col_separator_no_space(
                             buffer,
                             buffer.num_lines(),
@@ -430,7 +430,7 @@ impl Renderer {
 
         let (has_primary_spans, has_span_labels) =
             next_section.map_or((false, false), |s| match s {
-                Element::Title(_) | Element::ColumnSeparator(_) => (false, false),
+                Element::Title(_) | Element::Padding(_) => (false, false),
                 Element::Cause(cause) => (
                     cause.markers.iter().any(|m| m.kind.is_primary()),
                     cause.markers.iter().any(|m| m.label.is_some()),
diff --git a/src/snippet.rs b/src/snippet.rs
index aa5f9ff..bf22c5b 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -34,7 +34,7 @@ impl<'a> Message<'a> {
                 v.elements
                     .iter()
                     .map(|s| match s {
-                        Element::Title(_) | Element::Origin(_) | Element::ColumnSeparator(_) => 0,
+                        Element::Title(_) | Element::Origin(_) | Element::Padding(_) => 0,
                         Element::Cause(cause) => {
                             let end = cause
                                 .markers
@@ -104,7 +104,7 @@ pub enum Element<'a> {
     Cause(Snippet<'a, Annotation<'a>>),
     Suggestion(Snippet<'a, Patch<'a>>),
     Origin(Origin<'a>),
-    ColumnSeparator(ColumnSeparator),
+    Padding(Padding),
 }
 
 impl<'a> From<Title<'a>> for Element<'a> {
@@ -131,14 +131,14 @@ impl<'a> From<Origin<'a>> for Element<'a> {
     }
 }
 
-impl From<ColumnSeparator> for Element<'_> {
-    fn from(value: ColumnSeparator) -> Self {
-        Self::ColumnSeparator(value)
+impl From<Padding> for Element<'_> {
+    fn from(value: Padding) -> Self {
+        Self::Padding(value)
     }
 }
 
 #[derive(Debug)]
-pub struct ColumnSeparator;
+pub struct Padding;
 
 #[derive(Debug)]
 pub struct Title<'a> {

From d3b79b6466562c810d55b3d256de94ce5c352812 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 16 Apr 2025 15:59:54 -0500
Subject: [PATCH 296/302] fix!: Rename Level::title/Level::message to
 Level::header

This doesn't completely take care of  #118 because of #196
---
 benches/bench.rs                              |   4 +-
 examples/custom_error.rs                      |   2 +-
 examples/custom_level.rs                      |   2 +-
 examples/expected_type.rs                     |   2 +-
 examples/footer.rs                            |   2 +-
 examples/format.rs                            |   2 +-
 examples/highlight_source.rs                  |   2 +-
 examples/highlight_title.rs                   |   2 +-
 examples/multislice.rs                        |   2 +-
 src/level.rs                                  |   4 +-
 src/renderer/mod.rs                           |   2 +-
 tests/fixtures/color/ann_eof.toml             |   2 +-
 tests/fixtures/color/ann_insertion.toml       |   2 +-
 tests/fixtures/color/ann_multiline.toml       |   2 +-
 tests/fixtures/color/ann_multiline2.toml      |   2 +-
 tests/fixtures/color/ann_removed_nl.toml      |   2 +-
 .../color/ensure-emoji-highlight-width.toml   |   2 +-
 tests/fixtures/color/fold_ann_multiline.toml  |   2 +-
 .../fixtures/color/fold_bad_origin_line.toml  |   2 +-
 tests/fixtures/color/fold_leading.toml        |   2 +-
 tests/fixtures/color/fold_trailing.toml       |   2 +-
 tests/fixtures/color/issue_9.toml             |   2 +-
 .../fixtures/color/multiple_annotations.toml  |   2 +-
 tests/fixtures/color/simple.toml              |   2 +-
 tests/fixtures/color/strip_line.toml          |   2 +-
 tests/fixtures/color/strip_line_char.toml     |   2 +-
 tests/fixtures/color/strip_line_non_ws.toml   |   2 +-
 tests/fixtures/deserialize.rs                 |   6 +-
 tests/formatter.rs                            | 124 +++++++++---------
 tests/rustc_tests.rs                          |  64 ++++-----
 30 files changed, 126 insertions(+), 126 deletions(-)

diff --git a/benches/bench.rs b/benches/bench.rs
index a6cdb37..c3799fb 100644
--- a/benches/bench.rs
+++ b/benches/bench.rs
@@ -24,7 +24,7 @@ fn simple() -> String {
             _ => continue,
         }
     }"#;
-    let message = Level::ERROR.message("mismatched types").id("E0308").group(
+    let message = Level::ERROR.header("mismatched types").id("E0308").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(51)
@@ -69,7 +69,7 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) {
             (input, span)
         })
         .bench_values(|(input, span)| {
-            let message = Level::ERROR.message("mismatched types").id("E0308").group(
+            let message = Level::ERROR.header("mismatched types").id("E0308").group(
                 Group::new().element(
                     Snippet::source(&input)
                         .fold(true)
diff --git a/examples/custom_error.rs b/examples/custom_error.rs
index 418d342..b9e27b3 100644
--- a/examples/custom_error.rs
+++ b/examples/custom_error.rs
@@ -17,7 +17,7 @@ pub static C: u32 = 0 - 1;
 "#;
     let message = Level::ERROR
         .text(Some("error: internal compiler error"))
-        .message("could not evaluate static initializer")
+        .header("could not evaluate static initializer")
         .id("E0080")
         .group(
             Group::new().element(
diff --git a/examples/custom_level.rs b/examples/custom_level.rs
index 87cad9a..b2af361 100644
--- a/examples/custom_level.rs
+++ b/examples/custom_level.rs
@@ -30,7 +30,7 @@ fn main() {
 }
 "#;
     let message = Level::ERROR
-        .message("`break` with value from a `while` loop")
+        .header("`break` with value from a `while` loop")
         .id("E0571")
         .group(
             Group::new().element(
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index 5c801c7..02abdec 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -6,7 +6,7 @@ fn main() {
                     ,
                 range: <22, 25>,"#;
     let message =
-        Level::ERROR.message("expected type, found `22`").group(
+        Level::ERROR.header("expected type, found `22`").group(
             Group::new().element(
                 Snippet::source(source)
                     .line_start(26)
diff --git a/examples/footer.rs b/examples/footer.rs
index 95de15c..ca6f1dc 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -2,7 +2,7 @@ use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
 
 fn main() {
     let message = Level::ERROR
-        .message("mismatched types")
+        .header("mismatched types")
         .id("E0308")
         .group(
             Group::new().element(
diff --git a/examples/format.rs b/examples/format.rs
index eb68d82..4b688d4 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -23,7 +23,7 @@ fn main() {
             _ => continue,
         }
     }"#;
-    let message = Level::ERROR.message("mismatched types").id("E0308").group(
+    let message = Level::ERROR.header("mismatched types").id("E0308").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(51)
diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs
index 165dd86..92d8114 100644
--- a/examples/highlight_source.rs
+++ b/examples/highlight_source.rs
@@ -10,7 +10,7 @@ const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010
 fn main() {}
 "#;
     let message = Level::ERROR
-        .message("allocations are not allowed in constants")
+        .header("allocations are not allowed in constants")
         .id("E0010")
         .group(
             Group::new()
diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs
index ea74612..12c106a 100644
--- a/examples/highlight_title.rs
+++ b/examples/highlight_title.rs
@@ -43,7 +43,7 @@ fn main() {
         magenta.render_reset()
     );
 
-    let message = Level::ERROR.message("mismatched types").id("E0308").group(
+    let message = Level::ERROR.header("mismatched types").id("E0308").group(
         Group::new()
             .element(
                 Snippet::source(source)
diff --git a/examples/multislice.rs b/examples/multislice.rs
index 9be82e9..a7d340a 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,7 +1,7 @@
 use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet};
 
 fn main() {
-    let message = Level::ERROR.message("mismatched types").group(
+    let message = Level::ERROR.header("mismatched types").group(
         Group::new()
             .element(
                 Snippet::<Annotation<'_>>::source("Foo")
diff --git a/src/level.rs b/src/level.rs
index 46eaef1..3fd504d 100644
--- a/src/level.rs
+++ b/src/level.rs
@@ -56,12 +56,12 @@ impl<'a> Level<'a> {
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
-    pub fn message(self, title: &'a str) -> Message<'a> {
+    pub fn header(self, header: &'a str) -> Message<'a> {
         Message {
             id: None,
             groups: vec![Group::new().element(Element::Title(Title {
                 level: self,
-                title,
+                title: header,
                 primary: true,
             }))],
         }
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 1e88bca..e5572e5 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -19,7 +19,7 @@
 //! }
 //! "#;
 //! Level::ERROR
-//!     .message("unresolved import `baz::zed`")
+//!     .header("unresolved import `baz::zed`")
 //!     .id("E0432")
 //!     .group(
 //!         Group::new().element(
diff --git a/tests/fixtures/color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml
index ac129f3..ef711de 100644
--- a/tests/fixtures/color/ann_eof.toml
+++ b/tests/fixtures/color/ann_eof.toml
@@ -1,6 +1,6 @@
 [message]
 level = "Error"
-title = "expected `.`, `=`"
+header = "expected `.`, `=`"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml
index 13bc13c..30af1bf 100644
--- a/tests/fixtures/color/ann_insertion.toml
+++ b/tests/fixtures/color/ann_insertion.toml
@@ -1,6 +1,6 @@
 [message]
 level = "Error"
-title = "expected `.`, `=`"
+header = "expected `.`, `=`"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml
index 722c3e1..2a5f206 100644
--- a/tests/fixtures/color/ann_multiline.toml
+++ b/tests/fixtures/color/ann_multiline.toml
@@ -1,7 +1,7 @@
 [message]
 level = "Error"
 id = "E0027"
-title = "pattern does not mention fields `lineno`, `content`"
+header = "pattern does not mention fields `lineno`, `content`"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml
index 329beb4..854b38a 100644
--- a/tests/fixtures/color/ann_multiline2.toml
+++ b/tests/fixtures/color/ann_multiline2.toml
@@ -1,7 +1,7 @@
 [message]
 level = "Error"
 id = "E####"
-title = "spacing error found"
+header = "spacing error found"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml
index 8ed96bc..6ffeb7a 100644
--- a/tests/fixtures/color/ann_removed_nl.toml
+++ b/tests/fixtures/color/ann_removed_nl.toml
@@ -1,6 +1,6 @@
 [message]
 level = "Error"
-title = "expected `.`, `=`"
+header = "expected `.`, `=`"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml
index 8d7a14a..669959f 100644
--- a/tests/fixtures/color/ensure-emoji-highlight-width.toml
+++ b/tests/fixtures/color/ensure-emoji-highlight-width.toml
@@ -1,5 +1,5 @@
 [message]
-title = "invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)"
+header = "invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)"
 level = "Error"
 
 
diff --git a/tests/fixtures/color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml
index 745ef42..2cee27d 100644
--- a/tests/fixtures/color/fold_ann_multiline.toml
+++ b/tests/fixtures/color/fold_ann_multiline.toml
@@ -1,7 +1,7 @@
 [message]
 level = "Error"
 id = "E0308"
-title = "mismatched types"
+header = "mismatched types"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml
index 3b0d827..2fab2d6 100644
--- a/tests/fixtures/color/fold_bad_origin_line.toml
+++ b/tests/fixtures/color/fold_bad_origin_line.toml
@@ -1,6 +1,6 @@
 [message]
 level = "Error"
-title = ""
+header = ""
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml
index 564187b..0ef043c 100644
--- a/tests/fixtures/color/fold_leading.toml
+++ b/tests/fixtures/color/fold_leading.toml
@@ -1,7 +1,7 @@
 [message]
 level = "Error"
 id = "E0308"
-title = "invalid type: integer `20`, expected a bool"
+header = "invalid type: integer `20`, expected a bool"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml
index eea6365..91e4ab4 100644
--- a/tests/fixtures/color/fold_trailing.toml
+++ b/tests/fixtures/color/fold_trailing.toml
@@ -1,7 +1,7 @@
 [message]
 level = "Error"
 id = "E0308"
-title = "invalid type: integer `20`, expected a lints table"
+header = "invalid type: integer `20`, expected a lints table"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/issue_9.toml b/tests/fixtures/color/issue_9.toml
index 96ad2c0..f423915 100644
--- a/tests/fixtures/color/issue_9.toml
+++ b/tests/fixtures/color/issue_9.toml
@@ -1,6 +1,6 @@
 [message]
 level = "Error"
-title = "expected one of `.`, `;`, `?`, or an operator, found `for`"
+header = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml
index f4c90a4..367c53e 100644
--- a/tests/fixtures/color/multiple_annotations.toml
+++ b/tests/fixtures/color/multiple_annotations.toml
@@ -1,6 +1,6 @@
 [message]
 level = "Error"
-title = ""
+header = ""
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/simple.toml b/tests/fixtures/color/simple.toml
index b70f11c..d5a3647 100644
--- a/tests/fixtures/color/simple.toml
+++ b/tests/fixtures/color/simple.toml
@@ -1,6 +1,6 @@
 [message]
 level = "Error"
-title = "expected one of `.`, `;`, `?`, or an operator, found `for`"
+header = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/strip_line.toml b/tests/fixtures/color/strip_line.toml
index d7af686..18a7805 100644
--- a/tests/fixtures/color/strip_line.toml
+++ b/tests/fixtures/color/strip_line.toml
@@ -1,7 +1,7 @@
 [message]
 level = "Error"
 id = "E0308"
-title = "mismatched types"
+header = "mismatched types"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml
index 6585005..3174ced 100644
--- a/tests/fixtures/color/strip_line_char.toml
+++ b/tests/fixtures/color/strip_line_char.toml
@@ -1,7 +1,7 @@
 [message]
 level = "Error"
 id = "E0308"
-title = "mismatched types"
+header = "mismatched types"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml
index 1f085b5..b7844ec 100644
--- a/tests/fixtures/color/strip_line_non_ws.toml
+++ b/tests/fixtures/color/strip_line_non_ws.toml
@@ -1,7 +1,7 @@
 [message]
 level = "Error"
 id = "E0308"
-title = "mismatched types"
+header = "mismatched types"
 
 [[message.sections]]
 type = "Cause"
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index 55374df..2042966 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -16,7 +16,7 @@ pub(crate) struct Fixture {
 #[derive(Deserialize)]
 pub struct MessageDef {
     pub level: LevelDef,
-    pub title: String,
+    pub header: String,
     #[serde(default)]
     pub id: Option<String>,
     #[serde(default)]
@@ -27,11 +27,11 @@ impl<'a> From<&'a MessageDef> for Message<'a> {
     fn from(val: &'a MessageDef) -> Self {
         let MessageDef {
             level,
-            title,
+            header,
             id,
             sections,
         } = val;
-        let mut message = Level::from(level).message(title);
+        let mut message = Level::from(level).header(header);
         if let Some(id) = id {
             message = message.id(id);
         }
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 22b517b..75cf853 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -5,7 +5,7 @@ use snapbox::{assert_data_eq, str};
 
 #[test]
 fn test_i_29() {
-    let snippets = Level::ERROR.message("oops").group(
+    let snippets = Level::ERROR.header("oops").group(
         Group::new().element(
             Snippet::source("First line\r\nSecond oops line")
                 .origin("<current file>")
@@ -27,7 +27,7 @@ error: oops
 
 #[test]
 fn test_point_to_double_width_characters() {
-    let snippets = Level::ERROR.message("").group(
+    let snippets = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source("こんにちは、世界")
                 .origin("<current file>")
@@ -49,7 +49,7 @@ error:
 
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
-    let snippets = Level::ERROR.message("").group(
+    let snippets = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source("おはよう\nございます")
                 .origin("<current file>")
@@ -73,7 +73,7 @@ error:
 
 #[test]
 fn test_point_to_double_width_characters_multiple() {
-    let snippets = Level::ERROR.message("").group(
+    let snippets = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source("お寿司\n食べたい🍣")
                 .origin("<current file>")
@@ -98,7 +98,7 @@ error:
 
 #[test]
 fn test_point_to_double_width_characters_mixed() {
-    let snippets = Level::ERROR.message("").group(
+    let snippets = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source("こんにちは、新しいWorld!")
                 .origin("<current file>")
@@ -120,7 +120,7 @@ error:
 
 #[test]
 fn test_format_title() {
-    let input = Level::ERROR.message("This is a title").id("E0001");
+    let input = Level::ERROR.header("This is a title").id("E0001");
 
     let expected = str![r#"error[E0001]: This is a title"#];
     let renderer = Renderer::plain();
@@ -131,7 +131,7 @@ fn test_format_title() {
 fn test_format_snippet_only() {
     let source = "This is line 1\nThis is line 2";
     let input = Level::ERROR
-        .message("")
+        .header("")
         .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(5402)));
 
     let expected = str![[r#"
@@ -148,7 +148,7 @@ error:
 fn test_format_snippets_continuation() {
     let src_0 = "This is slice 1";
     let src_1 = "This is slice 2";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new()
             .element(
                 Snippet::<Annotation<'_>>::source(src_0)
@@ -182,7 +182,7 @@ fn test_format_snippet_annotation_standalone() {
     let source = [line_1, line_2].join("\n");
     // In line 2
     let range = 22..24;
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(&source).line_start(5402).annotation(
                 AnnotationKind::Context
@@ -205,7 +205,7 @@ error:
 #[test]
 fn test_format_footer_title() {
     let input = Level::ERROR
-        .message("")
+        .header("")
         .group(Group::new().element(Level::ERROR.title("This __is__ a title")));
     let expected = str![[r#"
 error: 
@@ -221,7 +221,7 @@ error:
 fn test_i26() {
     let source = "short";
     let label = "label";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source).line_start(0).annotation(
                 AnnotationKind::Primary
@@ -238,7 +238,7 @@ fn test_i26() {
 fn test_source_content() {
     let source = "This is an example\nof content lines";
     let input = Level::ERROR
-        .message("")
+        .header("")
         .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56)));
     let expected = str![[r#"
 error: 
@@ -253,7 +253,7 @@ error:
 #[test]
 fn test_source_annotation_standalone_singleline() {
     let source = "tests";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -273,7 +273,7 @@ error:
 #[test]
 fn test_source_annotation_standalone_multiline() {
     let source = "tests";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -297,7 +297,7 @@ error:
 #[test]
 fn test_only_source() {
     let input = Level::ERROR
-        .message("")
+        .header("")
         .group(Group::new().element(Snippet::<Annotation<'_>>::source("").origin("file.rs")));
     let expected = str![[r#"
 error: 
@@ -312,7 +312,7 @@ error:
 fn test_anon_lines() {
     let source = "This is an example\nof content lines\n\nabc";
     let input = Level::ERROR
-        .message("")
+        .header("")
         .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56)));
     let expected = str![[r#"
 error: 
@@ -328,7 +328,7 @@ LL | abc
 
 #[test]
 fn issue_130() {
-    let input = Level::ERROR.message("dummy").group(
+    let input = Level::ERROR.header("dummy").group(
         Group::new().element(
             Snippet::source("foo\nbar\nbaz")
                 .origin("file/path")
@@ -356,7 +356,7 @@ fn unterminated_string_multiline() {
 a\"
 // ...
 ";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -380,7 +380,7 @@ error:
 #[test]
 fn char_and_nl_annotate_char() {
     let source = "a\r\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -403,7 +403,7 @@ error:
 #[test]
 fn char_eol_annotate_char() {
     let source = "a\r\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -425,7 +425,7 @@ error:
 
 #[test]
 fn char_eol_annotate_char_double_width() {
-    let snippets = Level::ERROR.message("").group(
+    let snippets = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source("こん\r\nにちは\r\n世界")
                 .origin("<current file>")
@@ -450,7 +450,7 @@ error:
 #[test]
 fn annotate_eol() {
     let source = "a\r\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -473,7 +473,7 @@ error:
 #[test]
 fn annotate_eol2() {
     let source = "a\r\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -496,7 +496,7 @@ error:
 #[test]
 fn annotate_eol3() {
     let source = "a\r\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -519,7 +519,7 @@ error:
 #[test]
 fn annotate_eol4() {
     let source = "a\r\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -541,7 +541,7 @@ error:
 
 #[test]
 fn annotate_eol_double_width() {
-    let snippets = Level::ERROR.message("").group(
+    let snippets = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source("こん\r\nにちは\r\n世界")
                 .origin("<current file>")
@@ -566,7 +566,7 @@ error:
 #[test]
 fn multiline_eol_start() {
     let source = "a\r\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -590,7 +590,7 @@ error:
 #[test]
 fn multiline_eol_start2() {
     let source = "a\r\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -614,7 +614,7 @@ error:
 #[test]
 fn multiline_eol_start3() {
     let source = "a\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -637,7 +637,7 @@ error:
 
 #[test]
 fn multiline_eol_start_double_width() {
-    let snippets = Level::ERROR.message("").group(
+    let snippets = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source("こん\r\nにちは\r\n世界")
                 .origin("<current file>")
@@ -663,7 +663,7 @@ error:
 #[test]
 fn multiline_eol_start_eol_end() {
     let source = "a\nb\nc";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -688,7 +688,7 @@ error:
 #[test]
 fn multiline_eol_start_eol_end2() {
     let source = "a\r\nb\r\nc";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -713,7 +713,7 @@ error:
 #[test]
 fn multiline_eol_start_eol_end3() {
     let source = "a\r\nb\r\nc";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -738,7 +738,7 @@ error:
 #[test]
 fn multiline_eol_start_eof_end() {
     let source = "a\r\nb";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -762,7 +762,7 @@ error:
 #[test]
 fn multiline_eol_start_eof_end_double_width() {
     let source = "ん\r\nに";
-    let input = Level::ERROR.message("").group(
+    let input = Level::ERROR.header("").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("file/path")
@@ -786,7 +786,7 @@ error:
 #[test]
 fn two_single_line_same_line() {
     let source = r#"bar = { version = "0.1.0", optional = true }"#;
-    let input = Level::ERROR.message("unused optional dependency").group(
+    let input = Level::ERROR.header("unused optional dependency").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("Cargo.toml")
@@ -823,7 +823,7 @@ this is another line
 so is this
 bar = { version = "0.1.0", optional = true }
 "#;
-    let input = Level::ERROR.message("unused optional dependency").group(
+    let input = Level::ERROR.header("unused optional dependency").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(4)
@@ -862,7 +862,7 @@ this is another line
 so is this
 bar = { version = "0.1.0", optional = true }
 "#;
-    let input = Level::ERROR.message("unused optional dependency").group(
+    let input = Level::ERROR.header("unused optional dependency").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(4)
@@ -910,7 +910,7 @@ so is this
 bar = { version = "0.1.0", optional = true }
 this is another line
 "#;
-    let input = Level::ERROR.message("unused optional dependency").group(
+    let input = Level::ERROR.header("unused optional dependency").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(4)
@@ -961,7 +961,7 @@ error: unused optional dependency
 #[test]
 fn origin_correct_start_line() {
     let source = "aaa\nbbb\nccc\nddd\n";
-    let input = Level::ERROR.message("title").group(
+    let input = Level::ERROR.header("title").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("origin.txt")
@@ -987,7 +987,7 @@ error: title
 #[test]
 fn origin_correct_mid_line() {
     let source = "aaa\nbbb\nccc\nddd\n";
-    let input = Level::ERROR.message("title").group(
+    let input = Level::ERROR.header("title").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("origin.txt")
@@ -1018,7 +1018,7 @@ error: title
 fn two_suggestions_same_span() {
     let source = r#"    A.foo();"#;
     let input_new = Level::ERROR
-        .message("expected value, found enum `A`")
+        .header("expected value, found enum `A`")
         .id("E0423")
         .group(
             Group::new().element(
@@ -1086,7 +1086,7 @@ fn main() {
 }"#;
     let input_new =
         Level::ERROR
-            .message("no method named `pick` found for struct `Chaenomeles` in the current scope")
+            .header("no method named `pick` found for struct `Chaenomeles` in the current scope")
             .id("E0599")
             .group(
                 Group::new().element(
@@ -1146,7 +1146,7 @@ fn single_line_non_overlapping_suggestions() {
     let source = r#"    A.foo();"#;
 
     let input_new = Level::ERROR
-        .message("expected value, found enum `A`")
+        .header("expected value, found enum `A`")
         .id("E0423")
         .group(
             Group::new().element(
@@ -1188,7 +1188,7 @@ LL +     (A::Tuple()).bar();
 fn single_line_non_overlapping_suggestions2() {
     let source = r#"    ThisIsVeryLong.foo();"#;
     let input_new = Level::ERROR
-        .message("Found `ThisIsVeryLong`")
+        .header("Found `ThisIsVeryLong`")
         .id("E0423")
         .group(
             Group::new().element(
@@ -1237,7 +1237,7 @@ fn multiple_replacements() {
 "#;
 
     let input_new = Level::ERROR
-        .message("cannot borrow `*self` as mutable because it is also borrowed as immutable")
+        .header("cannot borrow `*self` as mutable because it is also borrowed as immutable")
         .id("E0502")
         .group(
             Group::new().element(
@@ -1321,7 +1321,7 @@ fn main() {
 }"#;
 
     let input_new = Level::ERROR
-        .message("cannot borrow `chars` as mutable more than once at a time")
+        .header("cannot borrow `chars` as mutable more than once at a time")
         .id("E0499")
         .group(
             Group::new().element(
@@ -1405,7 +1405,7 @@ struct Foo {
 fn main() {}"#;
 
     let input_new = Level::ERROR
-        .message("failed to resolve: use of undeclared crate or module `st`")
+        .header("failed to resolve: use of undeclared crate or module `st`")
         .id("E0433")
         .group(
             Group::new().element(
@@ -1487,7 +1487,7 @@ where
 fn main() {}"#;
 
     let input_new = Level::ERROR
-        .message("the size for values of type `T` cannot be known at compilation time")
+        .header("the size for values of type `T` cannot be known at compilation time")
         .id("E0277")
         .group(
             Group::new().element(
@@ -1556,7 +1556,7 @@ and where
 
 fn main() {}"#;
     let input_new = Level::ERROR
-        .message("the size for values of type `T` cannot be known at compilation time")
+        .header("the size for values of type `T` cannot be known at compilation time")
         .id("E0277")
         .group(Group::new().element(Snippet::source(source)
             .line_start(1)
@@ -1661,7 +1661,7 @@ zappy
 "#;
 
     let input_new = Level::ERROR
-        .message("the size for values of type `T` cannot be known at compilation time")
+        .header("the size for values of type `T` cannot be known at compilation time")
         .id("E0277")
         .group(
             Group::new()
@@ -1729,7 +1729,7 @@ fn main() {
 "#;
 
     let input_new = Level::ERROR
-        .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`")
+        .header("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`")
         .id("E0271")
         .group(Group::new().element(Snippet::source(source)
             .line_start(4)
@@ -1817,7 +1817,7 @@ fn main() {
 "#;
 
     let input_new = Level::ERROR
-        .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`")
+        .header("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`")
         .id("E0271")
         .group(Group::new().element(Snippet::source(source)
             .line_start(4)
@@ -1970,7 +1970,7 @@ fn main() {
 "#;
 
     let input_new = Level::ERROR
-        .message("mismatched types")
+        .header("mismatched types")
         .id("E0308")
         .group(Group::new().element(
             Snippet::source(source)
@@ -2054,7 +2054,7 @@ fn main() {
 "#;
 
     let input_new = Level::ERROR
-        .message("mismatched types")
+        .header("mismatched types")
         .id("E0308")
         .group(Group::new().element(
             Snippet::source(source)
@@ -2122,7 +2122,7 @@ LL │ ┃ )>>) {}
 #[test]
 fn unicode_cut_handling() {
     let source = "version = \"0.1.0\"\n# Ensure that the spans from toml handle utf-8 correctly\nauthors = [\n    { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }\n]\n";
-    let input = Level::ERROR.message("title").group(
+    let input = Level::ERROR.header("title").group(
         Group::new().element(
             Snippet::source(source)
                 .fold(false)
@@ -2148,7 +2148,7 @@ error: title
 fn unicode_cut_handling2() {
     let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
     let input = Level::ERROR
-        .message("expected item, found `?`")
+        .header("expected item, found `?`")
         .group(
             Group::new().element(
                 Snippet::source(source)
@@ -2175,7 +2175,7 @@ error: expected item, found `?`
 fn unicode_cut_handling3() {
     let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
     let input = Level::ERROR
-        .message("expected item, found `?`")
+        .header("expected item, found `?`")
         .group(
             Group::new().element(
                 Snippet::source(source)
@@ -2202,7 +2202,7 @@ error: expected item, found `?`
 fn unicode_cut_handling4() {
     let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?";
     let input = Level::ERROR
-        .message("expected item, found `?`")
+        .header("expected item, found `?`")
         .group(
             Group::new().element(
                 Snippet::source(source)
@@ -2234,7 +2234,7 @@ fn main() {
 //~^ ERROR mismatched types
 }
 "##;
-    let input = Level::ERROR.message("mismatched types").id("E0308").group(
+    let input = Level::ERROR.header("mismatched types").id("E0308").group(
         Group::new().element(
             Snippet::source(source)
                 .origin("$DIR/non-whitespace-trimming-unicode.rs")
@@ -2279,7 +2279,7 @@ fn main() {
 }
 "##;
     let input = Level::ERROR
-        .message("cannot add `&str` to `&str`")
+        .header("cannot add `&str` to `&str`")
         .id("E0369")
         .group(
             Group::new()
@@ -2346,7 +2346,7 @@ fn foo() {
 "##;
     let bin_source = "�|�\u{0002}!5�cc\u{0015}\u{0002}�Ӻi��WWj�ȥ�'�}�\u{0012}�J�ȉ��W�\u{001e}O�@����\u{001c}w�V���LO����\u{0014}[ \u{0003}_�'���SQ�~ذ��ų&��-\t��lN~��!@␌ _#���kQ��h�\u{001d}�:�\u{001c}\u{0007}�";
     let input = Level::ERROR
-        .message("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8")
+        .header("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8")
         .group(
             Group::new().element(
                 Snippet::source(source)
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index 93860d0..6998629 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -12,7 +12,7 @@ fn ends_on_col0() {
 fn foo() {
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -42,7 +42,7 @@ fn foo() {
 
   }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -74,7 +74,7 @@ fn foo() {
   X2 Y2
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -117,7 +117,7 @@ fn foo() {
   Y1 X1
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -161,7 +161,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -205,7 +205,7 @@ fn foo() {
   X2 Y2 Z2
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -252,7 +252,7 @@ fn foo() {
   X2 Y2 Z2
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -300,7 +300,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -350,7 +350,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -394,7 +394,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -437,7 +437,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -470,7 +470,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -502,7 +502,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -537,7 +537,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -571,7 +571,7 @@ fn foo() {
   a  bc  d
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -605,7 +605,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -633,7 +633,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -662,7 +662,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -701,7 +701,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -732,7 +732,7 @@ fn foo() {
   a { b { c } d }
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -772,7 +772,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -832,7 +832,7 @@ fn foo() {
   X3 Y3 Z3
 }
 "#;
-    let input = Level::ERROR.message("foo").group(
+    let input = Level::ERROR.header("foo").group(
         Group::new().element(
             Snippet::source(source)
                 .line_start(1)
@@ -887,7 +887,7 @@ fn issue_91334() {
 fn f(){||yield(((){),
 "#;
     let input = Level::ERROR
-        .message("this file contains an unclosed delimiter")
+        .header("this file contains an unclosed delimiter")
         .group(
             Group::new().element(
                 Snippet::source(source)
@@ -958,7 +958,7 @@ fn main() {
 }
 "#;
     let input = Level::ERROR
-        .message("`break` with value from a `while` loop")
+        .header("`break` with value from a `while` loop")
         .id("E0571")
         .group(
             Group::new().element(
@@ -1168,7 +1168,7 @@ fn nsize() {
 "#;
     let input =
         Level::ERROR
-            .message("`V0usize` cannot be safely transmuted into `[usize; 2]`")
+            .header("`V0usize` cannot be safely transmuted into `[usize; 2]`")
             .id("E0277")
             .group(
                 Group::new().element(
@@ -1254,7 +1254,7 @@ fn main() {
 }
 "#;
     let input = Level::ERROR
-        .message("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`")
+        .header("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`")
         .id("E027s7")
         .group(
             Group::new().element(
@@ -1323,7 +1323,7 @@ fn g() {
 fn main() {}
 "#;
     let input = Level::ERROR
-        .message("expected function, found `{integer}`")
+        .header("expected function, found `{integer}`")
         .id("E0618")
         .group(
             Group::new().element(
@@ -1414,7 +1414,7 @@ macro_rules! outer_macro {
 outer_macro!(FirstStruct, FirstAttrStruct);
 "#;
     let input = Level::WARNING
-        .message("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module")
+        .header("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module")
         .group(
             Group::new()
                 .element(
@@ -1547,7 +1547,7 @@ macro_rules! inline {
 }
 "#;
     let input = Level::ERROR
-        .message("can't call method `pow` on ambiguous numeric type `{integer}`")
+        .header("can't call method `pow` on ambiguous numeric type `{integer}`")
         .id("E0689")
         .group(
             Group::new().element(
@@ -1611,7 +1611,7 @@ fn main() {}
 "#;
 
     let input = Level::ERROR
-        .message("type annotations needed")
+        .header("type annotations needed")
         .id("E0282")
         .group(
             Group::new().element(
@@ -1717,7 +1717,7 @@ fn main() {}
 "##;
 
     let input = Level::ERROR
-        .message(
+        .header(
             "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered"
         )
         .id("E0004")
@@ -1819,7 +1819,7 @@ fn main() {
 }
 "#;
     let input = Level::ERROR
-        .message("the trait alias `EqAlias` is not dyn compatible")
+        .header("the trait alias `EqAlias` is not dyn compatible")
         .id("E0038")
         .group(
             Group::new().element(

From a928524d03391efbc6952b09670737b84d6c8d8a Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 16 Apr 2025 16:11:38 -0500
Subject: [PATCH 297/302] refactor: Remove redundant re-export

---
 src/lib.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/lib.rs b/src/lib.rs
index 021ed52..76836d0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -57,5 +57,4 @@ pub fn normalize_untrusted_str(s: &str) -> String {
 pub use level::Level;
 #[doc(inline)]
 pub use renderer::Renderer;
-pub use snippet::Padding;
 pub use snippet::*;

From 21ca62eaf23d9fabd75ad787b9c8cde541d60213 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 16 Apr 2025 16:33:28 -0500
Subject: [PATCH 298/302] docs: Rename 'range' parameter to 'span'

---
 src/renderer/mod.rs        | 14 +++++++-------
 src/renderer/source_map.rs | 18 +++++++++---------
 src/snippet.rs             | 24 ++++++++++++------------
 3 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index e5572e5..be2abab 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1523,7 +1523,7 @@ impl Renderer {
             }
             if suggestion.origin != primary_origin {
                 if let Some(origin) = suggestion.origin {
-                    let (loc, _) = sm.span_to_locations(parts[0].range.clone());
+                    let (loc, _) = sm.span_to_locations(parts[0].span.clone());
                     // --> file.rs:line:col
                     //  |
                     let arrow = self.file_start();
@@ -1563,8 +1563,8 @@ impl Renderer {
                 row_num += 1;
             }
 
-            let file_lines = sm.span_to_lines(parts[0].range.clone());
-            let (line_start, line_end) = sm.span_to_locations(parts[0].range.clone());
+            let file_lines = sm.span_to_lines(parts[0].span.clone());
+            let (line_start, line_end) = sm.span_to_locations(parts[0].span.clone());
             let mut lines = complete.lines();
             if lines.clone().next().is_none() {
                 // Account for a suggestion to completely remove a line(s) with whitespace (#94192).
@@ -1697,8 +1697,8 @@ impl Renderer {
                 // already existing code, despite the colors and UI elements.
                 // We special case `#[derive(_)]\n` and other attribute suggestions, because those
                 // are the ones where context is most useful.
-                let file_lines = sm.span_to_lines(parts[0].range.end..parts[0].range.end);
-                let (lo, _) = sm.span_to_locations(parts[0].range.clone());
+                let file_lines = sm.span_to_lines(parts[0].span.end..parts[0].span.end);
+                let (lo, _) = sm.span_to_locations(parts[0].span.clone());
                 let line_num = lo.line;
                 if let Some(line) = sm.get_line(line_num) {
                     let line = normalize_whitespace(line);
@@ -1724,7 +1724,7 @@ impl Renderer {
                 show_code_change
             {
                 for part in parts {
-                    let (span_start, span_end) = sm.span_to_locations(part.range.clone());
+                    let (span_start, span_end) = sm.span_to_locations(part.span.clone());
                     let span_start_pos = span_start.display;
                     let span_end_pos = span_end.display;
 
@@ -1764,7 +1764,7 @@ impl Renderer {
                     let padding: usize = max_line_num_len + 3;
                     for p in underline_start..underline_end {
                         if matches!(show_code_change, DisplaySuggestion::Underline)
-                            && is_different(sm, part.replacement, part.range.clone())
+                            && is_different(sm, part.replacement, part.span.clone())
                         {
                             // If this is a replacement, underline with `~`, if this is an addition
                             // underline with `+`.
diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs
index 33fe189..d014bb0 100644
--- a/src/renderer/source_map.rs
+++ b/src/renderer/source_map.rs
@@ -129,8 +129,8 @@ impl<'a> SourceMap<'a> {
         let source_len = self.source.len();
         if let Some(bigger) = annotations.iter().find_map(|x| {
             // Allow highlighting one past the last character in the source.
-            if source_len + 1 < x.range.end {
-                Some(&x.range)
+            if source_len + 1 < x.span.end {
+                Some(&x.span)
             } else {
                 None
             }
@@ -150,13 +150,13 @@ impl<'a> SourceMap<'a> {
         let mut multiline_annotations = vec![];
 
         for Annotation {
-            range,
+            span,
             label,
             kind,
             highlight_source,
         } in annotations
         {
-            let (lo, mut hi) = self.span_to_locations(range.clone());
+            let (lo, mut hi) = self.span_to_locations(span.clone());
 
             // Watch out for "empty spans". If we get a span like 6..6, we
             // want to just display a `^` at 6, so convert that to
@@ -374,13 +374,13 @@ impl<'a> SourceMap<'a> {
         }
         // Assumption: all spans are in the same file, and all spans
         // are disjoint. Sort in ascending order.
-        patches.sort_by_key(|p| p.range.start);
+        patches.sort_by_key(|p| p.span.start);
 
         // Find the bounding span.
-        let Some(lo) = patches.iter().map(|p| p.range.start).min() else {
+        let Some(lo) = patches.iter().map(|p| p.span.start).min() else {
             return Vec::new();
         };
-        let Some(hi) = patches.iter().map(|p| p.range.end).max() else {
+        let Some(hi) = patches.iter().map(|p| p.span.end).max() else {
             return Vec::new();
         };
 
@@ -410,7 +410,7 @@ impl<'a> SourceMap<'a> {
             // suggestion and snippet to look as if we just suggested to add
             // `"b"`, which is typically much easier for the user to understand.
             part.trim_trivial_replacements(self);
-            let (cur_lo, cur_hi) = self.span_to_locations(part.range.clone());
+            let (cur_lo, cur_hi) = self.span_to_locations(part.span.clone());
             if prev_hi.line == cur_lo.line {
                 let mut count = push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo));
                 while count > 0 {
@@ -454,7 +454,7 @@ impl<'a> SourceMap<'a> {
                     _ => 1,
                 })
                 .sum();
-            if !is_different(self, part.replacement, part.range.clone()) {
+            if !is_different(self, part.replacement, part.span.clone()) {
                 // Account for cases where we are suggesting the same code that's already
                 // there. This shouldn't happen often, but in some cases for multipart
                 // suggestions it's much easier to handle it here than in the origin.
diff --git a/src/snippet.rs b/src/snippet.rs
index bf22c5b..f37a048 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -39,7 +39,7 @@ impl<'a> Message<'a> {
                             let end = cause
                                 .markers
                                 .iter()
-                                .map(|a| a.range.end)
+                                .map(|a| a.span.end)
                                 .max()
                                 .unwrap_or(cause.source.len())
                                 .min(cause.source.len());
@@ -50,7 +50,7 @@ impl<'a> Message<'a> {
                             let end = suggestion
                                 .markers
                                 .iter()
-                                .map(|a| a.range.end)
+                                .map(|a| a.span.end)
                                 .max()
                                 .unwrap_or(suggestion.source.len())
                                 .min(suggestion.source.len());
@@ -222,7 +222,7 @@ impl<'a> Snippet<'a, Patch<'a>> {
 
 #[derive(Clone, Debug)]
 pub struct Annotation<'a> {
-    pub(crate) range: Range<usize>,
+    pub(crate) span: Range<usize>,
     pub(crate) label: Option<&'a str>,
     pub(crate) kind: AnnotationKind,
     pub(crate) highlight_source: bool,
@@ -254,7 +254,7 @@ pub enum AnnotationKind {
 impl AnnotationKind {
     pub fn span<'a>(self, span: Range<usize>) -> Annotation<'a> {
         Annotation {
-            range: span,
+            span,
             label: None,
             kind: self,
             highlight_source: false,
@@ -268,7 +268,7 @@ impl AnnotationKind {
 
 #[derive(Clone, Debug)]
 pub struct Patch<'a> {
-    pub(crate) range: Range<usize>,
+    pub(crate) span: Range<usize>,
     pub(crate) replacement: &'a str,
 }
 
@@ -276,8 +276,8 @@ impl<'a> Patch<'a> {
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
-    pub fn new(range: Range<usize>, replacement: &'a str) -> Self {
-        Self { range, replacement }
+    pub fn new(span: Range<usize>, replacement: &'a str) -> Self {
+        Self { span, replacement }
     }
 
     pub(crate) fn is_addition(&self, sm: &SourceMap<'_>) -> bool {
@@ -299,7 +299,7 @@ impl<'a> Patch<'a> {
     pub(crate) fn is_destructive_replacement(&self, sm: &SourceMap<'_>) -> bool {
         self.is_replacement(sm)
             && !sm
-                .span_to_snippet(self.range.clone())
+                .span_to_snippet(self.span.clone())
                 // This should use `is_some_and` when our MSRV is >= 1.70
                 .map_or(false, |s| {
                     as_substr(s.trim(), self.replacement.trim()).is_some()
@@ -307,8 +307,8 @@ impl<'a> Patch<'a> {
     }
 
     fn replaces_meaningful_content(&self, sm: &SourceMap<'_>) -> bool {
-        sm.span_to_snippet(self.range.clone())
-            .map_or(!self.range.is_empty(), |snippet| !snippet.trim().is_empty())
+        sm.span_to_snippet(self.span.clone())
+            .map_or(!self.span.is_empty(), |snippet| !snippet.trim().is_empty())
     }
 
     /// Try to turn a replacement into an addition when the span that is being
@@ -317,12 +317,12 @@ impl<'a> Patch<'a> {
         if self.replacement.is_empty() {
             return;
         }
-        let Some(snippet) = sm.span_to_snippet(self.range.clone()) else {
+        let Some(snippet) = sm.span_to_snippet(self.span.clone()) else {
             return;
         };
 
         if let Some((prefix, substr, suffix)) = as_substr(snippet, self.replacement) {
-            self.range = self.range.start + prefix..self.range.end.saturating_sub(suffix);
+            self.span = self.span.start + prefix..self.span.end.saturating_sub(suffix);
             self.replacement = substr;
         }
     }

From 66369feec323d89b8209e69ecdf365321a01ac47 Mon Sep 17 00:00:00 2001
From: Ed Page <eopage@gmail.com>
Date: Wed, 16 Apr 2025 16:46:45 -0500
Subject: [PATCH 299/302] docs: Make an attempt

---
 src/level.rs   | 20 ++++++++++++++
 src/snippet.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 92 insertions(+)

diff --git a/src/level.rs b/src/level.rs
index 3fd504d..87d1a9f 100644
--- a/src/level.rs
+++ b/src/level.rs
@@ -1,33 +1,41 @@
+//! [`Level`] constants for easy importing
+
 use crate::renderer::stylesheet::Stylesheet;
 use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT};
 use crate::{Element, Group, Message, Title};
 use anstyle::Style;
 
+/// Default `error:` [`Level`]
 pub const ERROR: Level<'_> = Level {
     name: None,
     level: LevelInner::Error,
 };
 
+/// Default `warning:` [`Level`]
 pub const WARNING: Level<'_> = Level {
     name: None,
     level: LevelInner::Warning,
 };
 
+/// Default `info:` [`Level`]
 pub const INFO: Level<'_> = Level {
     name: None,
     level: LevelInner::Info,
 };
 
+/// Default `note:` [`Level`]
 pub const NOTE: Level<'_> = Level {
     name: None,
     level: LevelInner::Note,
 };
 
+/// Default `help:` [`Level`]
 pub const HELP: Level<'_> = Level {
     name: None,
     level: LevelInner::Help,
 };
 
+/// [`Message`] or [`Title`] severity level
 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Level<'a> {
     pub(crate) name: Option<Option<&'a str>>,
@@ -41,9 +49,13 @@ impl<'a> Level<'a> {
     pub const NOTE: Level<'a> = NOTE;
     pub const HELP: Level<'a> = HELP;
 
+    /// <div class="warning">
+    ///
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
+    ///
+    /// </div>
     pub fn text(self, text: Option<&'a str>) -> Level<'a> {
         Level {
             name: Some(text),
@@ -53,9 +65,13 @@ impl<'a> Level<'a> {
 }
 
 impl<'a> Level<'a> {
+    /// <div class="warning">
+    ///
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
+    ///
+    /// </div>
     pub fn header(self, header: &'a str) -> Message<'a> {
         Message {
             id: None,
@@ -67,10 +83,14 @@ impl<'a> Level<'a> {
         }
     }
 
+    /// <div class="warning">
+    ///
     /// Text passed to this function is allowed to be pre-styled, as such all
     /// text is considered "trusted input" and has no normalizations applied to
     /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be
     /// used to normalize untrusted text before it is passed to this function.
+    ///
+    /// </div>
     pub fn title(self, title: &'a str) -> Title<'a> {
         Title {
             level: self,
diff --git a/src/snippet.rs b/src/snippet.rs
index f37a048..03bfe3d 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -10,6 +10,7 @@ pub(crate) const INFO_TXT: &str = "info";
 pub(crate) const NOTE_TXT: &str = "note";
 pub(crate) const WARNING_TXT: &str = "warning";
 
+/// Top-level user message
 #[derive(Debug)]
 pub struct Message<'a> {
     pub(crate) id: Option<&'a str>, // for "correctness", could be sloppy and be on Title
@@ -17,11 +18,19 @@ pub struct Message<'a> {
 }
 
 impl<'a> Message<'a> {
+    /// <div class="warning">
+    ///
+    /// Text passed to this function is considered "untrusted input", as such
+    /// all text is passed through a normalization function. Pre-styled text is
+    /// not allowed to be passed to this function.
+    ///
+    /// </div>
     pub fn id(mut self, id: &'a str) -> Self {
         self.id = Some(id);
         self
     }
 
+    /// Add an [`Element`] container
     pub fn group(mut self, group: Group<'a>) -> Self {
         self.groups.push(group);
         self
@@ -66,6 +75,7 @@ impl<'a> Message<'a> {
     }
 }
 
+/// An [`Element`] container
 #[derive(Debug)]
 pub struct Group<'a> {
     pub(crate) elements: Vec<Element<'a>>,
@@ -97,6 +107,7 @@ impl<'a> Group<'a> {
     }
 }
 
+/// A section of content within a [`Group`]
 #[derive(Debug)]
 #[non_exhaustive]
 pub enum Element<'a> {
@@ -137,9 +148,13 @@ impl From<Padding> for Element<'_> {
     }
 }
 
+/// A whitespace [`Element`] in a [`Group`]
 #[derive(Debug)]
 pub struct Padding;
 
+/// A text [`Element`] in a [`Group`]
+///
+/// See [`Level::title`] to create this.
 #[derive(Debug)]
 pub struct Title<'a> {
     pub(crate) level: Level<'a>,
@@ -154,6 +169,7 @@ impl Title<'_> {
     }
 }
 
+/// A source view [`Element`] in a [`Group`]
 #[derive(Debug)]
 pub struct Snippet<'a, T> {
     pub(crate) origin: Option<&'a str>,
@@ -164,9 +180,15 @@ pub struct Snippet<'a, T> {
 }
 
 impl<'a, T: Clone> Snippet<'a, T> {
+    /// The source code to be rendered
+    ///
+    /// <div class="warning">
+    ///
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
+    ///
+    /// </div>
     pub fn source(source: &'a str) -> Self {
         Self {
             origin: None,
@@ -177,19 +199,28 @@ impl<'a, T: Clone> Snippet<'a, T> {
         }
     }
 
+    /// When manually [`fold`][Self::fold]ing,
+    /// the [`source`][Self::source]s line offset from the original start
     pub fn line_start(mut self, line_start: usize) -> Self {
         self.line_start = line_start;
         self
     }
 
+    /// The location of the [`source`][Self::source] (e.g. a path)
+    ///
+    /// <div class="warning">
+    ///
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
+    ///
+    /// </div>
     pub fn origin(mut self, origin: &'a str) -> Self {
         self.origin = Some(origin);
         self
     }
 
+    /// Hide lines without [`Annotation`]s
     pub fn fold(mut self, fold: bool) -> Self {
         self.fold = fold;
         self
@@ -197,11 +228,13 @@ impl<'a, T: Clone> Snippet<'a, T> {
 }
 
 impl<'a> Snippet<'a, Annotation<'a>> {
+    /// Highlight and describe a span of text within the [`source`][Self::source]
     pub fn annotation(mut self, annotation: Annotation<'a>) -> Snippet<'a, Annotation<'a>> {
         self.markers.push(annotation);
         self
     }
 
+    /// Highlight and describe spans of text within the [`source`][Self::source]
     pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self {
         self.markers.extend(annotation);
         self
@@ -209,17 +242,22 @@ impl<'a> Snippet<'a, Annotation<'a>> {
 }
 
 impl<'a> Snippet<'a, Patch<'a>> {
+    /// Suggest to the user an edit to the [`source`][Self::source]
     pub fn patch(mut self, patch: Patch<'a>) -> Snippet<'a, Patch<'a>> {
         self.markers.push(patch);
         self
     }
 
+    /// Suggest to the user edits to the [`source`][Self::source]
     pub fn patches(mut self, patches: impl IntoIterator<Item = Patch<'a>>) -> Self {
         self.markers.extend(patches);
         self
     }
 }
 
+/// Highlighted and describe a span of text within a [`Snippet`]
+///
+/// See [`AnnotationKind`] to create an annotation.
 #[derive(Clone, Debug)]
 pub struct Annotation<'a> {
     pub(crate) span: Range<usize>,
@@ -229,20 +267,30 @@ pub struct Annotation<'a> {
 }
 
 impl<'a> Annotation<'a> {
+    /// Describe the reason the span is highlighted
+    ///
+    /// This will be styled according to the [`AnnotationKind`]
+    ///
+    /// <div class="warning">
+    ///
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
+    ///
+    /// </div>
     pub fn label(mut self, label: &'a str) -> Self {
         self.label = Some(label);
         self
     }
 
+    /// Style the source according to the [`AnnotationKind`]
     pub fn highlight_source(mut self, highlight_source: bool) -> Self {
         self.highlight_source = highlight_source;
         self
     }
 }
 
+/// The category of the [`Annotation`]
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub enum AnnotationKind {
     /// Color to [`Message`]'s [`Level`]
@@ -266,6 +314,7 @@ impl AnnotationKind {
     }
 }
 
+/// Suggested edit to the [`Snippet`]
 #[derive(Clone, Debug)]
 pub struct Patch<'a> {
     pub(crate) span: Range<usize>,
@@ -273,9 +322,15 @@ pub struct Patch<'a> {
 }
 
 impl<'a> Patch<'a> {
+    /// Splice `replacement` into the [`Snippet`] at the `span`
+    ///
+    /// <div class="warning">
+    ///
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
+    ///
+    /// </div>
     pub fn new(span: Range<usize>, replacement: &'a str) -> Self {
         Self { span, replacement }
     }
@@ -328,6 +383,7 @@ impl<'a> Patch<'a> {
     }
 }
 
+/// The location of the [`Snippet`] (e.g. a path)
 #[derive(Clone, Debug)]
 pub struct Origin<'a> {
     pub(crate) origin: &'a str,
@@ -338,9 +394,13 @@ pub struct Origin<'a> {
 }
 
 impl<'a> Origin<'a> {
+    /// <div class="warning">
+    ///
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
+    ///
+    /// </div>
     pub fn new(origin: &'a str) -> Self {
         Self {
             origin,
@@ -351,11 +411,17 @@ impl<'a> Origin<'a> {
         }
     }
 
+    /// Set the default line number to display
+    ///
+    /// Otherwise this will be inferred from the primary [`Annotation`]
     pub fn line(mut self, line: usize) -> Self {
         self.line = Some(line);
         self
     }
 
+    /// Set the default column to display
+    ///
+    /// Otherwise this will be inferred from the primary [`Annotation`]
     pub fn char_column(mut self, char_column: usize) -> Self {
         self.char_column = Some(char_column);
         self
@@ -366,9 +432,15 @@ impl<'a> Origin<'a> {
         self
     }
 
+    /// Like [`Annotation::label`], but when there is no source
+    ///
+    /// <div class="warning">
+    ///
     /// Text passed to this function is considered "untrusted input", as such
     /// all text is passed through a normalization function. Pre-styled text is
     /// not allowed to be passed to this function.
+    ///
+    /// </div>
     pub fn label(mut self, label: &'a str) -> Self {
         self.label = Some(label);
         self

From 17eb40e020e119d8f9bf20987050015d761d54c7 Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 26 Apr 2025 06:29:03 -0600
Subject: [PATCH 300/302] test(fixtures): Move to source code based color tests

---
 Cargo.lock                                    | 327 +-----------------
 Cargo.toml                                    |   6 -
 tests/color/ann_eof.rs                        |  18 +
 .../ann_eof.svg => color/ann_eof.term.svg}    |   0
 tests/color/ann_insertion.rs                  |  18 +
 .../ann_insertion.term.svg}                   |   0
 tests/color/ann_multiline.rs                  |  31 ++
 .../ann_multiline.term.svg}                   |   0
 tests/color/ann_multiline2.rs                 |  31 ++
 .../ann_multiline2.term.svg}                  |   0
 tests/color/ann_removed_nl.rs                 |  18 +
 .../ann_removed_nl.term.svg}                  |   0
 tests/color/ensure_emoji_highlight_width.rs   |  24 ++
 .../ensure_emoji_highlight_width.term.svg}    |   0
 tests/color/fold_ann_multiline.rs             |  50 +++
 .../fold_ann_multiline.term.svg}              |   0
 tests/color/fold_bad_origin_line.rs           |  24 ++
 .../fold_bad_origin_line.term.svg}            |   0
 tests/color/fold_leading.rs                   |  35 ++
 .../fold_leading.term.svg}                    |   0
 tests/color/fold_trailing.rs                  |  34 ++
 .../fold_trailing.term.svg}                   |   0
 tests/color/issue_9.rs                        |  31 ++
 .../issue_9.svg => color/issue_9.term.svg}    |   0
 tests/color/main.rs                           |  16 +
 tests/color/multiple_annotations.rs           |  42 +++
 .../multiple_annotations.term.svg}            |   0
 tests/color/simple.rs                         |  34 ++
 .../simple.svg => color/simple.term.svg}      |   0
 tests/color/strip_line.rs                     |  24 ++
 .../strip_line.term.svg}                      |   0
 tests/color/strip_line_char.rs                |  24 ++
 .../strip_line_char.term.svg}                 |   0
 tests/color/strip_line_non_ws.rs              |  30 ++
 .../strip_line_non_ws.term.svg}               |   0
 tests/fixtures/color/ann_eof.toml             |  15 -
 tests/fixtures/color/ann_insertion.toml       |  15 -
 tests/fixtures/color/ann_multiline.toml       |  21 --
 tests/fixtures/color/ann_multiline2.toml      |  21 --
 tests/fixtures/color/ann_removed_nl.toml      |  15 -
 .../color/ensure-emoji-highlight-width.toml   |  18 -
 tests/fixtures/color/fold_ann_multiline.toml  |  41 ---
 .../fixtures/color/fold_bad_origin_line.toml  |  20 --
 tests/fixtures/color/fold_leading.toml        |  29 --
 tests/fixtures/color/fold_trailing.toml       |  28 --
 tests/fixtures/color/issue_9.toml             |  34 --
 .../fixtures/color/multiple_annotations.toml  |  33 --
 tests/fixtures/color/simple.toml              |  23 --
 tests/fixtures/color/strip_line.toml          |  19 -
 tests/fixtures/color/strip_line_char.toml     |  19 -
 tests/fixtures/color/strip_line_non_ws.toml   |  27 --
 tests/fixtures/deserialize.rs                 | 217 ------------
 tests/fixtures/main.rs                        |  42 ---
 53 files changed, 489 insertions(+), 965 deletions(-)
 create mode 100644 tests/color/ann_eof.rs
 rename tests/{fixtures/color/ann_eof.svg => color/ann_eof.term.svg} (100%)
 create mode 100644 tests/color/ann_insertion.rs
 rename tests/{fixtures/color/ann_insertion.svg => color/ann_insertion.term.svg} (100%)
 create mode 100644 tests/color/ann_multiline.rs
 rename tests/{fixtures/color/ann_multiline.svg => color/ann_multiline.term.svg} (100%)
 create mode 100644 tests/color/ann_multiline2.rs
 rename tests/{fixtures/color/ann_multiline2.svg => color/ann_multiline2.term.svg} (100%)
 create mode 100644 tests/color/ann_removed_nl.rs
 rename tests/{fixtures/color/ann_removed_nl.svg => color/ann_removed_nl.term.svg} (100%)
 create mode 100644 tests/color/ensure_emoji_highlight_width.rs
 rename tests/{fixtures/color/ensure-emoji-highlight-width.svg => color/ensure_emoji_highlight_width.term.svg} (100%)
 create mode 100644 tests/color/fold_ann_multiline.rs
 rename tests/{fixtures/color/fold_ann_multiline.svg => color/fold_ann_multiline.term.svg} (100%)
 create mode 100644 tests/color/fold_bad_origin_line.rs
 rename tests/{fixtures/color/fold_bad_origin_line.svg => color/fold_bad_origin_line.term.svg} (100%)
 create mode 100644 tests/color/fold_leading.rs
 rename tests/{fixtures/color/fold_leading.svg => color/fold_leading.term.svg} (100%)
 create mode 100644 tests/color/fold_trailing.rs
 rename tests/{fixtures/color/fold_trailing.svg => color/fold_trailing.term.svg} (100%)
 create mode 100644 tests/color/issue_9.rs
 rename tests/{fixtures/color/issue_9.svg => color/issue_9.term.svg} (100%)
 create mode 100644 tests/color/main.rs
 create mode 100644 tests/color/multiple_annotations.rs
 rename tests/{fixtures/color/multiple_annotations.svg => color/multiple_annotations.term.svg} (100%)
 create mode 100644 tests/color/simple.rs
 rename tests/{fixtures/color/simple.svg => color/simple.term.svg} (100%)
 create mode 100644 tests/color/strip_line.rs
 rename tests/{fixtures/color/strip_line.svg => color/strip_line.term.svg} (100%)
 create mode 100644 tests/color/strip_line_char.rs
 rename tests/{fixtures/color/strip_line_char.svg => color/strip_line_char.term.svg} (100%)
 create mode 100644 tests/color/strip_line_non_ws.rs
 rename tests/{fixtures/color/strip_line_non_ws.svg => color/strip_line_non_ws.term.svg} (100%)
 delete mode 100644 tests/fixtures/color/ann_eof.toml
 delete mode 100644 tests/fixtures/color/ann_insertion.toml
 delete mode 100644 tests/fixtures/color/ann_multiline.toml
 delete mode 100644 tests/fixtures/color/ann_multiline2.toml
 delete mode 100644 tests/fixtures/color/ann_removed_nl.toml
 delete mode 100644 tests/fixtures/color/ensure-emoji-highlight-width.toml
 delete mode 100644 tests/fixtures/color/fold_ann_multiline.toml
 delete mode 100644 tests/fixtures/color/fold_bad_origin_line.toml
 delete mode 100644 tests/fixtures/color/fold_leading.toml
 delete mode 100644 tests/fixtures/color/fold_trailing.toml
 delete mode 100644 tests/fixtures/color/issue_9.toml
 delete mode 100644 tests/fixtures/color/multiple_annotations.toml
 delete mode 100644 tests/fixtures/color/simple.toml
 delete mode 100644 tests/fixtures/color/strip_line.toml
 delete mode 100644 tests/fixtures/color/strip_line_char.toml
 delete mode 100644 tests/fixtures/color/strip_line_non_ws.toml
 delete mode 100644 tests/fixtures/deserialize.rs
 delete mode 100644 tests/fixtures/main.rs

diff --git a/Cargo.lock b/Cargo.lock
index 8838e31..dd4fd20 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,21 +2,12 @@
 # It is not intended for manual editing.
 version = 3
 
-[[package]]
-name = "aho-corasick"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
-dependencies = [
- "memchr",
-]
-
 [[package]]
 name = "annotate-snippets"
 version = "0.11.5"
 dependencies = [
  "annotate-snippets",
- "anstream 0.6.18",
+ "anstream",
  "anstyle",
  "difference",
  "divan",
@@ -24,26 +15,9 @@ dependencies = [
  "memchr",
  "serde",
  "snapbox",
- "toml",
- "tryfn",
  "unicode-width 0.2.0",
 ]
 
-[[package]]
-name = "anstream"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
-dependencies = [
- "anstyle",
- "anstyle-parse",
- "anstyle-query",
- "anstyle-wincon 1.0.2",
- "colorchoice",
- "is-terminal",
- "utf8parse",
-]
-
 [[package]]
 name = "anstream"
 version = "0.6.18"
@@ -53,7 +27,7 @@ dependencies = [
  "anstyle",
  "anstyle-parse",
  "anstyle-query",
- "anstyle-wincon 3.0.6",
+ "anstyle-wincon",
  "colorchoice",
  "is_terminal_polyfill",
  "utf8parse",
@@ -98,23 +72,13 @@ version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa"
 dependencies = [
- "anstream 0.6.18",
+ "anstream",
  "anstyle",
  "anstyle-lossy",
  "html-escape",
  "unicode-width 0.1.13",
 ]
 
-[[package]]
-name = "anstyle-wincon"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
-dependencies = [
- "anstyle",
- "windows-sys 0.48.0",
-]
-
 [[package]]
 name = "anstyle-wincon"
 version = "3.0.6"
@@ -131,16 +95,6 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
-[[package]]
-name = "bstr"
-version = "1.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
-dependencies = [
- "memchr",
- "serde",
-]
-
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -154,8 +108,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487"
 dependencies = [
  "clap_builder",
- "clap_derive",
- "once_cell",
 ]
 
 [[package]]
@@ -164,25 +116,11 @@ version = "4.3.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e"
 dependencies = [
- "anstream 0.3.2",
  "anstyle",
  "clap_lex",
- "strsim",
  "terminal_size",
 ]
 
-[[package]]
-name = "clap_derive"
-version = "4.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "clap_lex"
 version = "0.5.0"
@@ -232,12 +170,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "equivalent"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
-
 [[package]]
 name = "errno"
 version = "0.3.9"
@@ -248,15 +180,6 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
-[[package]]
-name = "escape8259"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba4f4911e3666fcd7826997b4745c8224295a6f3072f1418c3067b97a67557ee"
-dependencies = [
- "rustversion",
-]
-
 [[package]]
 name = "escargot"
 version = "0.5.13"
@@ -275,31 +198,6 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
 
-[[package]]
-name = "globset"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
-dependencies = [
- "aho-corasick",
- "bstr",
- "log",
- "regex-automata",
- "regex-syntax",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
-
-[[package]]
-name = "heck"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
-
 [[package]]
 name = "hermit-abi"
 version = "0.3.9"
@@ -315,33 +213,6 @@ dependencies = [
  "utf8-width",
 ]
 
-[[package]]
-name = "ignore"
-version = "0.4.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
-dependencies = [
- "globset",
- "lazy_static",
- "log",
- "memchr",
- "regex",
- "same-file",
- "thread_local",
- "walkdir",
- "winapi-util",
-]
-
-[[package]]
-name = "indexmap"
-version = "2.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
 [[package]]
 name = "io-lifetimes"
 version = "1.0.11"
@@ -379,30 +250,12 @@ version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-
 [[package]]
 name = "libc"
 version = "0.2.155"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
 
-[[package]]
-name = "libtest-mimic"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58"
-dependencies = [
- "clap",
- "escape8259",
- "termcolor",
- "threadpool",
-]
-
 [[package]]
 name = "linux-raw-sys"
 version = "0.3.8"
@@ -427,16 +280,6 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
 
-[[package]]
-name = "num_cpus"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
 [[package]]
 name = "once_cell"
 version = "1.19.0"
@@ -471,41 +314,12 @@ dependencies = [
  "proc-macro2",
 ]
 
-[[package]]
-name = "regex"
-version = "1.10.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-automata",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-automata"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
 [[package]]
 name = "regex-lite"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
 
-[[package]]
-name = "regex-syntax"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
-
 [[package]]
 name = "rustix"
 version = "0.37.27"
@@ -520,27 +334,12 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
-[[package]]
-name = "rustversion"
-version = "1.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
-
 [[package]]
 name = "ryu"
 version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
 
-[[package]]
-name = "same-file"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
-dependencies = [
- "winapi-util",
-]
-
 [[package]]
 name = "serde"
 version = "1.0.219"
@@ -572,15 +371,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "serde_spanned"
-version = "0.6.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
-dependencies = [
- "serde",
-]
-
 [[package]]
 name = "similar"
 version = "2.5.0"
@@ -593,7 +383,7 @@ version = "0.6.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b"
 dependencies = [
- "anstream 0.6.18",
+ "anstream",
  "anstyle",
  "anstyle-svg",
  "escargot",
@@ -613,15 +403,9 @@ version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af"
 dependencies = [
- "anstream 0.6.18",
+ "anstream",
 ]
 
-[[package]]
-name = "strsim"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
-
 [[package]]
 name = "syn"
 version = "2.0.86"
@@ -633,15 +417,6 @@ dependencies = [
  "unicode-ident",
 ]
 
-[[package]]
-name = "termcolor"
-version = "1.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
-dependencies = [
- "winapi-util",
-]
-
 [[package]]
 name = "terminal_size"
 version = "0.2.6"
@@ -652,70 +427,6 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
-[[package]]
-name = "thread_local"
-version = "1.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
-dependencies = [
- "cfg-if",
- "once_cell",
-]
-
-[[package]]
-name = "threadpool"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
-dependencies = [
- "num_cpus",
-]
-
-[[package]]
-name = "toml"
-version = "0.8.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
-dependencies = [
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_edit",
-]
-
-[[package]]
-name = "toml_datetime"
-version = "0.6.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.22.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
-dependencies = [
- "indexmap",
- "serde",
- "serde_spanned",
- "toml_datetime",
- "winnow",
-]
-
-[[package]]
-name = "tryfn"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396"
-dependencies = [
- "ignore",
- "libtest-mimic",
- "snapbox",
-]
-
 [[package]]
 name = "unicode-ident"
 version = "1.0.12"
@@ -755,25 +466,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "walkdir"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
-dependencies = [
- "same-file",
- "winapi-util",
-]
-
-[[package]]
-name = "winapi-util"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
-dependencies = [
- "windows-sys 0.52.0",
-]
-
 [[package]]
 name = "windows-sys"
 version = "0.48.0"
@@ -921,12 +613,3 @@ name = "windows_x86_64_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-
-[[package]]
-name = "winnow"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
-dependencies = [
- "memchr",
-]
diff --git a/Cargo.toml b/Cargo.toml
index f30c64f..4a8adf0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -128,17 +128,11 @@ divan = "0.1.14"
 glob = "0.3.1"
 serde = { version = "1.0.199", features = ["derive"] }
 snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] }
-toml = "0.8.0"
-tryfn = "0.2.1"
 
 [[bench]]
 name = "bench"
 harness = false
 
-[[test]]
-name = "fixtures"
-harness = false
-
 [features]
 default = []
 simd = ["memchr"]
diff --git a/tests/color/ann_eof.rs b/tests/color/ann_eof.rs
new file mode 100644
index 0000000..00e34b1
--- /dev/null
+++ b/tests/color/ann_eof.rs
@@ -0,0 +1,18 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let input = Level::ERROR.header("expected `.`, `=`").group(
+        Group::new().element(
+            Snippet::source("asdf")
+                .origin("Cargo.toml")
+                .line_start(1)
+                .annotation(AnnotationKind::Primary.span(4..4).label("")),
+        ),
+    );
+    let expected = file!["ann_eof.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/ann_eof.svg b/tests/color/ann_eof.term.svg
similarity index 100%
rename from tests/fixtures/color/ann_eof.svg
rename to tests/color/ann_eof.term.svg
diff --git a/tests/color/ann_insertion.rs b/tests/color/ann_insertion.rs
new file mode 100644
index 0000000..802a0c7
--- /dev/null
+++ b/tests/color/ann_insertion.rs
@@ -0,0 +1,18 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let input = Level::ERROR.header("expected `.`, `=`").group(
+        Group::new().element(
+            Snippet::source("asf")
+                .origin("Cargo.toml")
+                .line_start(1)
+                .annotation(AnnotationKind::Primary.span(2..2).label("'d' belongs here")),
+        ),
+    );
+    let expected = file!["ann_insertion.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/color/ann_insertion.term.svg
similarity index 100%
rename from tests/fixtures/color/ann_insertion.svg
rename to tests/color/ann_insertion.term.svg
diff --git a/tests/color/ann_multiline.rs b/tests/color/ann_multiline.rs
new file mode 100644
index 0000000..4b561ed
--- /dev/null
+++ b/tests/color/ann_multiline.rs
@@ -0,0 +1,31 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"                        if let DisplayLine::Source {
+                            ref mut inline_marks,
+                        } = body[body_idx]
+"#;
+
+    let input = Level::ERROR
+        .header("pattern does not mention fields `lineno`, `content`")
+        .id("E0027")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .origin("src/display_list.rs")
+                    .line_start(139)
+                    .fold(false)
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(31..128)
+                            .label("missing fields `lineno`, `content`"),
+                    ),
+            ),
+        );
+    let expected = file!["ann_multiline.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/color/ann_multiline.term.svg
similarity index 100%
rename from tests/fixtures/color/ann_multiline.svg
rename to tests/color/ann_multiline.term.svg
diff --git a/tests/color/ann_multiline2.rs b/tests/color/ann_multiline2.rs
new file mode 100644
index 0000000..9996fa9
--- /dev/null
+++ b/tests/color/ann_multiline2.rs
@@ -0,0 +1,31 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"This is an example
+of an edge case of an annotation overflowing
+to exactly one character on next line.
+"#;
+
+    let input = Level::ERROR
+        .header("spacing error found")
+        .id("E####")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .origin("foo.txt")
+                    .line_start(26)
+                    .fold(false)
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(11..19)
+                            .label("this should not be on separate lines"),
+                    ),
+            ),
+        );
+    let expected = file!["ann_multiline2.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/color/ann_multiline2.term.svg
similarity index 100%
rename from tests/fixtures/color/ann_multiline2.svg
rename to tests/color/ann_multiline2.term.svg
diff --git a/tests/color/ann_removed_nl.rs b/tests/color/ann_removed_nl.rs
new file mode 100644
index 0000000..45a6462
--- /dev/null
+++ b/tests/color/ann_removed_nl.rs
@@ -0,0 +1,18 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let input = Level::ERROR.header("expected `.`, `=`").group(
+        Group::new().element(
+            Snippet::source("asdf")
+                .origin("Cargo.toml")
+                .line_start(1)
+                .annotation(AnnotationKind::Primary.span(4..5).label("")),
+        ),
+    );
+    let expected = file!["ann_removed_nl.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/color/ann_removed_nl.term.svg
similarity index 100%
rename from tests/fixtures/color/ann_removed_nl.svg
rename to tests/color/ann_removed_nl.term.svg
diff --git a/tests/color/ensure_emoji_highlight_width.rs b/tests/color/ensure_emoji_highlight_width.rs
new file mode 100644
index 0000000..b239784
--- /dev/null
+++ b/tests/color/ensure_emoji_highlight_width.rs
@@ -0,0 +1,24 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#""haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }
+"#;
+
+    let input = Level::ERROR.header("invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)")
+        .group(
+            Group::new()
+                .element(
+                    Snippet::source(source)
+                        .origin("<file>")
+                        .line_start(7)
+                        .annotation(AnnotationKind::Primary.span(0..35).label(""))
+                )
+            )
+;
+    let expected = file!["ensure_emoji_highlight_width.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/color/ensure_emoji_highlight_width.term.svg
similarity index 100%
rename from tests/fixtures/color/ensure-emoji-highlight-width.svg
rename to tests/color/ensure_emoji_highlight_width.term.svg
diff --git a/tests/color/fold_ann_multiline.rs b/tests/color/fold_ann_multiline.rs
new file mode 100644
index 0000000..3995b68
--- /dev/null
+++ b/tests/color/fold_ann_multiline.rs
@@ -0,0 +1,50 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#") -> Option<String> {
+    for ann in annotations {
+        match (ann.range.0, ann.range.1) {
+            (None, None) => continue,
+            (Some(start), Some(end)) if start > end_index || end < start_index => continue,
+            (Some(start), Some(end)) if start >= start_index && end <= end_index => {
+                let label = if let Some(ref label) = ann.label {
+                    format!(" {}", label)
+                } else {
+                    String::from("")
+                };
+
+                return Some(format!(
+                    "{}{}{}",
+                    " ".repeat(start - start_index),
+                    "^".repeat(end - start),
+                    label
+                ));
+            }
+            _ => continue,
+        }
+    }
+"#;
+
+    let input = Level::ERROR.header("mismatched types").id("E0308").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("src/format.rs")
+                .line_start(51)
+                .fold(true)
+                .annotation(AnnotationKind::Context.span(5..19).label(
+                    "expected `std::option::Option<std::string::String>` because of return type",
+                ))
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(22..766)
+                        .label("expected enum `std::option::Option`, found ()"),
+                ),
+        ),
+    );
+    let expected = file!["fold_ann_multiline.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/color/fold_ann_multiline.term.svg
similarity index 100%
rename from tests/fixtures/color/fold_ann_multiline.svg
rename to tests/color/fold_ann_multiline.term.svg
diff --git a/tests/color/fold_bad_origin_line.rs b/tests/color/fold_bad_origin_line.rs
new file mode 100644
index 0000000..1a21a5e
--- /dev/null
+++ b/tests/color/fold_bad_origin_line.rs
@@ -0,0 +1,24 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"
+
+invalid syntax
+"#;
+
+    let input = Level::ERROR.header("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("path/to/error.rs")
+                .line_start(1)
+                .fold(true)
+                .annotation(AnnotationKind::Context.span(2..16).label("error here")),
+        ),
+    );
+    let expected = file!["fold_bad_origin_line.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/color/fold_bad_origin_line.term.svg
similarity index 100%
rename from tests/fixtures/color/fold_bad_origin_line.svg
rename to tests/color/fold_bad_origin_line.term.svg
diff --git a/tests/color/fold_leading.rs b/tests/color/fold_leading.rs
new file mode 100644
index 0000000..93ba499
--- /dev/null
+++ b/tests/color/fold_leading.rs
@@ -0,0 +1,35 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"[workspace]
+
+[package]
+name = "hello"
+version = "1.0.0"
+license = "MIT"
+rust-version = "1.70"
+edition = "2021"
+
+[lints]
+workspace = 20
+"#;
+
+    let input = Level::ERROR
+        .header("invalid type: integer `20`, expected a bool")
+        .id("E0308")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .origin("Cargo.toml")
+                    .line_start(1)
+                    .fold(true)
+                    .annotation(AnnotationKind::Primary.span(132..134).label("")),
+            ),
+        );
+    let expected = file!["fold_leading.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/fold_leading.svg b/tests/color/fold_leading.term.svg
similarity index 100%
rename from tests/fixtures/color/fold_leading.svg
rename to tests/color/fold_leading.term.svg
diff --git a/tests/color/fold_trailing.rs b/tests/color/fold_trailing.rs
new file mode 100644
index 0000000..f86ade7
--- /dev/null
+++ b/tests/color/fold_trailing.rs
@@ -0,0 +1,34 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"lints = 20
+
+[workspace]
+
+[package]
+name = "hello"
+version = "1.0.0"
+license = "MIT"
+rust-version = "1.70"
+edition = "2021"
+"#;
+
+    let input = Level::ERROR
+        .header("invalid type: integer `20`, expected a lints table")
+        .id("E0308")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .origin("Cargo.toml")
+                    .line_start(1)
+                    .fold(true)
+                    .annotation(AnnotationKind::Primary.span(8..10).label("")),
+            ),
+        );
+    let expected = file!["fold_trailing.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/color/fold_trailing.term.svg
similarity index 100%
rename from tests/fixtures/color/fold_trailing.svg
rename to tests/color/fold_trailing.term.svg
diff --git a/tests/color/issue_9.rs b/tests/color/issue_9.rs
new file mode 100644
index 0000000..2accd2f
--- /dev/null
+++ b/tests/color/issue_9.rs
@@ -0,0 +1,31 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let input = Level::ERROR.header("expected one of `.`, `;`, `?`, or an operator, found `for`")
+        .group(
+            Group::new()
+                .element(
+                    Snippet::source("let x = vec![1];")
+                        .origin("/code/rust/src/test/ui/annotate-snippet/suggestion.rs")
+                        .line_start(4)
+                        .annotation(AnnotationKind::Context.span(4..5).label("move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait"))
+                )
+                .element(
+                    Snippet::source("let y = x;")
+                        .line_start(7)
+                        .annotation(AnnotationKind::Context.span(8..9).label("value moved here"))
+                )
+                .element(
+                    Snippet::source("x;")
+                        .line_start(9)
+                        .annotation(AnnotationKind::Primary.span(0..1).label("value used here after move"))
+                )
+            )
+;
+    let expected = file!["issue_9.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/issue_9.svg b/tests/color/issue_9.term.svg
similarity index 100%
rename from tests/fixtures/color/issue_9.svg
rename to tests/color/issue_9.term.svg
diff --git a/tests/color/main.rs b/tests/color/main.rs
new file mode 100644
index 0000000..f954bb7
--- /dev/null
+++ b/tests/color/main.rs
@@ -0,0 +1,16 @@
+mod ann_eof;
+mod ann_insertion;
+mod ann_multiline;
+mod ann_multiline2;
+mod ann_removed_nl;
+mod ensure_emoji_highlight_width;
+mod fold_ann_multiline;
+mod fold_bad_origin_line;
+mod fold_leading;
+mod fold_trailing;
+mod issue_9;
+mod multiple_annotations;
+mod simple;
+mod strip_line;
+mod strip_line_char;
+mod strip_line_non_ws;
diff --git a/tests/color/multiple_annotations.rs b/tests/color/multiple_annotations.rs
new file mode 100644
index 0000000..b568b91
--- /dev/null
+++ b/tests/color/multiple_annotations.rs
@@ -0,0 +1,42 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {
+    if let Some(annotation) = main_annotation {
+        result.push(format_title_line(
+            &annotation.annotation_type,
+            None,
+            &annotation.label,
+        ));
+    }
+}
+"#;
+
+    let input = Level::ERROR.header("").group(
+        Group::new().element(
+            Snippet::source(source)
+                .line_start(96)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(100..110)
+                        .label("Variable defined here"),
+                )
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(184..194)
+                        .label("Referenced here"),
+                )
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(243..253)
+                        .label("Referenced again here"),
+                ),
+        ),
+    );
+    let expected = file!["multiple_annotations.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/color/multiple_annotations.term.svg
similarity index 100%
rename from tests/fixtures/color/multiple_annotations.svg
rename to tests/color/multiple_annotations.term.svg
diff --git a/tests/color/simple.rs b/tests/color/simple.rs
new file mode 100644
index 0000000..35e83d3
--- /dev/null
+++ b/tests/color/simple.rs
@@ -0,0 +1,34 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"        })
+
+        for line in &self.body {
+"#;
+
+    let input = Level::ERROR
+        .header("expected one of `.`, `;`, `?`, or an operator, found `for`")
+        .group(
+            Group::new().element(
+                Snippet::source(source)
+                    .origin("src/format_color.rs")
+                    .line_start(169)
+                    .annotation(
+                        AnnotationKind::Primary
+                            .span(20..23)
+                            .label("unexpected token"),
+                    )
+                    .annotation(
+                        AnnotationKind::Context
+                            .span(10..11)
+                            .label("expected one of `.`, `;`, `?`, or an operator here"),
+                    ),
+            ),
+        );
+    let expected = file!["simple.term.svg"];
+    let renderer = Renderer::styled();
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/simple.svg b/tests/color/simple.term.svg
similarity index 100%
rename from tests/fixtures/color/simple.svg
rename to tests/color/simple.term.svg
diff --git a/tests/color/strip_line.rs b/tests/color/strip_line.rs
new file mode 100644
index 0000000..fd1ba58
--- /dev/null
+++ b/tests/color/strip_line.rs
@@ -0,0 +1,24 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"                                                                                                                                                                                    let _: () = 42;"#;
+
+    let input = Level::ERROR.header("mismatched types").id("E0308").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("$DIR/whitespace-trimming.rs")
+                .line_start(4)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(192..194)
+                        .label("expected (), found integer"),
+                ),
+        ),
+    );
+    let expected = file!["strip_line.term.svg"];
+    let renderer = Renderer::styled().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/strip_line.svg b/tests/color/strip_line.term.svg
similarity index 100%
rename from tests/fixtures/color/strip_line.svg
rename to tests/color/strip_line.term.svg
diff --git a/tests/color/strip_line_char.rs b/tests/color/strip_line_char.rs
new file mode 100644
index 0000000..df609e2
--- /dev/null
+++ b/tests/color/strip_line_char.rs
@@ -0,0 +1,24 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"                                                                                                                                                                                    let _: () = 42ñ"#;
+
+    let input = Level::ERROR.header("mismatched types").id("E0308").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("$DIR/whitespace-trimming.rs")
+                .line_start(4)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(192..194)
+                        .label("expected (), found integer"),
+                ),
+        ),
+    );
+    let expected = file!["strip_line_char.term.svg"];
+    let renderer = Renderer::styled().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/color/strip_line_char.term.svg
similarity index 100%
rename from tests/fixtures/color/strip_line_char.svg
rename to tests/color/strip_line_char.term.svg
diff --git a/tests/color/strip_line_non_ws.rs b/tests/color/strip_line_non_ws.rs
new file mode 100644
index 0000000..f82d369
--- /dev/null
+++ b/tests/color/strip_line_non_ws.rs
@@ -0,0 +1,30 @@
+use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
+
+use snapbox::{assert_data_eq, file};
+
+#[test]
+fn case() {
+    let source = r#"	let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();
+"#;
+
+    let input = Level::ERROR.header("mismatched types").id("E0308").group(
+        Group::new().element(
+            Snippet::source(source)
+                .origin("$DIR/non-whitespace-trimming.rs")
+                .line_start(4)
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(237..239)
+                        .label("expected `()`, found integer"),
+                )
+                .annotation(
+                    AnnotationKind::Primary
+                        .span(232..234)
+                        .label("expected due to this"),
+                ),
+        ),
+    );
+    let expected = file!["strip_line_non_ws.term.svg"];
+    let renderer = Renderer::styled().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input), expected);
+}
diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/color/strip_line_non_ws.term.svg
similarity index 100%
rename from tests/fixtures/color/strip_line_non_ws.svg
rename to tests/color/strip_line_non_ws.term.svg
diff --git a/tests/fixtures/color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml
deleted file mode 100644
index ef711de..0000000
--- a/tests/fixtures/color/ann_eof.toml
+++ /dev/null
@@ -1,15 +0,0 @@
-[message]
-level = "Error"
-header = "expected `.`, `=`"
-
-[[message.sections]]
-type = "Cause"
-source = "asdf"
-line_start = 1
-origin = "Cargo.toml"
-annotations = [
-  { label = "", kind = "Primary", range = [4, 4] },
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml
deleted file mode 100644
index 30af1bf..0000000
--- a/tests/fixtures/color/ann_insertion.toml
+++ /dev/null
@@ -1,15 +0,0 @@
-[message]
-level = "Error"
-header = "expected `.`, `=`"
-
-[[message.sections]]
-type = "Cause"
-source = "asf"
-line_start = 1
-origin = "Cargo.toml"
-annotations = [
-    { label = "'d' belongs here", kind = "Primary", range = [2, 2] }
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml
deleted file mode 100644
index 2a5f206..0000000
--- a/tests/fixtures/color/ann_multiline.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-[message]
-level = "Error"
-id = "E0027"
-header = "pattern does not mention fields `lineno`, `content`"
-
-[[message.sections]]
-type = "Cause"
-source = """
-                        if let DisplayLine::Source {
-                            ref mut inline_marks,
-                        } = body[body_idx]
-"""
-line_start = 139
-origin = "src/display_list.rs"
-fold = false
-annotations = [
-    { label = "missing fields `lineno`, `content`", kind = "Primary", range = [31, 128] }
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml
deleted file mode 100644
index 854b38a..0000000
--- a/tests/fixtures/color/ann_multiline2.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-[message]
-level = "Error"
-id = "E####"
-header = "spacing error found"
-
-[[message.sections]]
-type = "Cause"
-source = """
-This is an example
-of an edge case of an annotation overflowing
-to exactly one character on next line.
-"""
-line_start = 26
-origin = "foo.txt"
-fold = false
-annotations = [
-    { label = "this should not be on separate lines", kind = "Primary", range = [11, 19] },
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml
deleted file mode 100644
index 6ffeb7a..0000000
--- a/tests/fixtures/color/ann_removed_nl.toml
+++ /dev/null
@@ -1,15 +0,0 @@
-[message]
-level = "Error"
-header = "expected `.`, `=`"
-
-[[message.sections]]
-type = "Cause"
-source = "asdf"
-line_start = 1
-origin = "Cargo.toml"
-annotations = [
-    { label = "", kind = "Primary", range = [4, 5] },
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml
deleted file mode 100644
index 669959f..0000000
--- a/tests/fixtures/color/ensure-emoji-highlight-width.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-[message]
-header = "invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)"
-level = "Error"
-
-
-[[message.sections]]
-type = "Cause"
-source = """
-"haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }
-"""
-line_start = 7
-origin = "<file>"
-annotations = [
-    { label = "", kind = "Primary", range = [0, 35] },
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml
deleted file mode 100644
index 2cee27d..0000000
--- a/tests/fixtures/color/fold_ann_multiline.toml
+++ /dev/null
@@ -1,41 +0,0 @@
-[message]
-level = "Error"
-id = "E0308"
-header = "mismatched types"
-
-[[message.sections]]
-type = "Cause"
-source = """
-) -> Option<String> {
-    for ann in annotations {
-        match (ann.range.0, ann.range.1) {
-            (None, None) => continue,
-            (Some(start), Some(end)) if start > end_index || end < start_index => continue,
-            (Some(start), Some(end)) if start >= start_index && end <= end_index => {
-                let label = if let Some(ref label) = ann.label {
-                    format!(" {}", label)
-                } else {
-                    String::from("")
-                };
-
-                return Some(format!(
-                    "{}{}{}",
-                    " ".repeat(start - start_index),
-                    "^".repeat(end - start),
-                    label
-                ));
-            }
-            _ => continue,
-        }
-    }
-"""
-line_start = 51
-origin = "src/format.rs"
-fold = true
-annotations = [
-    { label = "expected `std::option::Option<std::string::String>` because of return type", kind = "Context", range = [5, 19] },
-    { label = "expected enum `std::option::Option`, found ()", kind = "Primary", range = [22, 766] },
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml
deleted file mode 100644
index 2fab2d6..0000000
--- a/tests/fixtures/color/fold_bad_origin_line.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[message]
-level = "Error"
-header = ""
-
-[[message.sections]]
-type = "Cause"
-source = """
-
-
-invalid syntax
-"""
-line_start = 1
-origin = "path/to/error.rs"
-fold = true
-annotations = [
-    { label = "error here", kind = "Context", range = [2,16] },
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml
deleted file mode 100644
index 0ef043c..0000000
--- a/tests/fixtures/color/fold_leading.toml
+++ /dev/null
@@ -1,29 +0,0 @@
-[message]
-level = "Error"
-id = "E0308"
-header = "invalid type: integer `20`, expected a bool"
-
-[[message.sections]]
-type = "Cause"
-source = """
-[workspace]
-
-[package]
-name = "hello"
-version = "1.0.0"
-license = "MIT"
-rust-version = "1.70"
-edition = "2021"
-
-[lints]
-workspace = 20
-"""
-line_start = 1
-origin = "Cargo.toml"
-fold = true
-annotations = [
-    { label = "", kind = "Primary", range = [132, 134] },
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml
deleted file mode 100644
index 91e4ab4..0000000
--- a/tests/fixtures/color/fold_trailing.toml
+++ /dev/null
@@ -1,28 +0,0 @@
-[message]
-level = "Error"
-id = "E0308"
-header = "invalid type: integer `20`, expected a lints table"
-
-[[message.sections]]
-type = "Cause"
-source = """
-lints = 20
-
-[workspace]
-
-[package]
-name = "hello"
-version = "1.0.0"
-license = "MIT"
-rust-version = "1.70"
-edition = "2021"
-"""
-line_start = 1
-origin = "Cargo.toml"
-fold = true
-annotations = [
-    { label = "", kind = "Primary", range = [8, 10] },
-]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/issue_9.toml b/tests/fixtures/color/issue_9.toml
deleted file mode 100644
index f423915..0000000
--- a/tests/fixtures/color/issue_9.toml
+++ /dev/null
@@ -1,34 +0,0 @@
-[message]
-level = "Error"
-header = "expected one of `.`, `;`, `?`, or an operator, found `for`"
-
-[[message.sections]]
-type = "Cause"
-source = "let x = vec![1];"
-line_start = 4
-origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs"
-[[message.sections.annotations]]
-label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait"
-kind = "Context"
-range = [4, 5]
-
-[[message.sections]]
-type = "Cause"
-source = "let y = x;"
-line_start = 7
-[[message.sections.annotations]]
-label = "value moved here"
-kind = "Context"
-range = [8, 9]
-
-[[message.sections]]
-type = "Cause"
-source = "x;"
-line_start = 9
-[[message.sections.annotations]]
-label = "value used here after move"
-kind = "Primary"
-range = [0, 1]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml
deleted file mode 100644
index 367c53e..0000000
--- a/tests/fixtures/color/multiple_annotations.toml
+++ /dev/null
@@ -1,33 +0,0 @@
-[message]
-level = "Error"
-header = ""
-
-[[message.sections]]
-type = "Cause"
-source = """
-fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {
-    if let Some(annotation) = main_annotation {
-        result.push(format_title_line(
-            &annotation.annotation_type,
-            None,
-            &annotation.label,
-        ));
-    }
-}
-"""
-line_start = 96
-[[message.sections.annotations]]
-label = "Variable defined here"
-kind = "Primary"
-range = [100, 110]
-[[message.sections.annotations]]
-label = "Referenced here"
-kind = "Primary"
-range = [184, 194]
-[[message.sections.annotations]]
-label = "Referenced again here"
-kind = "Primary"
-range = [243, 253]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/simple.toml b/tests/fixtures/color/simple.toml
deleted file mode 100644
index d5a3647..0000000
--- a/tests/fixtures/color/simple.toml
+++ /dev/null
@@ -1,23 +0,0 @@
-[message]
-level = "Error"
-header = "expected one of `.`, `;`, `?`, or an operator, found `for`"
-
-[[message.sections]]
-type = "Cause"
-source = """
-        })
-
-        for line in &self.body {"""
-line_start = 169
-origin = "src/format_color.rs"
-[[message.sections.annotations]]
-label = "unexpected token"
-kind = "Primary"
-range = [20, 23]
-[[message.sections.annotations]]
-label = "expected one of `.`, `;`, `?`, or an operator here"
-kind = "Context"
-range = [10, 11]
-
-[renderer]
-color = true
diff --git a/tests/fixtures/color/strip_line.toml b/tests/fixtures/color/strip_line.toml
deleted file mode 100644
index 18a7805..0000000
--- a/tests/fixtures/color/strip_line.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[message]
-level = "Error"
-id = "E0308"
-header = "mismatched types"
-
-[[message.sections]]
-type = "Cause"
-source = "                                                                                                                                                                                    let _: () = 42;"
-line_start = 4
-origin = "$DIR/whitespace-trimming.rs"
-
-[[message.sections.annotations]]
-label = "expected (), found integer"
-kind = "Primary"
-range = [192, 194]
-
-[renderer]
-color = true
-anonymized_line_numbers = true
diff --git a/tests/fixtures/color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml
deleted file mode 100644
index 3174ced..0000000
--- a/tests/fixtures/color/strip_line_char.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[message]
-level = "Error"
-id = "E0308"
-header = "mismatched types"
-
-[[message.sections]]
-type = "Cause"
-source = "                                                                                                                                                                                    let _: () = 42ñ"
-line_start = 4
-origin = "$DIR/whitespace-trimming.rs"
-
-[[message.sections.annotations]]
-label = "expected (), found integer"
-kind = "Primary"
-range = [192, 194]
-
-[renderer]
-color = true
-anonymized_line_numbers = true
diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml
deleted file mode 100644
index b7844ec..0000000
--- a/tests/fixtures/color/strip_line_non_ws.toml
+++ /dev/null
@@ -1,27 +0,0 @@
-[message]
-level = "Error"
-id = "E0308"
-header = "mismatched types"
-
-[[message.sections]]
-type = "Cause"
-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.sections.annotations]]
-label = "expected `()`, found integer"
-kind = "Primary"
-range = [237, 239]
-
-[[message.sections.annotations]]
-label = "expected due to this"
-kind = "Primary"
-range = [232, 234]
-
-
-[renderer]
-anonymized_line_numbers = true
-color = true
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
deleted file mode 100644
index 2042966..0000000
--- a/tests/fixtures/deserialize.rs
+++ /dev/null
@@ -1,217 +0,0 @@
-use serde::Deserialize;
-use std::ops::Range;
-
-use annotate_snippets::renderer::DEFAULT_TERM_WIDTH;
-use annotate_snippets::{
-    Annotation, AnnotationKind, Element, Group, Level, Message, Patch, Renderer, Snippet,
-};
-
-#[derive(Deserialize)]
-pub(crate) struct Fixture {
-    #[serde(default)]
-    pub(crate) renderer: RendererDef,
-    pub(crate) message: MessageDef,
-}
-
-#[derive(Deserialize)]
-pub struct MessageDef {
-    pub level: LevelDef,
-    pub header: String,
-    #[serde(default)]
-    pub id: Option<String>,
-    #[serde(default)]
-    pub sections: Vec<ElementDef>,
-}
-
-impl<'a> From<&'a MessageDef> for Message<'a> {
-    fn from(val: &'a MessageDef) -> Self {
-        let MessageDef {
-            level,
-            header,
-            id,
-            sections,
-        } = val;
-        let mut message = Level::from(level).header(header);
-        if let Some(id) = id {
-            message = message.id(id);
-        }
-
-        message = message.group(Group::new().elements(sections.iter().map(|s| match s {
-            ElementDef::Title(title) => {
-                Element::Title(Level::from(&title.level).title(&title.title))
-            }
-            ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)),
-            ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)),
-        })));
-        message
-    }
-}
-
-#[derive(Deserialize)]
-#[serde(tag = "type")]
-pub enum ElementDef {
-    Title(TitleDef),
-    Cause(SnippetAnnotationDef),
-    Suggestion(SnippetPatchDef),
-}
-
-impl<'a> From<&'a ElementDef> for Element<'a> {
-    fn from(val: &'a ElementDef) -> Self {
-        match val {
-            ElementDef::Title(title) => {
-                Element::Title(Level::from(&title.level).title(&title.title))
-            }
-            ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)),
-            ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)),
-        }
-    }
-}
-
-#[derive(Deserialize)]
-pub struct TitleDef {
-    pub title: String,
-    pub level: LevelDef,
-}
-
-#[derive(Deserialize)]
-pub struct SnippetAnnotationDef {
-    pub(crate) origin: Option<String>,
-    pub(crate) line_start: usize,
-    pub(crate) source: String,
-    pub(crate) annotations: Vec<AnnotationDef>,
-    #[serde(default)]
-    pub(crate) fold: bool,
-}
-
-impl<'a> From<&'a SnippetAnnotationDef> for Snippet<'a, Annotation<'a>> {
-    fn from(val: &'a SnippetAnnotationDef) -> Self {
-        let SnippetAnnotationDef {
-            origin,
-            line_start,
-            source,
-            annotations,
-            fold,
-        } = val;
-        let mut snippet = Snippet::source(source).line_start(*line_start).fold(*fold);
-        if let Some(origin) = origin {
-            snippet = snippet.origin(origin);
-        }
-        snippet = snippet.annotations(annotations.iter().map(Into::into));
-        snippet
-    }
-}
-
-#[derive(Deserialize)]
-pub struct AnnotationDef {
-    pub range: Range<usize>,
-    pub label: String,
-    #[serde(with = "AnnotationKindDef")]
-    pub kind: AnnotationKind,
-}
-
-impl<'a> From<&'a AnnotationDef> for Annotation<'a> {
-    fn from(val: &'a AnnotationDef) -> Self {
-        let AnnotationDef { range, label, kind } = val;
-        kind.span(range.start..range.end).label(label)
-    }
-}
-
-#[allow(dead_code)]
-#[derive(Deserialize)]
-#[serde(remote = "AnnotationKind")]
-enum AnnotationKindDef {
-    Primary,
-    Context,
-}
-
-#[derive(Deserialize)]
-pub struct SnippetPatchDef {
-    pub(crate) origin: Option<String>,
-    pub(crate) line_start: usize,
-    pub(crate) source: String,
-    pub(crate) patches: Vec<PatchDef>,
-    #[serde(default)]
-    pub(crate) fold: bool,
-}
-
-impl<'a> From<&'a SnippetPatchDef> for Snippet<'a, Patch<'a>> {
-    fn from(val: &'a SnippetPatchDef) -> Self {
-        let SnippetPatchDef {
-            origin,
-            line_start,
-            source,
-            patches,
-            fold,
-        } = val;
-        let mut snippet = Snippet::source(source).line_start(*line_start).fold(*fold);
-        if let Some(origin) = origin {
-            snippet = snippet.origin(origin);
-        }
-        snippet = snippet.patches(patches.iter().map(Into::into));
-        snippet
-    }
-}
-
-#[derive(Deserialize)]
-pub struct PatchDef {
-    pub range: Range<usize>,
-    pub replacement: String,
-}
-
-impl<'a> From<&'a PatchDef> for Patch<'a> {
-    fn from(val: &'a PatchDef) -> Self {
-        let PatchDef { range, replacement } = val;
-        Patch::new(range.start..range.end, replacement)
-    }
-}
-
-#[allow(dead_code)]
-#[derive(Clone, Copy, Deserialize)]
-pub enum LevelDef {
-    Error,
-    Warning,
-    Info,
-    Note,
-    Help,
-}
-
-impl<'a> From<&'a LevelDef> for Level<'a> {
-    fn from(val: &'a LevelDef) -> Self {
-        match val {
-            LevelDef::Error => Level::ERROR,
-            LevelDef::Warning => Level::WARNING,
-            LevelDef::Info => Level::INFO,
-            LevelDef::Note => Level::NOTE,
-            LevelDef::Help => Level::HELP,
-        }
-    }
-}
-
-#[derive(Default, Deserialize)]
-pub struct RendererDef {
-    #[serde(default)]
-    anonymized_line_numbers: bool,
-    #[serde(default)]
-    term_width: Option<usize>,
-    #[serde(default)]
-    color: bool,
-}
-
-impl From<RendererDef> for Renderer {
-    fn from(val: RendererDef) -> Self {
-        let RendererDef {
-            anonymized_line_numbers,
-            term_width,
-            color,
-        } = val;
-
-        let renderer = if color {
-            Renderer::styled()
-        } else {
-            Renderer::plain()
-        };
-        renderer
-            .anonymized_line_numbers(anonymized_line_numbers)
-            .term_width(term_width.unwrap_or(DEFAULT_TERM_WIDTH))
-    }
-}
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
deleted file mode 100644
index 2708262..0000000
--- a/tests/fixtures/main.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-mod deserialize;
-
-use crate::deserialize::Fixture;
-use annotate_snippets::{Message, Renderer};
-use snapbox::data::DataFormat;
-use snapbox::Data;
-use std::error::Error;
-
-fn main() {
-    #[cfg(not(windows))]
-    tryfn::Harness::new("tests/fixtures/", setup, test)
-        .select(["*/*.toml"])
-        .test();
-}
-
-fn setup(input_path: std::path::PathBuf) -> tryfn::Case {
-    let parent = input_path
-        .parent()
-        .unwrap()
-        .file_name()
-        .unwrap()
-        .to_str()
-        .unwrap();
-    let file_name = input_path.file_name().unwrap().to_str().unwrap();
-    let name = format!("{parent}/{file_name}");
-    let expected = Data::read_from(&input_path.with_extension("svg"), None);
-    tryfn::Case {
-        name,
-        fixture: input_path,
-        expected,
-    }
-}
-
-fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> {
-    let src = std::fs::read_to_string(input_path)?;
-    let fixture: Fixture = toml::from_str(&src)?;
-    let renderer: Renderer = fixture.renderer.into();
-    let message: Message<'_> = (&fixture.message).into();
-
-    let actual = renderer.render(message);
-    Ok(Data::from(actual).coerce_to(DataFormat::TermSvg))
-}

From df05eb77a29bc9baa56764f6041b318f4e55ba4d Mon Sep 17 00:00:00 2001
From: Scott Schafer <schaferjscott@gmail.com>
Date: Sat, 26 Apr 2025 08:23:27 -0600
Subject: [PATCH 301/302] chore: Remove unused dev-dependencies

---
 Cargo.lock | 15 ---------------
 Cargo.toml |  3 ---
 2 files changed, 18 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index dd4fd20..e643134 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,11 +9,8 @@ dependencies = [
  "annotate-snippets",
  "anstream",
  "anstyle",
- "difference",
  "divan",
- "glob",
  "memchr",
- "serde",
  "snapbox",
  "unicode-width 0.2.0",
 ]
@@ -139,12 +136,6 @@ version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af"
 
-[[package]]
-name = "difference"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
-
 [[package]]
 name = "divan"
 version = "0.1.17"
@@ -192,12 +183,6 @@ dependencies = [
  "serde_json",
 ]
 
-[[package]]
-name = "glob"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
-
 [[package]]
 name = "hermit-abi"
 version = "0.3.9"
diff --git a/Cargo.toml b/Cargo.toml
index 4a8adf0..8434567 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -123,10 +123,7 @@ unicode-width = "0.2.0"
 [dev-dependencies]
 annotate-snippets = { path = ".", features = ["testing-colors"] }
 anstream = "0.6.13"
-difference = "2.0.0"
 divan = "0.1.14"
-glob = "0.3.1"
-serde = { version = "1.0.199", features = ["derive"] }
 snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] }
 
 [[bench]]

From 9c5ba46e1cd041df9e4a62a2936130c1f5c8ceea Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 1 May 2025 03:41:39 +0000
Subject: [PATCH 302/302] chore(deps): Update Rust crate divan to v0.1.21
 (#202)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Cargo.lock | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index e643134..532bb57 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -138,9 +138,9 @@ checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af"
 
 [[package]]
 name = "divan"
-version = "0.1.17"
+version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0583193020b29b03682d8d33bb53a5b0f50df6daacece12ca99b904cfdcb8c4"
+checksum = "a405457ec78b8fe08b0e32b4a3570ab5dff6dd16eb9e76a5ee0a9d9cbd898933"
 dependencies = [
  "cfg-if",
  "clap",
@@ -152,9 +152,9 @@ dependencies = [
 
 [[package]]
 name = "divan-macros"
-version = "0.1.17"
+version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c"
+checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323"
 dependencies = [
  "proc-macro2",
  "quote",