annotate-snippets-0.11.4/.cargo_vcs_info.json0000644000000001360000000000100145730ustar { "git": { "sha1": "fb498f918087557f48dd34b81f3bf4081fe6e961" }, "path_in_vcs": "" }annotate-snippets-0.11.4/.clippy.toml000064400000000000000000000001601046102023000156330ustar 00000000000000allow-print-in-tests = true allow-expect-in-tests = true allow-unwrap-in-tests = true allow-dbg-in-tests = true annotate-snippets-0.11.4/.github/renovate.json5000064400000000000000000000047141046102023000175340ustar 00000000000000{ schedule: [ 'before 5am on the first day of the month', ], semanticCommits: 'enabled', commitMessageLowerCase: 'never', 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: [ 'STABLE.*?(?\\d+\\.\\d+(\\.\\d+)?)', '(?\\d+\\.\\d+(\\.\\d+)?).*?STABLE', ], depNameTemplate: 'STABLE', packageNameTemplate: 'rust-lang/rust', datasourceTemplate: 'github-releases', }, ], packageRules: [ { commitMessageTopic: 'Rust Stable', matchManagers: [ 'custom.regex', ], matchPackageNames: [ 'STABLE', ], extractVersion: '^(?\\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', ], matchDepTypes: [ 'build-dependencies', 'dependencies', ], matchCurrentVersion: '>=0.1.0', matchUpdateTypes: [ 'patch', ], enabled: false, }, { matchManagers: [ 'cargo', ], matchDepTypes: [ 'build-dependencies', 'dependencies', ], matchCurrentVersion: '>=1.0.0', matchUpdateTypes: [ 'minor', 'patch', ], 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', 'patch', ], automerge: true, groupName: 'compatible (dev)', }, ], } annotate-snippets-0.11.4/.github/workflows/audit.yml000064400000000000000000000023131046102023000206100ustar 00000000000000name: 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 concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true 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 annotate-snippets-0.11.4/.github/workflows/ci.yml000064400000000000000000000075071046102023000201070ustar 00000000000000name: CI permissions: contents: read on: pull_request: push: branches: - master env: RUST_BACKTRACE: 1 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, lockfile, docs, rustfmt, clippy] runs-on: ubuntu-latest if: "always()" steps: - 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-14"] 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 - uses: taiki-e/install-action@cargo-hack - name: Build run: cargo test --workspace --no-run - name: Test run: cargo hack test --feature-powerset --workspace msrv: name: "Check MSRV" 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 - uses: taiki-e/install-action@cargo-hack - name: Default features run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets 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 update --workspace --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: "1.79" # 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: toolchain: "1.79" # 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.79" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools run: cargo install clippy-sarif --locked - name: Install SARIF tools run: cargo install sarif-fmt --locked - 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@v3 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 annotate-snippets-0.11.4/.github/workflows/committed.yml000064400000000000000000000011231046102023000214650ustar 00000000000000# 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 concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true 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 annotate-snippets-0.11.4/.github/workflows/pre-commit.yml000064400000000000000000000007071046102023000215630ustar 00000000000000name: pre-commit permissions: {} # none on: pull_request: push: 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: contents: read runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - uses: pre-commit/action@v3.0.1 annotate-snippets-0.11.4/.github/workflows/rust-next.yml000064400000000000000000000027411046102023000214600ustar 00000000000000name: rust-next permissions: contents: read on: schedule: - cron: '1 1 1 * *' env: RUST_BACKTRACE: 1 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", "macos-14"] 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 - 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 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: toolchain: stable - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-hack - name: Update dependencies run: cargo update - name: Build run: cargo test --workspace --no-run - name: Test run: cargo hack test --feature-powerset --workspace annotate-snippets-0.11.4/.github/workflows/spelling.yml000064400000000000000000000007031046102023000213200ustar 00000000000000name: Spelling permissions: contents: read on: [pull_request] env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always CLICOLOR: 1 concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true 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 annotate-snippets-0.11.4/.gitignore000064400000000000000000000000071046102023000153500ustar 00000000000000target annotate-snippets-0.11.4/.pre-commit-config.yaml000064400000000000000000000011511046102023000176420ustar 00000000000000repos: - 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] - repo: https://github.com/crate-ci/typos rev: v1.16.20 hooks: - id: typos stages: [commit] - repo: https://github.com/crate-ci/committed rev: v1.0.20 hooks: - id: committed stages: [commit-msg] annotate-snippets-0.11.4/CHANGELOG.md000064400000000000000000000201671046102023000152020ustar 00000000000000# 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/). ## [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) ## [0.11.3] - 2024-06-06 ### Fixes - Dropped MSRV to 1.65 ## [0.11.2] - 2024-04-27 ### 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 - 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 - 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` 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 ### 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 - 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 - `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) - Origin line number is not correct when using a slice with fold: true (#52) ## [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) ## [0.9.0] - June 28, 2020 - Add strip code to the left and right of long lines. (#36) ## [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) ## [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. ## [0.6.1] - July 23, 2019 - Fix too many anonymized line numbers (#5) ## [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) [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 [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 [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 annotate-snippets-0.11.4/Cargo.lock0000644000000654220000000000100125570ustar # This file is automatically @generated by Cargo. # 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 = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" version = "0.11.4" dependencies = [ "anstream 0.6.14", "anstyle", "criterion", "difference", "glob", "serde", "snapbox", "toml", "tryfn", "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon 3.0.3", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-lossy" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fcff6599f06e21b0165c85052ccd6e67dc388ddd1c516a9dc5f55dc8cacf004" dependencies = [ "anstyle", ] [[package]] name = "anstyle-parse" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-svg" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa" dependencies = [ "anstream 0.6.14", "anstyle", "anstyle-lossy", "html-escape", "unicode-width", ] [[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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bstr" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487" dependencies = [ "clap_builder", "clap_derive", "once_cell", ] [[package]] name = "clap_builder" 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", ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "criterion" version = "0.5.1" 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", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[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.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eb5f6eeda986377996e9ed570cbc20cc16d30440696f82f129c863e4e3e83" dependencies = [ "log", "once_cell", "serde", "serde_json", ] [[package]] name = "glob" 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 = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" dependencies = [ "crunchy", ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[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.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 = "is-terminal" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] [[package]] name = "is_terminal_polyfill" version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b52b2de84ed0341893ce61ca1af04fa54eea0a764ecc38c6855cc5db84dc1927" 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" 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 = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[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.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 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.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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" dependencies = [ "libc", "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 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" 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-syntax" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[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.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "similar" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "snapbox" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94204b12a4d3550420babdb4148c6639692e4e3e61060866929c5107f208aeb6" dependencies = [ "anstream 0.6.14", "anstyle", "anstyle-svg", "escargot", "libc", "normalize-line-endings", "os_pipe", "serde_json", "similar", "snapbox-macros", "wait-timeout", "windows-sys 0.52.0", ] [[package]] name = "snapbox-macros" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d" dependencies = [ "anstream 0.6.14", ] [[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.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", "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 = "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 = "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 = "tryfn" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "493e1390312bb94363f762687be32a1bd01c3333dfad25a5a7fffab1edc64839" dependencies = [ "ignore", "libtest-mimic", "snapbox", ] [[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.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[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 = "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.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "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" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "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.5", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "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]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[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.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 = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" annotate-snippets-0.11.4/Cargo.toml0000644000000114350000000000100125750ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65" name = "annotate-snippets" version = "0.11.4" authors = ["Zibi Braniecki "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Library for building code annotations" readme = "README.md" keywords = [ "code", "analysis", "ascii", "errors", "debug", ] license = "Apache-2.0/MIT" repository = "https://github.com/rust-lang/annotate-snippets-rs" [package.metadata.release] tag-name = "{{version}}" [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" min = 1 replace = "{{version}}" search = "Unreleased" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = "...{{tag_name}}" search = '\.\.\.HEAD' [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" min = 1 replace = "{{date}}" search = "ReleaseDate" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = """ ## [Unreleased] - ReleaseDate """ search = "" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = """ [Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/{{tag_name}}...HEAD""" search = "" [lib] name = "annotate_snippets" path = "src/lib.rs" [[example]] name = "expected_type" path = "examples/expected_type.rs" [[example]] name = "footer" path = "examples/footer.rs" [[example]] name = "format" path = "examples/format.rs" [[example]] name = "multislice" path = "examples/multislice.rs" [[test]] name = "examples" path = "tests/examples.rs" [[test]] name = "fixtures" path = "tests/fixtures/main.rs" harness = false [[test]] name = "formatter" path = "tests/formatter.rs" [[bench]] name = "simple" path = "benches/simple.rs" harness = false [dependencies.anstyle] version = "1.0.4" [dependencies.unicode-width] version = "0.1.11" [dev-dependencies.anstream] version = "0.6.13" [dev-dependencies.criterion] version = "0.5.1" [dev-dependencies.difference] version = "2.0.0" [dev-dependencies.glob] version = "0.3.1" [dev-dependencies.serde] version = "1.0.199" features = ["derive"] [dev-dependencies.snapbox] version = "0.6.0" features = [ "diff", "term-svg", "cmd", "examples", ] [dev-dependencies.toml] version = "0.5.11" [dev-dependencies.tryfn] version = "0.2.1" [features] default = [] testing-colors = [] [badges.maintenance] status = "actively-developed" [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" 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" [lints.rust] unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" [lints.rust.rust_2018_idioms] level = "warn" priority = -1 annotate-snippets-0.11.4/Cargo.toml.orig000064400000000000000000000065351046102023000162630ustar 00000000000000[package] name = "annotate-snippets" version = "0.11.4" edition = "2021" rust-version = "1.65" # MSRV authors = ["Zibi Braniecki "] description = "Library for building code annotations" license = "Apache-2.0/MIT" 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="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/{{tag_name}}...HEAD", exactly=1}, ] [badges] maintenance = { status = "actively-developed" } [dependencies] anstyle = "1.0.4" 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.199", features = ["derive"] } snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] } toml = "0.5.11" tryfn = "0.2.1" [[bench]] name = "simple" harness = false [[test]] name = "fixtures" harness = false [features] default = [] testing-colors = [] [lints.rust] 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" annotate-snippets-0.11.4/LICENSE-APACHE000064400000000000000000000261351046102023000153160ustar 00000000000000 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. annotate-snippets-0.11.4/LICENSE-MIT000064400000000000000000000020271046102023000150200ustar 00000000000000Copyright 2017 Mozilla 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. annotate-snippets-0.11.4/README.md000064400000000000000000000015441046102023000146460ustar 00000000000000# annotate-snippets `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: ![Screenshot](./examples/expected_type.svg) Local Development ----------------- cargo build cargo test When submitting a PR please use [`cargo fmt`][] (nightly). [`cargo fmt`]: https://github.com/rust-lang/rustfmt [Documentation]: https://docs.rs/annotate-snippets/ annotate-snippets-0.11.4/benches/simple.rs000064400000000000000000000033621046102023000166350ustar 00000000000000#![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) { let source = r#") -> Option { for ann in annotations { match (ann.range.0, ann.range.1) { (None, None) => continue, (Some(start), Some(end)) if start > end_index => continue, (Some(start), Some(end)) if start >= start_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 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` because of return type"), ) .annotation( Level::Error .span(26..724) .label("expected enum `std::option::Option`"), ), ); let _result = renderer.render(message).to_string(); } pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("format", |b| { b.iter(|| black_box(create_snippet(Renderer::plain()))); }); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); annotate-snippets-0.11.4/committed.toml000064400000000000000000000001231046102023000162410ustar 00000000000000style="conventional" ignore_author_re="(dependabot|renovate)" merge_commit = false annotate-snippets-0.11.4/deny.toml000064400000000000000000000252411046102023000152230ustar 00000000000000# 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 # 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 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. 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] # 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", "OpenSSL", ] # 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"], 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] # 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 # 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: # 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 = "allow" # 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 = [ #"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 = [ #"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 #{ 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`. # 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 = [] # 1 or more gitlab.com organizations to allow git sources for gitlab = [] # 1 or more bitbucket.org organizations to allow git sources for bitbucket = [] annotate-snippets-0.11.4/examples/expected_type.rs000064400000000000000000000015651046102023000204200ustar 00000000000000use annotate_snippets::{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 renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); } annotate-snippets-0.11.4/examples/expected_type.svg000064400000000000000000000044351046102023000205720ustar 00000000000000 error: expected type, found `22` --> examples/footer.rs:29:25 | 26 | annotations: vec![SourceAnnotation { | ---------------- info: while parsing this struct 27 | label: "expected struct `annotate_snippets::snippet::Slice`, found reference" 28 | , 29 | range: <22, 25>, | ^^ expected struct `annotate_snippets::snippet::Slice`, found reference | annotate-snippets-0.11.4/examples/footer.rs000064400000000000000000000014241046102023000170460ustar 00000000000000use annotate_snippets::{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(Level::Error.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`", )); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); } annotate-snippets-0.11.4/examples/footer.svg000064400000000000000000000035471046102023000172310ustar 00000000000000 error[E0308]: mismatched types --> src/multislice.rs:13:22 | 13 | slices: vec!["A", | ^^^ expected struct `annotate_snippets::snippet::Slice`, found reference | = note: expected type: `snippet::Annotation` found type: `__&__snippet::Annotation` annotate-snippets-0.11.4/examples/format.rs000064400000000000000000000026471046102023000170500ustar 00000000000000use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let source = r#") -> Option { for ann in annotations { match (ann.range.0, ann.range.1) { (None, None) => continue, (Some(start), Some(end)) if start > end_index => continue, (Some(start), Some(end)) if start >= start_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 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` because of return type"), ) .annotation( Level::Error .span(26..724) .label("expected enum `std::option::Option`"), ), ); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); } annotate-snippets-0.11.4/examples/format.svg000064400000000000000000000136231046102023000172170ustar 00000000000000 error[E0308]: mismatched types --> src/format.rs:51:6 | 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, 56 | | (Some(start), Some(end)) if start >= start_index => { 57 | | let label = if let Some(ref label) = ann.label { 58 | | format!(" {}", label) 59 | | } else { 60 | | String::from("") 61 | | }; 62 | | 63 | | return Some(format!( 64 | | "{}{}{}", 65 | | " ".repeat(start - start_index), 66 | | "^".repeat(end - start), 67 | | label 68 | | )); 69 | | } 70 | | _ => continue, 71 | | } 72 | | } | |____^ expected enum `std::option::Option` | annotate-snippets-0.11.4/examples/multislice.rs000064400000000000000000000007711046102023000177260ustar 00000000000000use annotate_snippets::{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 renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); } annotate-snippets-0.11.4/examples/multislice.svg000064400000000000000000000031341046102023000200750ustar 00000000000000 error: mismatched types --> src/format.rs | 51 | Foo | ::: src/display.rs | 129 | Faa | annotate-snippets-0.11.4/release.toml000064400000000000000000000000321046102023000156730ustar 00000000000000allow-branch = ["master"] annotate-snippets-0.11.4/src/lib.rs000064400000000000000000000030611046102023000152660ustar 00000000000000//! A library for formatting of text or programming code snippets. //! //! It's primary purpose is to build an ASCII-graphical representation of the snippet //! with annotations. //! //! # Example //! //! ```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 //! Message --> Renderer --> impl Display //! ``` //! //! The input type - [Message] is a structure designed //! to align with likely output from any parser whose code snippet is to be //! annotated. //! //! 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] also handles the user-configurable formatting //! 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 //! ``` #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] #![warn(missing_debug_implementations)] pub mod renderer; mod snippet; #[doc(inline)] pub use renderer::Renderer; pub use snippet::*; annotate-snippets-0.11.4/src/renderer/display_list.rs000064400000000000000000001403651046102023000210370ustar 00000000000000//! `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}; use std::fmt::{Display, Write}; use std::ops::Range; use std::{cmp, fmt}; 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>, pub(crate) stylesheet: &'a Stylesheet, pub(crate) anonymized_line_numbers: bool, } 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, 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 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 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> { 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, inline_marks_width: usize, count_offset: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { let body_len = self .body .iter() .map(|set| set.display_lines.len()) .sum::(); 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(()) } } #[derive(Debug, PartialEq)] pub(crate) struct DisplaySet<'a> { pub(crate) display_lines: Vec>, pub(crate) margin: Margin, } impl<'a> DisplaySet<'a> { fn format_label( &self, label: &[DisplayTextFragment<'_>], stylesheet: &Stylesheet, f: &mut fmt::Formatter<'_>, ) -> 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() )?; } } } Ok(()) } fn format_annotation( &self, annotation: &Annotation<'_>, continuation: bool, in_source: bool, stylesheet: &Stylesheet, f: &mut fmt::Formatter<'_>, ) -> 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 { format_repeat_char(' ', formatted_len + 2, f)?; return self.format_label(&annotation.label, stylesheet, f); } if formatted_len == 0 { self.format_label(&annotation.label, stylesheet, f) } 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())?; 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)?; } } Ok(()) } } #[inline] fn format_raw_line( &self, line: &DisplayRawLine<'_>, lineno_width: usize, stylesheet: &Stylesheet, 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 = stylesheet.line_no(); 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) } } DisplayRawLine::Annotation { annotation, source_aligned, continuation, } => { if *source_aligned { if *continuation { format_repeat_char(' ', lineno_width + 3, f)?; } 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(' ')?; } } self.format_annotation(annotation, *continuation, false, stylesheet, f) } } } #[inline] fn format_line( &self, dl: &DisplayLine<'_>, lineno_width: usize, inline_marks_width: usize, stylesheet: &Stylesheet, anonymized_line_numbers: bool, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match dl { DisplayLine::Source { lineno, inline_marks, line, annotations, } => { 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())?; } 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())?; } 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)?; } f.write_char(' ')?; let text = normalize_whitespace(text); let line_len = text.as_bytes().len(); let mut left = self.margin.left(line_len); let right = self.margin.right(line_len); if self.margin.was_cut_left() { "...".fmt(f)?; left += 3; } // On long lines, we strip the source line, accounting for unicode. let mut taken = 0; let code: String = text .chars() .skip(left) .take_while(|ch| { // Make sure that the trimming on the right will fall within the terminal width. // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` // is. For now, just accept that sometimes the code line will be longer than // desired. let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); if taken + next > right - left { return false; } taken += next; true }) .collect(); if self.margin.was_cut_right(line_len) { code[..taken.saturating_sub(3)].fmt(f)?; "...".fmt(f)?; } else { code.fmt(f)?; } let mut left: usize = text .chars() .take(left) .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)) .sum(); if self.margin.was_cut_left() { left = left.saturating_sub(3); } for annotation in annotations { // Each annotation should be on its own line f.write_char('\n')?; // Add the line number and the line number delimiter write!(f, "{}", stylesheet.line_no.render())?; format_repeat_char(' ', lineno_width, f)?; f.write_str(" |")?; write!(f, "{}", stylesheet.line_no.render_reset())?; if !inline_marks.is_empty() || 0 < inline_marks_width { f.write_char(' ')?; self.format_inline_marks( inline_marks, inline_marks_width, stylesheet, f, )?; } self.format_source_annotation(annotation, left, stylesheet, f)?; } } else if !inline_marks.is_empty() { f.write_char(' ')?; self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, 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, stylesheet, f)?; } 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())?; } Ok(()) } } /// Inline annotation which can be used in either Raw or Source line. #[derive(Debug, PartialEq)] pub(crate) struct Annotation<'a> { pub(crate) annotation_type: DisplayAnnotationType, pub(crate) id: Option<&'a str>, pub(crate) label: Vec>, } /// 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, inline_marks: Vec, line: DisplaySourceLine<'a>, annotations: Vec>, }, /// A line indicating a folded part of the slice. Fold { inline_marks: Vec }, /// 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(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, } /// 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(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, /// A line ending a multiline annotation. MultilineEnd, } /// 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, /// 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(crate) enum DisplayAnnotationType { None, Error, Warning, Info, Note, Help, } impl From 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<'a> CursorLines<'a> { fn new(src: &str) -> CursorLines<'_> { CursorLines(src) } } #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) enum EndLine { Eof = 0, Crlf = 1, Lf = 2, } impl<'a> Iterator for CursorLines<'a> { type Item = (&'a str, EndLine); fn next(&mut self) -> Option { 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_message( message: snippet::Message<'_>, term_width: usize, anonymized_line_numbers: bool, primary: bool, ) -> Vec> { 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) }; for (idx, snippet) in snippets.into_iter().enumerate() { let snippet = fold_prefix_suffix(snippet); sets.push(format_snippet( snippet, idx == 0, !footer.is_empty(), 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> { 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, ) -> Vec> { 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, has_footer: 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, has_footer, 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: Option, b: Option) -> Option<(A, B)> { a.and_then(|a| b.map(|b| (a, b))) } fn format_header<'a>( origin: Option<&'a str>, main_range: Option, body: &[DisplayLine<'_>], is_first: bool, ) -> Option> { 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 + *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; } } } 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 = 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(body: Vec>) -> Vec> { const INNER_CONTEXT: usize = 1; const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1; let mut lines = vec![]; 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) { unhighlighed_lines.push(line); } else { 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); } _ => { 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); } } lines.push(line); } } _ => { unhighlighed_lines.push(line); } } } lines } fn format_body( snippet: snippet::Snippet<'_>, need_empty_header: bool, has_footer: 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 `{:?}` is beyond the end of buffer `{}`", bigger, 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 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); 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![], }); 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(|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::(); 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::(); 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 => { // 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 { 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::(); 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, }); } 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] { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, 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 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)) .sum::() .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); 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, }); } false } _ => true, } }); } 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 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 { 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 { for _ in 0..n { f.write_char(c)?; } 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 .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 } annotate-snippets-0.11.4/src/renderer/margin.rs000064400000000000000000000113701046102023000176050ustar 00000000000000use std::cmp::{max, min}; const ELLIPSIS_PASSING: usize = 6; const LONG_WHITESPACE: usize = 20; const LONG_WHITESPACE_PADDING: usize = 4; #[derive(Clone, Copy, Debug, PartialEq)] 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. 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. 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, } impl Margin { pub(crate) fn new( whitespace_left: usize, span_left: usize, span_right: usize, label_right: usize, term_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, term_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.term_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.term_width { // Trimming only whitespace isn't enough, let's get craftier. 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.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.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.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.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.term_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.term_width { line_len } else { min(line_len, self.computed_right) } } } annotate-snippets-0.11.4/src/renderer/mod.rs000064400000000000000000000113441046102023000171100ustar 00000000000000//! The renderer for [`Message`]s //! //! # 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")); //! //! let renderer = Renderer::styled(); //! println!("{}", renderer.render(snippet)); mod display_list; mod margin; pub(crate) mod stylesheet; use crate::snippet::Message; pub use anstyle::*; use display_list::DisplayList; use margin::Margin; use std::fmt::Display; use stylesheet::Stylesheet; pub const DEFAULT_TERM_WIDTH: usize = 140; /// A renderer for [`Message`]s #[derive(Clone, Debug)] pub struct Renderer { anonymized_line_numbers: bool, term_width: usize, stylesheet: Stylesheet, } impl Renderer { /// No terminal styling pub const fn plain() -> Self { Self { anonymized_line_numbers: false, term_width: DEFAULT_TERM_WIDTH, stylesheet: Stylesheet::plain(), } } /// Default terminal styling /// /// # Note /// When testing styled terminal output, see the [`testing-colors` feature](crate#features) pub const fn styled() -> Self { 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() }; Self { stylesheet: Stylesheet { error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD), warning: if USE_WINDOWS_COLORS { 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: BRIGHT_BLUE.effects(Effects::BOLD), emphasis: if USE_WINDOWS_COLORS { AnsiColor::BrightWhite.on_default() } else { Style::new() } .effects(Effects::BOLD), none: Style::new(), }, ..Self::plain() } } /// 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 terminal width pub const fn term_width(mut self, term_width: usize) -> Self { self.term_width = term_width; 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, msg: Message<'a>) -> impl Display + 'a { DisplayList::new( msg, &self.stylesheet, self.anonymized_line_numbers, self.term_width, ) } } annotate-snippets-0.11.4/src/renderer/stylesheet.rs000064400000000000000000000025121046102023000205170ustar 00000000000000use anstyle::Style; #[derive(Clone, Copy, Debug)] pub(crate) 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::plain() } } impl Stylesheet { pub(crate) const fn plain() -> 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 } } annotate-snippets-0.11.4/src/snippet.rs000064400000000000000000000074451046102023000162140ustar 00000000000000//! 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 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>, pub(crate) title: &'a str, pub(crate) snippets: Vec>, pub(crate) footer: Vec>, } impl<'a> Message<'a> { pub fn id(mut self, id: &'a str) -> Self { self.id = Some(id); self } pub fn snippet(mut self, slice: Snippet<'a>) -> Self { self.snippets.push(slice); self } pub fn snippets(mut self, slice: impl IntoIterator>) -> Self { self.snippets.extend(slice); self } pub fn footer(mut self, footer: Message<'a>) -> Self { self.footer.push(footer); self } pub fn footers(mut self, footer: impl IntoIterator>) -> Self { self.footer.extend(footer); self } } /// 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(crate) origin: Option<&'a str>, pub(crate) line_start: usize, pub(crate) source: &'a str, pub(crate) annotations: Vec>, pub(crate) fold: bool, } impl<'a> Snippet<'a> { pub fn source(source: &'a str) -> Self { Self { origin: None, 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 } pub fn annotation(mut self, annotation: Annotation<'a>) -> Self { self.annotations.push(annotation); self } pub fn annotations(mut self, annotation: impl IntoIterator>) -> Self { self.annotations.extend(annotation); self } /// Hide lines without [`Annotation`]s pub fn fold(mut self, fold: bool) -> Self { self.fold = fold; self } } /// An annotation for a [`Snippet`]. /// /// 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, 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 { /// Error annotations are displayed using red color and "^" character. Error, /// Warning annotations are displayed using blue color and "-" character. Warning, Info, Note, Help, } 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`] pub fn span<'a>(self, span: Range) -> Annotation<'a> { Annotation { range: span, label: None, level: self, } } } annotate-snippets-0.11.4/tests/examples.rs000064400000000000000000000017461046102023000167210ustar 00000000000000#[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.raw()); } annotate-snippets-0.11.4/tests/fixtures/deserialize.rs000064400000000000000000000100411046102023000212400ustar 00000000000000use serde::{Deserialize, Deserializer, Serialize}; 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> { #[serde(default)] pub(crate) renderer: RendererDef, #[serde(borrow)] pub(crate) message: MessageDef<'a>, } #[derive(Deserialize)] pub struct MessageDef<'a> { #[serde(with = "LevelDef")] pub level: Level, #[serde(borrow)] pub title: &'a str, #[serde(default)] #[serde(borrow)] pub id: Option<&'a str>, #[serde(default)] #[serde(borrow)] pub footer: Vec>, #[serde(deserialize_with = "deserialize_snippets")] #[serde(borrow)] pub snippets: Vec>, } impl<'a> From> for Message<'a> { fn from(val: MessageDef<'a>) -> Self { let MessageDef { level, title, id, footer, snippets, } = val; let mut message = level.title(title); if let Some(id) = id { message = message.id(id); } message = message.snippets(snippets); message = message.footers(footer.into_iter().map(Into::into)); message } } fn deserialize_snippets<'de, D>(deserializer: D) -> Result>, 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 line_start: usize, #[serde(borrow)] pub origin: Option<&'a str>, #[serde(deserialize_with = "deserialize_annotations")] #[serde(borrow)] pub annotations: Vec>, #[serde(default)] pub fold: bool, } impl<'a> From> for Snippet<'a> { fn from(val: SnippetDef<'a>) -> Self { let SnippetDef { source, line_start, origin, 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); snippet } } fn deserialize_annotations<'de, D>(deserializer: D) -> Result>, 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> { pub range: Range, #[serde(borrow)] pub label: &'a str, #[serde(with = "LevelDef")] pub level: Level, } impl<'a> From> for Annotation<'a> { fn from(val: AnnotationDef<'a>) -> Self { let AnnotationDef { range, label, level, } = val; level.span(range).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)] #[serde(remote = "Level")] enum LevelDef { Error, Warning, Info, Note, Help, } #[derive(Default, Deserialize)] pub struct RendererDef { #[serde(default)] anonymized_line_numbers: bool, #[serde(default)] term_width: Option, } impl From for Renderer { fn from(val: RendererDef) -> Self { let RendererDef { anonymized_line_numbers, term_width, } = val; Renderer::plain() .anonymized_line_numbers(anonymized_line_numbers) .term_width(term_width.unwrap_or(DEFAULT_TERM_WIDTH)) } } annotate-snippets-0.11.4/tests/fixtures/main.rs000064400000000000000000000017451046102023000176770ustar 00000000000000mod 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 name = input_path.file_name().unwrap().to_str().unwrap().to_owned(); 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> { 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 actual = renderer.render(message).to_string(); Ok(Data::from(actual).coerce_to(DataFormat::TermSvg)) } annotate-snippets-0.11.4/tests/fixtures/no-color/ann_eof.svg000064400000000000000000000015771046102023000222660ustar 00000000000000 error: expected `.`, `=` --> Cargo.toml:1:5 | 1 | asdf | ^ | annotate-snippets-0.11.4/tests/fixtures/no-color/ann_eof.toml000064400000000000000000000003141046102023000224260ustar 00000000000000[message] level = "Error" title = "expected `.`, `=`" [[message.snippets]] source = "asdf" line_start = 1 origin = "Cargo.toml" [[message.snippets.annotations]] label = "" level = "Error" range = [4, 4] annotate-snippets-0.11.4/tests/fixtures/no-color/ann_insertion.svg000064400000000000000000000016151046102023000235200ustar 00000000000000 error: expected `.`, `=` --> Cargo.toml:1:3 | 1 | asf | ^ 'd' belongs here | annotate-snippets-0.11.4/tests/fixtures/no-color/ann_insertion.toml000064400000000000000000000003331046102023000236700ustar 00000000000000[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] annotate-snippets-0.11.4/tests/fixtures/no-color/ann_multiline.svg000064400000000000000000000025411046102023000235070ustar 00000000000000 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` | annotate-snippets-0.11.4/tests/fixtures/no-color/ann_multiline.toml000064400000000000000000000007131046102023000236620ustar 00000000000000[message] level = "Error" id = "E0027" title = "pattern does not mention fields `lineno`, `content`" [[message.snippets]] source = """ if let DisplayLine::Source { ref mut inline_marks, } = body[body_idx] """ line_start = 139 origin = "src/display_list.rs" fold = false [[message.snippets.annotations]] label = "missing fields `lineno`, `content`" level = "Error" range = [31, 128] annotate-snippets-0.11.4/tests/fixtures/no-color/ann_multiline2.svg000064400000000000000000000022261046102023000235710ustar 00000000000000 error[E####]: spacing error found --> foo.txt:26:12 | 26 | This is an example | ^^^^^^^ this should not be on separate lines 27 | of an edge case of an annotation overflowing 28 | to exactly one character on next line. | annotate-snippets-0.11.4/tests/fixtures/no-color/ann_multiline2.toml000064400000000000000000000005641046102023000237500ustar 00000000000000[message] level = "Error" id = "E####" title = "spacing error found" [[message.snippets]] 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 [[message.snippets.annotations]] label = "this should not be on separate lines" level = "Error" range = [11, 19] annotate-snippets-0.11.4/tests/fixtures/no-color/ann_removed_nl.svg000064400000000000000000000015771046102023000236470ustar 00000000000000 error: expected `.`, `=` --> Cargo.toml:1:5 | 1 | asdf | ^ | annotate-snippets-0.11.4/tests/fixtures/no-color/ann_removed_nl.toml000064400000000000000000000003141046102023000240070ustar 00000000000000[message] level = "Error" title = "expected `.`, `=`" [[message.snippets]] source = "asdf" line_start = 1 origin = "Cargo.toml" [[message.snippets.annotations]] label = "" level = "Error" range = [4, 5] annotate-snippets-0.11.4/tests/fixtures/no-color/ensure-emoji-highlight-width.svg000064400000000000000000000021561046102023000263370ustar 00000000000000 error: invalid character ` ` in package name: `haha this isn't a valid name πŸ›`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters) --> <file>:7:1 | 7 | "haha this isn't a valid name πŸ›" = { package = "libc", version = "0.1" } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | annotate-snippets-0.11.4/tests/fixtures/no-color/ensure-emoji-highlight-width.toml000064400000000000000000000006401046102023000265070ustar 00000000000000[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 = "" [[message.snippets.annotations]] label = "" level = "Error" range = [0, 35] annotate-snippets-0.11.4/tests/fixtures/no-color/fold_ann_multiline.svg000064400000000000000000000030051046102023000245070ustar 00000000000000 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) { ... | 71 | | } 72 | | } | |_____^ expected enum `std::option::Option`, found () | annotate-snippets-0.11.4/tests/fixtures/no-color/fold_ann_multiline.toml000064400000000000000000000022601046102023000246650ustar 00000000000000[message] level = "Error" id = "E0308" title = "mismatched types" [[message.snippets]] source = """ ) -> Option { 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 [[message.snippets.annotations]] label = "expected `std::option::Option` because of return type" level = "Warning" range = [5, 19] [[message.snippets.annotations]] label = "expected enum `std::option::Option`, found ()" level = "Error" range = [22, 766] annotate-snippets-0.11.4/tests/fixtures/no-color/fold_bad_origin_line.svg000064400000000000000000000016201046102023000247560ustar 00000000000000 error --> path/to/error.rs:3:1 | 3 | invalid syntax | -------------- error here | annotate-snippets-0.11.4/tests/fixtures/no-color/fold_bad_origin_line.toml000064400000000000000000000003531046102023000251340ustar 00000000000000[message] level = "Error" title = "" [[message.snippets]] source = """ invalid syntax """ line_start = 1 origin = "path/to/error.rs" fold = true [[message.snippets.annotations]] label = "error here" level = "Warning" range = [2,16] annotate-snippets-0.11.4/tests/fixtures/no-color/fold_leading.svg000064400000000000000000000016721046102023000232640ustar 00000000000000 error[E0308]: invalid type: integer `20`, expected a bool --> Cargo.toml:11:13 | 11 | workspace = 20 | ^^ | annotate-snippets-0.11.4/tests/fixtures/no-color/fold_leading.toml000064400000000000000000000006131046102023000234320ustar 00000000000000[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] annotate-snippets-0.11.4/tests/fixtures/no-color/fold_trailing.svg000064400000000000000000000016621046102023000234710ustar 00000000000000 error[E0308]: invalid type: integer `20`, expected a lints table --> Cargo.toml:1:9 | 1 | lints = 20 | ^^ | annotate-snippets-0.11.4/tests/fixtures/no-color/fold_trailing.toml000064400000000000000000000006031046102023000236370ustar 00000000000000[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] annotate-snippets-0.11.4/tests/fixtures/no-color/issue_9.svg000064400000000000000000000027501046102023000222330ustar 00000000000000 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 | annotate-snippets-0.11.4/tests/fixtures/no-color/issue_9.toml000064400000000000000000000012761046102023000224110ustar 00000000000000[message] level = "Error" title = "expected one of `.`, `;`, `?`, or an operator, found `for`" [[message.snippets]] source = "let x = vec![1];" line_start = 4 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" [[message.snippets.annotations]] label = "move occurs because `x` has type `std::vec::Vec`, which does not implement the `Copy` trait" level = "Warning" range = [4, 5] [[message.snippets]] source = "let y = x;" line_start = 7 [[message.snippets.annotations]] label = "value moved here" level = "Warning" range = [8, 9] [[message.snippets]] source = "x;" line_start = 9 [[message.snippets.annotations]] label = "value used here after move" level = "Error" range = [0, 1] annotate-snippets-0.11.4/tests/fixtures/no-color/multiple_annotations.svg000064400000000000000000000034601046102023000251220ustar 00000000000000 error | 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 | } | annotate-snippets-0.11.4/tests/fixtures/no-color/multiple_annotations.toml000064400000000000000000000012331046102023000252720ustar 00000000000000[message] level = "Error" title = "" [[message.snippets]] source = """ fn add_title_line(result: &mut Vec, 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.snippets.annotations]] label = "Variable defined here" level = "Error" range = [100, 110] [[message.snippets.annotations]] label = "Referenced here" level = "Error" range = [184, 194] [[message.snippets.annotations]] label = "Referenced again here" level = "Error" range = [243, 253] annotate-snippets-0.11.4/tests/fixtures/no-color/simple.svg000064400000000000000000000023631046102023000221440ustar 00000000000000 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 | annotate-snippets-0.11.4/tests/fixtures/no-color/simple.toml000064400000000000000000000007001046102023000223110ustar 00000000000000[message] level = "Error" title = "expected one of `.`, `;`, `?`, or an operator, found `for`" [[message.snippets]] source = """ }) for line in &self.body {""" line_start = 169 origin = "src/format_color.rs" [[message.snippets.annotations]] label = "unexpected token" level = "Error" range = [20, 23] [[message.snippets.annotations]] label = "expected one of `.`, `;`, `?`, or an operator here" level = "Warning" range = [10, 11] annotate-snippets-0.11.4/tests/fixtures/no-color/strip_line.svg000064400000000000000000000017701046102023000230240ustar 00000000000000 error[E0308]: mismatched types --> $DIR/whitespace-trimming.rs:4:193 | LL | ... let _: () = 42; | ^^ expected (), found integer | annotate-snippets-0.11.4/tests/fixtures/no-color/strip_line.toml000064400000000000000000000010001046102023000231620ustar 00000000000000[message] level = "Error" id = "E0308" title = "mismatched types" [[message.snippets]] source = " let _: () = 42;" line_start = 4 origin = "$DIR/whitespace-trimming.rs" [[message.snippets.annotations]] label = "expected (), found integer" level = "Error" range = [192, 194] [renderer] color = false anonymized_line_numbers = true annotate-snippets-0.11.4/tests/fixtures/no-color/strip_line_char.svg000064400000000000000000000017711046102023000240220ustar 00000000000000 error[E0308]: mismatched types --> $DIR/whitespace-trimming.rs:4:193 | LL | ... let _: () = 42Γ± | ^^ expected (), found integer | annotate-snippets-0.11.4/tests/fixtures/no-color/strip_line_char.toml000064400000000000000000000010021046102023000241610ustar 00000000000000[message] level = "Error" id = "E0308" title = "mismatched types" [[message.snippets]] source = " let _: () = 42Γ±" line_start = 4 origin = "$DIR/whitespace-trimming.rs" [[message.snippets.annotations]] label = "expected (), found integer" level = "Error" range = [192, 194] [renderer] color = false anonymized_line_numbers = true annotate-snippets-0.11.4/tests/fixtures/no-color/strip_line_non_ws.svg000064400000000000000000000023711046102023000244050ustar 00000000000000 error[E0308]: mismatched types --> $DIR/non-whitespace-trimming.rs:4:242 | LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () ... | ^^ expected `()`, found integer | ^^ expected due to this | annotate-snippets-0.11.4/tests/fixtures/no-color/strip_line_non_ws.toml000064400000000000000000000014201046102023000245530ustar 00000000000000[message] 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 _: () = (); """ line_start = 4 origin = "$DIR/non-whitespace-trimming.rs" [[message.snippets.annotations]] label = "expected `()`, found integer" level = "Error" range = [241, 243] [[message.snippets.annotations]] label = "expected due to this" level = "Error" range = [236, 238] [renderer] anonymized_line_numbers = true annotate-snippets-0.11.4/tests/formatter.rs000064400000000000000000000415711046102023000171060ustar 00000000000000use annotate_snippets::{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("") .annotation(Level::Error.span(19..23).label("oops")) .fold(true), ); let expected = str![[r#" error: oops --> :2:8 | 2 | Second oops line | ^^^^ oops | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters() { let snippets = Level::Error.title("").snippet( Snippet::source("γ“γ‚“γ«γ‘γ―γ€δΈ–η•Œ") .origin("") .annotation(Level::Error.span(18..24).label("world")), ); let expected = str![[r#" error --> :1:7 | 1 | γ“γ‚“γ«γ‘γ―γ€δΈ–η•Œ | ^^^^ world | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters_across_lines() { let snippets = Level::Error.title("").snippet( Snippet::source("γŠγ―γ‚ˆγ†\nございます") .origin("") .annotation(Level::Error.span(6..22).label("Good morning")), ); let expected = str![[r#" error --> :1:3 | 1 | γŠγ―γ‚ˆγ† | _____^ 2 | | ございます | |______^ Good morning | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters_multiple() { let snippets = Level::Error.title("").snippet( Snippet::source("お寿司\nι£ŸγΉγŸγ„πŸ£") .origin("") .annotation(Level::Error.span(0..9).label("Sushi1")) .annotation(Level::Note.span(16..22).label("Sushi2")), ); let expected = str![[r#" error --> :1:1 | 1 | お寿司 | ^^^^^^ Sushi1 2 | ι£ŸγΉγŸγ„πŸ£ | ---- note: Sushi2 | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters_mixed() { let snippets = Level::Error.title("").snippet( Snippet::source("こんにけは、新しいWorld!") .origin("") .annotation(Level::Error.span(18..32).label("New world")), ); let expected = str![[r#" error --> :1:7 | 1 | こんにけは、新しいWorld! | ^^^^^^^^^^^ New world | "#]]; let renderer = Renderer::plain(); assert_data_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 = str![r#"error[E0001]: This is a title"#]; let renderer = Renderer::plain(); assert_data_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 = str![[r#" error | 5402 | This is line 1 5403 | This is line 2 | "#]]; let renderer = Renderer::plain(); assert_data_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 = str![[r#" 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); } #[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 = str![[r#" 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); } #[test] fn test_format_footer_title() { let input = Level::Error .title("") .footer(Level::Error.title("This __is__ a title")); let expected = str![[r#" error = error: This __is__ a title "#]]; let renderer = Renderer::plain(); assert_data_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 = str![[r#" error | 56 | This is an example 57 | of content lines | "#]]; let renderer = Renderer::plain(); assert_data_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 = str![[r#" error | 1 | tests | ----- help: Example string | "#]]; let renderer = Renderer::plain(); assert_data_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 = str![[r#" error | 1 | tests | ----- help: Example string | ----- help: Second line | "#]]; let renderer = Renderer::plain(); assert_data_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 = str![[r#" error --> file.rs | | "#]]; let renderer = Renderer::plain(); assert_data_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 = str![[r#" error | 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); } #[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("") .annotation(Level::Error.span(3..8)), // γ‚“\r\n ); let expected = str![[r#" error --> :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("") .annotation(Level::Error.span(7..8)), // \n ); let expected = str![[r#" error --> :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("") .annotation(Level::Error.span(7..11)), // \r\nに ); let expected = str![[r#" error --> :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); }