annotate-snippets-0.9.1/.cargo_vcs_info.json0000644000000001120000000000100145110ustar { "git": { "sha1": "77bd1c8e03466ff290c15dc025eac13088c244ee" } } annotate-snippets-0.9.1/.gitignore000064400000000000000000000000360072674642500153260ustar 00000000000000/target **/*.rs.bk Cargo.lock annotate-snippets-0.9.1/.travis.yml000064400000000000000000000010410072674642500154440ustar 00000000000000language: rust sudo: required dist: trusty addons: apt: packages: - libssl-dev cache: cargo rust: - stable - beta - nightly matrix: allow_failures: - rust: nightly before_cache: | if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f fi script: - cargo clean - cargo build - cargo test after_success: | if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then cargo tarpaulin --ciserver travis-ci --coveralls $TRAVIS_JOB_ID fi annotate-snippets-0.9.1/CHANGELOG.md000064400000000000000000000021420072674642500151470ustar 00000000000000# Changelog ## Unreleased - … ## annotate-snippets 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) ## annotate-snippets 0.9.0 (June 28, 2020) - Add strip code to the left and right of long lines. (#36) ## annotate-snippets 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) ## annotate-snippets 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. ## annotate-snippets 0.6.1 (July 23, 2019) - Fix too many anonymized line numbers (#5) ## annotate-snippets 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) annotate-snippets-0.9.1/Cargo.lock0000644000000371550000000000100125050ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "annotate-snippets" version = "0.9.1" dependencies = [ "criterion", "difference", "glob", "serde", "toml", "unicode-width", "yansi-term", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bstr" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" dependencies = [ "lazy_static", "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "cast" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" dependencies = [ "rustc_version", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "clap" version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" dependencies = [ "bitflags", "textwrap", "unicode-width", ] [[package]] name = "criterion" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc755679c12bda8e5523a71e4d654b6bf2e14bd838dfc48cde6559a05caf7d1" dependencies = [ "atty", "cast", "clap", "criterion-plot", "csv", "itertools", "lazy_static", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01e15e0ea58e8234f96146b1f91fa9d0e4dd7a38da93ff7a75d42c0b9d3a545" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" dependencies = [ "crossbeam-epoch", "crossbeam-utils", "maybe-uninit", ] [[package]] name = "crossbeam-epoch" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "lazy_static", "maybe-uninit", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-queue" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg", "cfg-if", "lazy_static", ] [[package]] name = "csv" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" dependencies = [ "bstr", "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ "memchr", ] [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "either" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" [[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "hermit-abi" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" dependencies = [ "libc", ] [[package]] name = "itertools" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "js-sys" version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" 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.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" [[package]] name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" dependencies = [ "cfg-if", ] [[package]] name = "maybe-uninit" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "memoffset" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" dependencies = [ "autocfg", ] [[package]] name = "num-traits" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "oorandom" version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405" [[package]] name = "plotters" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e3bb8da247d27ae212529352020f3e5ee16e83c0c258061d27b08ab92675eeb" dependencies = [ "js-sys", "num-traits", "wasm-bindgen", "web-sys", ] [[package]] name = "proc-macro2" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" dependencies = [ "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" dependencies = [ "crossbeam-deque", "crossbeam-queue", "crossbeam-utils", "lazy_static", "num_cpus", ] [[package]] name = "regex" version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" dependencies = [ "regex-syntax", ] [[package]] name = "regex-automata" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ "byteorder", ] [[package]] name = "regex-syntax" version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ "semver", ] [[package]] name = "ryu" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ "semver-parser", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "syn" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "tinytemplate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a" dependencies = [ "serde", "serde_json", ] [[package]] name = "toml" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" dependencies = [ "serde", ] [[package]] name = "unicode-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "walkdir" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ "same-file", "winapi", "winapi-util", ] [[package]] name = "wasm-bindgen" version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" [[package]] name = "web-sys" version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "yansi-term" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e44ab40470a13c8746f6a9b5e042ba56dbd55e5a48921e131779b4575935bc7" dependencies = [ "winapi", ] annotate-snippets-0.9.1/Cargo.toml0000644000000030710000000000100125160ustar # 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 believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "annotate-snippets" version = "0.9.1" authors = ["Zibi Braniecki "] 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" [[bench]] name = "simple" harness = false [dependencies.unicode-width] version = "0.1" [dependencies.yansi-term] version = "0.1" optional = true [dev-dependencies.criterion] version = "0.3" [dev-dependencies.difference] version = "2.0" [dev-dependencies.glob] version = "0.3" [dev-dependencies.serde] version = "1.0" features = ["derive"] [dev-dependencies.toml] version = "0.5" [dev-dependencies.yansi-term] version = "0.1" [features] color = ["yansi-term"] default = [] [badges.coveralls] branch = "master" repository = "rust-lang/annotate-snippets-rs" service = "github" [badges.maintenance] status = "actively-developed" [badges.travis-ci] branch = "master" repository = "rust-lang/annotate-snippets-rs" annotate-snippets-0.9.1/Cargo.toml.orig000064400000000000000000000016330072674642500162310ustar 00000000000000[package] name = "annotate-snippets" version = "0.9.1" edition = "2018" 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"] [badges] travis-ci = { repository = "rust-lang/annotate-snippets-rs", branch = "master" } coveralls = { repository = "rust-lang/annotate-snippets-rs", branch = "master", service = "github" } maintenance = { status = "actively-developed" } [dependencies] unicode-width = "0.1" yansi-term = { version = "0.1", optional = true } [dev-dependencies] glob = "0.3" toml = "0.5" serde = { version = "1.0", features = ["derive"] } difference = "2.0" yansi-term = "0.1" criterion = "0.3" [[bench]] name = "simple" harness = false [features] default = [] color = ["yansi-term"] annotate-snippets-0.9.1/LICENSE-APACHE000064400000000000000000000261350072674642500152720ustar 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.9.1/LICENSE-MIT000064400000000000000000000020270072674642500147740ustar 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.9.1/README.md000064400000000000000000000054570072674642500146310ustar 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) [![Build Status](https://travis-ci.com/rust-lang/annotate-snippets-rs.svg?branch=master)](https://travis-ci.com/rust-lang/annotate-snippets-rs) [![Coverage Status](https://coveralls.io/repos/github/rust-lang/annotate-snippets-rs/badge.svg?branch=master)](https://coveralls.io/github/rust-lang/annotate-snippets-rs?branch=master) The library helps visualize meta information annotating source code slices. It takes a data structure called `Snippet` on the input and produces a `String` which may look like this: ```text error[E0308]: mismatched types --> src/format.rs:52:1 | 51 | ) -> Option { | -------------- expected `Option` because of return type 52 | / for ann in annotations { 53 | | match (ann.range.0, ann.range.1) { 54 | | (None, None) => continue, 55 | | (Some(start), Some(end)) if start > end_index => continue, ... | 71 | | } 72 | | } | |_____^ expected enum `std::option::Option`, found () ``` [Documentation][] [Documentation]: https://docs.rs/annotate-snippets/ Usage ----- ```rust use annotate_snippets::{ display_list::{DisplayList, FormatOptions}, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; fn main() { let snippet = Snippet { title: Some(Annotation { label: Some("expected type, found `22`"), id: None, annotation_type: AnnotationType::Error, }), footer: vec![], slices: vec![Slice { source: r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , range: <22, 25>,"#, line_start: 26, origin: Some("examples/footer.rs"), fold: true, annotations: vec![ SourceAnnotation { label: "", annotation_type: AnnotationType::Error, range: (187, 189), }, SourceAnnotation { label: "while parsing this struct", annotation_type: AnnotationType::Info, range: (34, 50), }, ], }], opt: FormatOptions { color: true, ..Default::default() }, }; let dl = DisplayList::from(snippet); println!("{}", dl); } ``` Local Development ----------------- cargo build cargo test When submitting a PR please use [`cargo fmt`][] (nightly). [`cargo fmt`]: https://github.com/rust-lang/rustfmt annotate-snippets-0.9.1/benches/simple.rs000064400000000000000000000043440072674642500166120ustar 00000000000000#![allow(clippy::unit_arg)] #[macro_use] extern crate criterion; use criterion::{black_box, Criterion}; use annotate_snippets::{ display_list::{DisplayList, FormatOptions}, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; fn create_snippet() { let snippet = Snippet { slices: vec![Slice { 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, } }"#, line_start: 51, origin: Some("src/format.rs"), fold: false, annotations: vec![ SourceAnnotation { label: "expected `Option` because of return type", annotation_type: AnnotationType::Warning, range: (5, 19), }, SourceAnnotation { label: "expected enum `std::option::Option`", annotation_type: AnnotationType::Error, range: (26, 724), }, ], }], title: Some(Annotation { label: Some("mismatched types"), id: Some("E0308"), annotation_type: AnnotationType::Error, }), footer: vec![], opt: FormatOptions { color: true, ..Default::default() }, }; let dl = DisplayList::from(snippet); let _result = dl.to_string(); } pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("format", |b| b.iter(|| black_box(create_snippet()))); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); annotate-snippets-0.9.1/examples/expected_type.rs000064400000000000000000000025310072674642500203660ustar 00000000000000use annotate_snippets::{ display_list::{DisplayList, FormatOptions}, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; fn main() { let snippet = Snippet { title: Some(Annotation { label: Some("expected type, found `22`"), id: None, annotation_type: AnnotationType::Error, }), footer: vec![], slices: vec![Slice { source: r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , range: <22, 25>,"#, line_start: 26, origin: Some("examples/footer.rs"), fold: true, annotations: vec![ SourceAnnotation { label: "", annotation_type: AnnotationType::Error, range: (193, 195), }, SourceAnnotation { label: "while parsing this struct", annotation_type: AnnotationType::Info, range: (34, 50), }, ], }], opt: FormatOptions { color: true, ..Default::default() }, }; let dl = DisplayList::from(snippet); println!("{}", dl); } annotate-snippets-0.9.1/examples/footer.rs000064400000000000000000000023270072674642500170250ustar 00000000000000use annotate_snippets::{ display_list::{DisplayList, FormatOptions}, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; fn main() { let snippet = Snippet { title: Some(Annotation { label: Some("mismatched types"), id: Some("E0308"), annotation_type: AnnotationType::Error, }), footer: vec![Annotation { label: Some( "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", ), id: None, annotation_type: AnnotationType::Note, }], slices: vec![Slice { source: " slices: vec![\"A\",", line_start: 13, origin: Some("src/multislice.rs"), fold: false, annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference", range: (21, 24), annotation_type: AnnotationType::Error, }], }], opt: FormatOptions { color: true, ..Default::default() }, }; let dl = DisplayList::from(snippet); println!("{}", dl); } annotate-snippets-0.9.1/examples/format.rs000064400000000000000000000036340072674642500170210ustar 00000000000000use annotate_snippets::{ display_list::{DisplayList, FormatOptions}, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; fn main() { let snippet = Snippet { slices: vec![Slice { 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, } }"#, line_start: 51, origin: Some("src/format.rs"), fold: false, annotations: vec![ SourceAnnotation { label: "expected `Option` because of return type", annotation_type: AnnotationType::Warning, range: (5, 19), }, SourceAnnotation { label: "expected enum `std::option::Option`", annotation_type: AnnotationType::Error, range: (26, 724), }, ], }], title: Some(Annotation { label: Some("mismatched types"), id: Some("E0308"), annotation_type: AnnotationType::Error, }), footer: vec![], opt: FormatOptions { color: true, ..Default::default() }, }; let dl = DisplayList::from(snippet); println!("{}", dl); } annotate-snippets-0.9.1/examples/multislice.rs000064400000000000000000000017420072674642500177010ustar 00000000000000use annotate_snippets::{ display_list::{DisplayList, FormatOptions}, snippet::{Annotation, AnnotationType, Slice, Snippet}, }; fn main() { let snippet = Snippet { title: Some(Annotation { label: Some("mismatched types"), id: None, annotation_type: AnnotationType::Error, }), footer: vec![], slices: vec![ Slice { source: "Foo", line_start: 51, origin: Some("src/format.rs"), fold: false, annotations: vec![], }, Slice { source: "Faa", line_start: 129, origin: Some("src/display.rs"), fold: false, annotations: vec![], }, ], opt: FormatOptions { color: true, ..Default::default() }, }; let dl = DisplayList::from(snippet); println!("{}", dl); } annotate-snippets-0.9.1/src/display_list/from_snippet.rs000064400000000000000000000504070072674642500217070ustar 00000000000000//! Trait for converting `Snippet` to `DisplayList`. use super::*; use crate::{formatter::get_term_style, snippet}; struct CursorLines<'a>(&'a str); impl<'a> CursorLines<'a> { fn new(src: &str) -> CursorLines<'_> { CursorLines(src) } } enum EndLine { EOF = 0, CRLF = 1, LF = 2, } impl<'a> Iterator for CursorLines<'a> { type Item = (&'a str, EndLine); fn next(&mut self) -> Option { if self.0.is_empty() { None } else { self.0 .find('\n') .map(|x| { let ret = if 0 < x { if self.0.as_bytes()[x - 1] == b'\r' { (&self.0[..x - 1], EndLine::LF) } else { (&self.0[..x], EndLine::CRLF) } } else { ("", EndLine::CRLF) }; self.0 = &self.0[x + 1..]; ret }) .or_else(|| { let ret = Some((self.0, EndLine::EOF)); self.0 = ""; ret }) } } } fn format_label( label: Option<&str>, style: Option, ) -> Vec> { let mut result = vec![]; if let Some(label) = label { for (idx, element) in label.split("__").enumerate() { let element_style = match style { Some(s) => s, None => { if idx % 2 == 0 { DisplayTextStyle::Regular } else { DisplayTextStyle::Emphasis } } }; result.push(DisplayTextFragment { content: element, style: element_style, }); } } result } fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> { let label = annotation.label.unwrap_or_default(); DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::from(annotation.annotation_type), id: annotation.id, label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)), }, source_aligned: false, continuation: false, }) } fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec> { let mut result = vec![]; let label = annotation.label.unwrap_or_default(); for (i, line) in label.lines().enumerate() { result.push(DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::from(annotation.annotation_type), id: None, label: format_label(Some(line), None), }, source_aligned: true, continuation: i != 0, })); } result } fn format_slice( slice: snippet::Slice<'_>, is_first: bool, has_footer: bool, margin: Option, ) -> Vec> { let main_range = slice.annotations.get(0).map(|x| x.range.0); let origin = slice.origin; let line_start = slice.line_start; let need_empty_header = origin.is_some() || is_first; let mut body = format_body(slice, need_empty_header, has_footer, margin); let header = format_header(origin, main_range, line_start, &body, is_first); let mut result = vec![]; if let Some(header) = header { result.push(header); } result.append(&mut body); result } #[inline] // TODO: option_zip fn zip_opt(a: 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, mut row: usize, 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; for item in body { if let DisplayLine::Source { line: DisplaySourceLine::Content { range, .. }, .. } = item { if main_range >= range.0 && main_range <= range.1 { col = main_range - range.0 + 1; break; } row += 1; } } return Some(DisplayLine::Raw(DisplayRawLine::Origin { path, pos: Some((row, col)), header_type: display_header, })); } if let Some(path) = origin { return Some(DisplayLine::Raw(DisplayRawLine::Origin { path, pos: None, header_type: display_header, })); } None } fn fold_body(mut body: Vec>) -> Vec> { enum Line { Fold(usize), Source(usize), } let mut lines = vec![]; let mut no_annotation_lines_counter = 0; for (idx, line) in body.iter().enumerate() { match line { DisplayLine::Source { line: DisplaySourceLine::Annotation { .. }, .. } => { let fold_start = idx - no_annotation_lines_counter; if no_annotation_lines_counter > 2 { let fold_end = idx; let pre_len = if no_annotation_lines_counter > 8 { 4 } else { 0 }; let post_len = if no_annotation_lines_counter > 8 { 2 } else { 1 }; for (i, _) in body .iter() .enumerate() .take(fold_start + pre_len) .skip(fold_start) { lines.push(Line::Source(i)); } lines.push(Line::Fold(idx)); for (i, _) in body .iter() .enumerate() .take(fold_end) .skip(fold_end - post_len) { lines.push(Line::Source(i)); } } else { for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) { lines.push(Line::Source(i)); } } no_annotation_lines_counter = 0; } DisplayLine::Source { .. } => { no_annotation_lines_counter += 1; continue; } _ => { no_annotation_lines_counter += 1; } } lines.push(Line::Source(idx)); } let mut new_body = vec![]; let mut removed = 0; for line in lines { match line { Line::Source(i) => { new_body.push(body.remove(i - removed)); removed += 1; } Line::Fold(i) => { if let DisplayLine::Source { line: DisplaySourceLine::Annotation { .. }, ref inline_marks, .. } = body.get(i - removed).unwrap() { new_body.push(DisplayLine::Fold { inline_marks: inline_marks.clone(), }) } else { unreachable!() } } } } new_body } fn format_body( slice: snippet::Slice<'_>, need_empty_header: bool, has_footer: bool, margin: Option, ) -> Vec> { let source_len = slice.source.chars().count(); if let Some(bigger) = slice.annotations.iter().find_map(|x| { if source_len < x.range.1 { Some(x.range) } else { None } }) { panic!( "SourceAnnotation range `{:?}` is bigger than source length `{}`", bigger, source_len ) } let mut body = vec![]; let mut current_line = slice.line_start; let mut current_index = 0; let mut line_info = vec![]; struct LineInfo { line_start_index: usize, line_end_index: usize, // How many spaces each character in the line take up when displayed char_widths: Vec, } for (line, end_line) in CursorLines::new(slice.source) { let line_length = line.chars().count(); let line_range = (current_index, current_index + line_length); let char_widths = line .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .chain(std::iter::once(1)) // treat the end of line as signle-width .collect::>(); body.push(DisplayLine::Source { lineno: Some(current_line), inline_marks: vec![], line: DisplaySourceLine::Content { text: line, range: line_range, }, }); line_info.push(LineInfo { line_start_index: line_range.0, line_end_index: line_range.1, char_widths, }); current_line += 1; current_index += line_length + end_line as usize; } let mut annotation_line_count = 0; let mut annotations = slice.annotations; for ( idx, LineInfo { line_start_index, line_end_index, char_widths, }, ) in line_info.into_iter().enumerate() { let margin_left = margin .map(|m| m.left(line_end_index - line_start_index)) .unwrap_or_default(); // It would be nice to use filter_drain here once it's stable. annotations = annotations .into_iter() .filter(|annotation| { let body_idx = idx + annotation_line_count; let annotation_type = match annotation.annotation_type { snippet::AnnotationType::Error => DisplayAnnotationType::None, snippet::AnnotationType::Warning => DisplayAnnotationType::None, _ => DisplayAnnotationType::from(annotation.annotation_type), }; match annotation.range { (start, _) if start > line_end_index => true, (start, end) if start >= line_start_index && end <= line_end_index || start == line_end_index && end - start <= 1 => { let annotation_start_col = char_widths .iter() .take(start - line_start_index) .sum::() - margin_left; let annotation_end_col = char_widths .iter() .take(end - line_start_index) .sum::() - margin_left; let range = (annotation_start_col, annotation_end_col); body.insert( body_idx + 1, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { annotation: Annotation { annotation_type, id: None, label: format_label(Some(annotation.label), None), }, range, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), annotation_part: DisplayAnnotationPart::Standalone, }, }, ); annotation_line_count += 1; false } (start, end) if start >= line_start_index && start <= line_end_index && end > line_end_index => { if start - line_start_index == 0 { if let DisplayLine::Source { ref mut inline_marks, .. } = body[body_idx] { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationStart, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), }); } } else { let annotation_start_col = char_widths .iter() .take(start - line_start_index) .sum::(); let range = (annotation_start_col, annotation_start_col + 1); body.insert( body_idx + 1, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::None, id: None, label: vec![], }, range, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), annotation_part: DisplayAnnotationPart::MultilineStart, }, }, ); annotation_line_count += 1; } true } (start, end) if start < line_start_index && end > line_end_index => { if let DisplayLine::Source { ref mut inline_marks, .. } = body[body_idx] { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), }); } true } (start, end) if start < line_start_index && end >= line_start_index && end <= line_end_index => { if let DisplayLine::Source { ref mut inline_marks, .. } = body[body_idx] { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), }); } let end_mark = char_widths .iter() .take(end - line_start_index) .sum::() .saturating_sub(1); let range = (end_mark - margin_left, (end_mark + 1) - margin_left); body.insert( body_idx + 1, DisplayLine::Source { lineno: None, inline_marks: vec![DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), }], line: DisplaySourceLine::Annotation { annotation: Annotation { annotation_type, id: None, label: format_label(Some(annotation.label), None), }, range, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), annotation_part: DisplayAnnotationPart::MultilineEnd, }, }, ); annotation_line_count += 1; false } _ => true, } }) .collect(); } if slice.fold { body = fold_body(body); } if need_empty_header { body.insert( 0, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, }, ); } if has_footer { body.push(DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, }); } else if let Some(DisplayLine::Source { .. }) = body.last() { body.push(DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, }); } body } impl<'a> From> for DisplayList<'a> { fn from( snippet::Snippet { title, footer, slices, opt, }: snippet::Snippet<'a>, ) -> DisplayList<'a> { let mut body = vec![]; if let Some(annotation) = title { body.push(format_title(annotation)); } for (idx, slice) in slices.into_iter().enumerate() { body.append(&mut format_slice( slice, idx == 0, !footer.is_empty(), opt.margin, )); } for annotation in footer { body.append(&mut format_annotation(annotation)); } let FormatOptions { color, anonymized_line_numbers, margin, } = opt; Self { body, stylesheet: get_term_style(color), anonymized_line_numbers, margin, } } } impl From for DisplayAnnotationType { fn from(at: snippet::AnnotationType) -> Self { match at { snippet::AnnotationType::Error => DisplayAnnotationType::Error, snippet::AnnotationType::Warning => DisplayAnnotationType::Warning, snippet::AnnotationType::Info => DisplayAnnotationType::Info, snippet::AnnotationType::Note => DisplayAnnotationType::Note, snippet::AnnotationType::Help => DisplayAnnotationType::Help, } } } annotate-snippets-0.9.1/src/display_list/mod.rs000064400000000000000000000024220072674642500177530ustar 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: mod from_snippet; mod structs; pub use self::structs::*; annotate-snippets-0.9.1/src/display_list/structs.rs000064400000000000000000000243050072674642500207070ustar 00000000000000use std::cmp::{max, min}; use std::fmt; use crate::formatter::{get_term_style, style::Stylesheet}; /// List of lines to be displayed. pub struct DisplayList<'a> { pub body: Vec>, pub stylesheet: Box, pub anonymized_line_numbers: bool, pub margin: Option, } impl<'a> From>> for DisplayList<'a> { fn from(body: Vec>) -> DisplayList<'a> { Self { body, anonymized_line_numbers: false, stylesheet: get_term_style(false), margin: None, } } } impl<'a> PartialEq for DisplayList<'a> { fn eq(&self, other: &Self) -> bool { self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers } } impl<'a> fmt::Debug for DisplayList<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DisplayList") .field("body", &self.body) .field("anonymized_line_numbers", &self.anonymized_line_numbers) .finish() } } #[derive(Debug, Default, Copy, Clone)] pub struct FormatOptions { pub color: bool, pub anonymized_line_numbers: bool, pub margin: Option, } #[derive(Clone, Copy, Debug)] pub struct Margin { /// The available whitespace in the left that can be consumed when centering. whitespace_left: usize, /// The column of the beginning of left-most span. span_left: usize, /// The column of the end of right-most span. span_right: usize, /// The beginning of the line to be displayed. computed_left: usize, /// The end of the line to be displayed. computed_right: usize, /// The current width of the terminal. 140 by default and in tests. column_width: usize, /// The end column of a span label, including the span. Doesn't account for labels not in the /// same line as the span. label_right: usize, } impl Margin { pub fn new( whitespace_left: usize, span_left: usize, span_right: usize, label_right: usize, column_width: usize, max_line_len: usize, ) -> Self { // The 6 is padding to give a bit of room for `...` when displaying: // ``` // error: message // --> file.rs:16:58 // | // 16 | ... fn foo(self) -> Self::Bar { // | ^^^^^^^^^ // ``` let mut m = Margin { whitespace_left: whitespace_left.saturating_sub(6), span_left: span_left.saturating_sub(6), span_right: span_right + 6, computed_left: 0, computed_right: 0, column_width, label_right: label_right + 6, }; m.compute(max_line_len); m } pub(crate) fn was_cut_left(&self) -> bool { self.computed_left > 0 } pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { let right = if self.computed_right == self.span_right || self.computed_right == self.label_right { // Account for the "..." padding given above. Otherwise we end up with code lines that // do fit but end in "..." as if they were trimmed. self.computed_right - 6 } else { self.computed_right }; right < line_len && self.computed_left + self.column_width < line_len } fn compute(&mut self, max_line_len: usize) { // When there's a lot of whitespace (>20), we want to trim it as it is useless. self.computed_left = if self.whitespace_left > 20 { self.whitespace_left - 16 // We want some padding. } else { 0 }; // We want to show as much as possible, max_line_len is the right-most boundary for the // relevant code. self.computed_right = max(max_line_len, self.computed_left); if self.computed_right - self.computed_left > self.column_width { // Trimming only whitespace isn't enough, let's get craftier. if self.label_right - self.whitespace_left <= self.column_width { // Attempt to fit the code window only trimming whitespace. self.computed_left = self.whitespace_left; self.computed_right = self.computed_left + self.column_width; } else if self.label_right - self.span_left <= self.column_width { // Attempt to fit the code window considering only the spans and labels. let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; self.computed_left = self.span_left.saturating_sub(padding_left); self.computed_right = self.computed_left + self.column_width; } else if self.span_right - self.span_left <= self.column_width { // Attempt to fit the code window considering the spans and labels plus padding. let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; self.computed_left = self.span_left.saturating_sub(padding_left); self.computed_right = self.computed_left + self.column_width; } else { // Mostly give up but still don't show the full line. self.computed_left = self.span_left; self.computed_right = self.span_right; } } } pub(crate) fn left(&self, line_len: usize) -> usize { min(self.computed_left, line_len) } pub(crate) fn right(&self, line_len: usize) -> usize { if line_len.saturating_sub(self.computed_left) <= self.column_width { line_len } else { min(line_len, self.computed_right) } } } /// Inline annotation which can be used in either Raw or Source line. #[derive(Debug, PartialEq)] pub struct Annotation<'a> { pub annotation_type: DisplayAnnotationType, pub id: Option<&'a str>, pub label: Vec>, } /// A single line used in `DisplayList`. #[derive(Debug, PartialEq)] pub enum DisplayLine<'a> { /// A line with `lineno` portion of the slice. Source { lineno: Option, inline_marks: Vec, line: DisplaySourceLine<'a>, }, /// 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 enum DisplaySourceLine<'a> { /// A line with the content of the Slice. Content { text: &'a str, range: (usize, usize), // meta information for annotation placement. }, /// An annotation line which is displayed in context of the slice. Annotation { annotation: Annotation<'a>, range: (usize, usize), annotation_type: DisplayAnnotationType, annotation_part: DisplayAnnotationPart, }, /// An empty source line. Empty, } /// Raw line - a line which does not have the `lineno` part and is not considered /// a part of the snippet. #[derive(Debug, PartialEq)] pub enum DisplayRawLine<'a> { /// A line which provides information about the location of the given /// slice in the project structure. Origin { path: &'a str, pos: Option<(usize, usize)>, header_type: DisplayHeaderType, }, /// An annotation line which is not part of any snippet. Annotation { annotation: Annotation<'a>, /// If set to `true`, the annotation will be aligned to the /// lineno delimiter of the snippet. source_aligned: bool, /// If set to `true`, only the label of the `Annotation` will be /// displayed. It allows for a multiline annotation to be aligned /// without displaing the meta information (`type` and `id`) to be /// displayed on each line. continuation: bool, }, } /// An inline text fragment which any label is composed of. #[derive(Debug, PartialEq)] pub struct DisplayTextFragment<'a> { pub content: &'a str, pub style: DisplayTextStyle, } /// A style for the `DisplayTextFragment` which can be visually formatted. /// /// This information may be used to emphasis parts of the label. #[derive(Debug, Clone, Copy, PartialEq)] pub enum DisplayTextStyle { Regular, Emphasis, } /// An indicator of what part of the annotation a given `Annotation` is. #[derive(Debug, Clone, PartialEq)] pub enum DisplayAnnotationPart { /// A standalone, single-line annotation. Standalone, /// A continuation of a multi-line label of an annotation. LabelContinuation, /// A consequitive annotation in case multiple annotations annotate a single line. Consequitive, /// A line starting a multiline annotation. MultilineStart, /// A line ending a multiline annotation. MultilineEnd, } /// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. #[derive(Debug, Clone, PartialEq)] pub struct DisplayMark { pub mark_type: DisplayMarkType, pub annotation_type: DisplayAnnotationType, } /// A type of the `DisplayMark`. #[derive(Debug, Clone, PartialEq)] pub enum DisplayMarkType { /// A mark indicating a multiline annotation going through the current line. AnnotationThrough, /// A mark indicating a multiline annotation starting on the given line. AnnotationStart, } /// A type of the `Annotation` which may impact the sigils, style or text displayed. /// /// There are several ways to uses this information when formatting the `DisplayList`: /// /// * An annotation may display the name of the type like `error` or `info`. /// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`. /// * `ColorStylesheet` may use different colors for different annotations. #[derive(Debug, Clone, PartialEq)] pub enum DisplayAnnotationType { None, Error, Warning, Info, Note, Help, } /// Information whether the header is the initial one or a consequitive one /// for multi-slice cases. // TODO: private #[derive(Debug, Clone, PartialEq)] pub enum DisplayHeaderType { /// Initial header is the first header in the snippet. Initial, /// Continuation marks all headers of following slices in the snippet. Continuation, } annotate-snippets-0.9.1/src/formatter/mod.rs000064400000000000000000000407630072674642500172700ustar 00000000000000use std::{ cmp, fmt::{self, Display, Write}, iter::once, }; pub mod style; use self::style::{Style, StyleClass, Stylesheet}; #[cfg(feature = "color")] use crate::stylesheets::color::AnsiTermStylesheet; use crate::{display_list::*, stylesheets::no_color::NoColorStylesheet}; fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result { for _ in 0..n { f.write_char(c)?; } Ok(()) } #[inline] fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { annotation .label .iter() .all(|fragment| fragment.content.is_empty()) } #[cfg(feature = "color")] #[inline] pub fn get_term_style(color: bool) -> Box { if color { Box::new(AnsiTermStylesheet) } else { Box::new(NoColorStylesheet) } } #[cfg(not(feature = "color"))] #[inline] pub fn get_term_style(_color: bool) -> Box { Box::new(NoColorStylesheet) } impl<'a> fmt::Display for DisplayList<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let lineno_width = self.body.iter().fold(0, |max, line| match line { DisplayLine::Source { lineno: Some(lineno), .. } => { // The largest line is the largest width. cmp::max(*lineno, max) } _ => max, }); let lineno_width = if lineno_width == 0 { lineno_width } else if self.anonymized_line_numbers { Self::ANONYMIZED_LINE_NUM.len() } else { ((lineno_width as f64).log10().floor() as usize) + 1 }; let inline_marks_width = self.body.iter().fold(0, |max, line| match line { DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), _ => max, }); for (i, line) in self.body.iter().enumerate() { self.format_line(line, lineno_width, inline_marks_width, f)?; if i + 1 < self.body.len() { f.write_char('\n')?; } } Ok(()) } } impl<'a> DisplayList<'a> { const ANONYMIZED_LINE_NUM: &'static str = "LL"; const ERROR_TXT: &'static str = "error"; const HELP_TXT: &'static str = "help"; const INFO_TXT: &'static str = "info"; const NOTE_TXT: &'static str = "note"; const WARNING_TXT: &'static str = "warning"; #[inline] fn format_annotation_type( annotation_type: &DisplayAnnotationType, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match annotation_type { DisplayAnnotationType::Error => f.write_str(Self::ERROR_TXT), DisplayAnnotationType::Help => f.write_str(Self::HELP_TXT), DisplayAnnotationType::Info => f.write_str(Self::INFO_TXT), DisplayAnnotationType::Note => f.write_str(Self::NOTE_TXT), DisplayAnnotationType::Warning => f.write_str(Self::WARNING_TXT), DisplayAnnotationType::None => Ok(()), } } fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize { match annotation_type { DisplayAnnotationType::Error => Self::ERROR_TXT.len(), DisplayAnnotationType::Help => Self::HELP_TXT.len(), DisplayAnnotationType::Info => Self::INFO_TXT.len(), DisplayAnnotationType::Note => Self::NOTE_TXT.len(), DisplayAnnotationType::Warning => Self::WARNING_TXT.len(), DisplayAnnotationType::None => 0, } } fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box { self.stylesheet.get_style(match annotation_type { DisplayAnnotationType::Error => StyleClass::Error, DisplayAnnotationType::Warning => StyleClass::Warning, DisplayAnnotationType::Info => StyleClass::Info, DisplayAnnotationType::Note => StyleClass::Note, DisplayAnnotationType::Help => StyleClass::Help, DisplayAnnotationType::None => StyleClass::None, }) } fn format_label( &self, label: &[DisplayTextFragment<'_>], f: &mut fmt::Formatter<'_>, ) -> fmt::Result { let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); for fragment in label { match fragment.style { DisplayTextStyle::Regular => fragment.content.fmt(f)?, DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?, } } Ok(()) } fn format_annotation( &self, annotation: &Annotation<'_>, continuation: bool, in_source: bool, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { let color = self.get_annotation_style(&annotation.annotation_type); let formatted_len = if let Some(id) = &annotation.id { 2 + id.len() + Self::annotation_type_len(&annotation.annotation_type) } else { Self::annotation_type_len(&annotation.annotation_type) }; if continuation { format_repeat_char(' ', formatted_len + 2, f)?; return self.format_label(&annotation.label, f); } if formatted_len == 0 { self.format_label(&annotation.label, f) } else { color.paint_fn( Box::new(|f| { Self::format_annotation_type(&annotation.annotation_type, f)?; if let Some(id) = &annotation.id { f.write_char('[')?; f.write_str(id)?; f.write_char(']')?; } Ok(()) }), f, )?; if !is_annotation_empty(annotation) { if in_source { color.paint_fn( Box::new(|f| { f.write_str(": ")?; self.format_label(&annotation.label, f) }), f, )?; } else { f.write_str(": ")?; self.format_label(&annotation.label, f)?; } } Ok(()) } } #[inline] fn format_source_line( &self, line: &DisplaySourceLine<'_>, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match line { DisplaySourceLine::Empty => Ok(()), DisplaySourceLine::Content { text, .. } => { f.write_char(' ')?; if let Some(margin) = self.margin { let line_len = text.chars().count(); let mut left = margin.left(line_len); let right = margin.right(line_len); if margin.was_cut_left() { // We have stripped some code/whitespace from the beginning, make it clear. "...".fmt(f)?; left += 3; } // On long lines, we strip the source line, accounting for unicode. let mut taken = 0; let cut_right = if margin.was_cut_right(line_len) { taken += 3; true } else { false }; // Specifies that it will end on the next character, so it will return // until the next one to the final condition. let mut ended = false; let range = text .char_indices() .skip(left) // Complete char iterator with final character .chain(once((text.len(), '\0'))) // Take until the next one to the final condition .take_while(|(_, ch)| { // Fast return to iterate over final byte position if ended { return false; } // Make sure that the trimming on the right will fall within the terminal width. // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. // For now, just accept that sometimes the code line will be longer than desired. taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); if taken > right - left { ended = true; } true }) // Reduce to start and end byte position .fold((None, 0), |acc, (i, _)| { if acc.0.is_some() { (acc.0, i) } else { (Some(i), i) } }); // Format text with margins text[range.0.expect("One character at line")..range.1].fmt(f)?; if cut_right { // We have stripped some code after the right-most span end, make it clear we did so. "...".fmt(f)?; } Ok(()) } else { text.fmt(f) } } DisplaySourceLine::Annotation { range, annotation, annotation_type, annotation_part, } => { let indent_char = match annotation_part { DisplayAnnotationPart::Standalone => ' ', DisplayAnnotationPart::LabelContinuation => ' ', DisplayAnnotationPart::Consequitive => ' ', DisplayAnnotationPart::MultilineStart => '_', DisplayAnnotationPart::MultilineEnd => '_', }; let mark = match annotation_type { DisplayAnnotationType::Error => '^', DisplayAnnotationType::Warning => '-', DisplayAnnotationType::Info => '-', DisplayAnnotationType::Note => '-', DisplayAnnotationType::Help => '-', DisplayAnnotationType::None => ' ', }; let color = self.get_annotation_style(annotation_type); let indent_length = match annotation_part { DisplayAnnotationPart::LabelContinuation => range.1, DisplayAnnotationPart::Consequitive => range.1, _ => range.0, }; color.paint_fn( Box::new(|f| { format_repeat_char(indent_char, indent_length + 1, f)?; format_repeat_char(mark, range.1 - indent_length, f) }), f, )?; if !is_annotation_empty(annotation) { f.write_char(' ')?; color.paint_fn( Box::new(|f| { self.format_annotation( annotation, annotation_part == &DisplayAnnotationPart::LabelContinuation, true, f, ) }), f, )?; } Ok(()) } } } #[inline] fn format_raw_line( &self, line: &DisplayRawLine<'_>, lineno_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match line { DisplayRawLine::Origin { path, pos, header_type, } => { let header_sigil = match header_type { DisplayHeaderType::Initial => "-->", DisplayHeaderType::Continuation => ":::", }; let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); if let Some((col, row)) = pos { format_repeat_char(' ', lineno_width, f)?; lineno_color.paint(header_sigil, f)?; f.write_char(' ')?; path.fmt(f)?; f.write_char(':')?; col.fmt(f)?; f.write_char(':')?; row.fmt(f) } else { format_repeat_char(' ', lineno_width, f)?; lineno_color.paint(header_sigil, f)?; f.write_char(' ')?; path.fmt(f) } } DisplayRawLine::Annotation { annotation, source_aligned, continuation, } => { if *source_aligned { if *continuation { format_repeat_char(' ', lineno_width + 3, f)?; } else { let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); format_repeat_char(' ', lineno_width, f)?; f.write_char(' ')?; lineno_color.paint("=", f)?; f.write_char(' ')?; } } self.format_annotation(annotation, *continuation, false, f) } } } #[inline] fn format_line( &self, dl: &DisplayLine<'_>, lineno_width: usize, inline_marks_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match dl { DisplayLine::Source { lineno, inline_marks, line, } => { let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); if self.anonymized_line_numbers && lineno.is_some() { lineno_color.paint_fn( Box::new(|f| { f.write_str(Self::ANONYMIZED_LINE_NUM)?; f.write_str(" |") }), f, )?; } else { lineno_color.paint_fn( Box::new(|f| { match lineno { Some(n) => write!(f, "{:>width$}", n, width = lineno_width), None => format_repeat_char(' ', lineno_width, f), }?; f.write_str(" |") }), f, )?; } if *line != DisplaySourceLine::Empty { if !inline_marks.is_empty() || 0 < inline_marks_width { f.write_char(' ')?; self.format_inline_marks(inline_marks, inline_marks_width, f)?; } self.format_source_line(line, f)?; } else if !inline_marks.is_empty() { f.write_char(' ')?; self.format_inline_marks(inline_marks, inline_marks_width, f)?; } Ok(()) } DisplayLine::Fold { inline_marks } => { f.write_str("...")?; if !inline_marks.is_empty() || 0 < inline_marks_width { format_repeat_char(' ', lineno_width, f)?; self.format_inline_marks(inline_marks, inline_marks_width, f)?; } Ok(()) } DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f), } } fn format_inline_marks( &self, inline_marks: &[DisplayMark], inline_marks_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?; for mark in inline_marks { self.get_annotation_style(&mark.annotation_type).paint_fn( Box::new(|f| { f.write_char(match mark.mark_type { DisplayMarkType::AnnotationThrough => '|', DisplayMarkType::AnnotationStart => '/', }) }), f, )?; } Ok(()) } } annotate-snippets-0.9.1/src/formatter/style.rs000064400000000000000000000030640072674642500176420ustar 00000000000000//! Set of structures required to implement a stylesheet //! //! In order to provide additional styling information for the //! formatter, a structs can implement `Stylesheet` and `Style` //! traits. //! use std::fmt; /// StyleClass is a collection of named variants of style classes pub enum StyleClass { /// Message indicating an error. Error, /// Message indicating a warning. Warning, /// Message indicating an information. Info, /// Message indicating a note. Note, /// Message indicating a help. Help, /// Style for line numbers. LineNo, /// Parts of the text that are to be emphasised. Emphasis, /// Parts of the text that are regular. Usually a no-op. None, } /// This trait implements a return value for the `Stylesheet::get_style`. pub trait Style { /// The method used to write text with formatter fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result; /// The method used to write display function with formatter fn paint_fn<'a>( &self, c: Box) -> fmt::Result + 'a>, f: &mut fmt::Formatter<'_>, ) -> fmt::Result; /// The method used by the `Formatter` to display the message in bold font. fn bold(&self) -> Box; } /// Trait to annotate structs that can provide `Style` implementations for /// every `StyleClass` variant. pub trait Stylesheet { /// Returns a `Style` implementer based on the requested `StyleClass` variant. fn get_style(&self, class: StyleClass) -> Box; } annotate-snippets-0.9.1/src/lib.rs000064400000000000000000000035250072674642500152470ustar 00000000000000#![deny(rust_2018_idioms)] //! 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 //! //! ```text //! error[E0308]: mismatched types //! --> src/format.rs:52:1 //! | //! 51 | ) -> Option { //! | -------------- expected `Option` because of return type //! 52 | / for ann in annotations { //! 53 | | match (ann.range.0, ann.range.1) { //! 54 | | (None, None) => continue, //! 55 | | (Some(start), Some(end)) if start > end_index => continue, //! ... | //! 71 | | } //! 72 | | } //! | |_____^ expected enum `std::option::Option`, found () //! ``` //! //! The crate uses a three stage process with two conversions between states: //! //! ```text //! Snippet --> DisplayList --> String //! ``` //! //! The input type - [Snippet](self::snippet) is a structure designed //! to align with likely output from any parser whose code snippet is to be //! annotated. //! //! The middle structure - [DisplayList](self::display_list) is a //! structure designed to store the snippet data converted into a vector //! of lines containing semantic information about each line. //! This structure is the easiest to manipulate and organize. //! //! Finally, `impl Display` into a final `String` output. //! //! A user of the crate may choose to provide their own equivalent of the input //! structure with an `Into` trait. //! //! A user of the crate may also choose to provide their own formatter logic, //! to convert a `DisplayList` into a `String`, or just a `Stylesheet` to //! use the crate's formatting logic, but with a custom stylesheet. // TODO: check documentation pub mod display_list; pub mod formatter; pub mod snippet; pub mod stylesheets; annotate-snippets-0.9.1/src/snippet.rs000064400000000000000000000045360072674642500161660ustar 00000000000000//! Structures used as an input for the library. //! //! Example: //! //! ``` //! use annotate_snippets::snippet::*; //! //! Snippet { //! title: Some(Annotation { //! label: Some("mismatched types"), //! id: None, //! annotation_type: AnnotationType::Error, //! }), //! footer: vec![], //! slices: vec![ //! Slice { //! source: "Foo", //! line_start: 51, //! origin: Some("src/format.rs"), //! fold: false, //! annotations: vec![], //! }, //! Slice { //! source: "Faa", //! line_start: 129, //! origin: Some("src/display.rs"), //! fold: false, //! annotations: vec![], //! }, //! ], //! opt: Default::default(), //! }; //! ``` use crate::display_list::FormatOptions; /// Primary structure provided for formatting #[derive(Debug, Default)] pub struct Snippet<'a> { pub title: Option>, pub footer: Vec>, pub slices: Vec>, pub opt: FormatOptions, } /// Structure containing the slice of text to be annotated and /// basic information about the location of the slice. /// /// One `Slice` is meant to represent a single, continuous, /// slice of source code that you want to annotate. #[derive(Debug)] pub struct Slice<'a> { pub source: &'a str, pub line_start: usize, pub origin: Option<&'a str>, pub annotations: Vec>, /// If set explicitly to `true`, the snippet will fold /// parts of the slice that don't contain any annotations. pub fold: bool, } /// Types of annotations. #[derive(Debug, Clone, Copy, PartialEq)] pub enum AnnotationType { /// Error annotations are displayed using red color and "^" character. Error, /// Warning annotations are displayed using blue color and "-" character. Warning, Info, Note, Help, } /// An annotation for a `Slice`. #[derive(Debug)] pub struct SourceAnnotation<'a> { pub range: (usize, usize), pub label: &'a str, pub annotation_type: AnnotationType, } /// An annotation for a `Snippet`. #[derive(Debug)] pub struct Annotation<'a> { /// Identifier of the annotation. Usually error code like "E0308". pub id: Option<&'a str>, pub label: Option<&'a str>, pub annotation_type: AnnotationType, } annotate-snippets-0.9.1/src/stylesheets/color.rs000064400000000000000000000026110072674642500201660ustar 00000000000000use std::fmt::{self, Display}; use yansi_term::{Color::Fixed, Style as AnsiTermStyle}; use crate::formatter::style::{Style, StyleClass, Stylesheet}; struct AnsiTermStyleWrapper { style: AnsiTermStyle, } impl Style for AnsiTermStyleWrapper { fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.style.paint(text).fmt(f) } fn paint_fn<'a>( &self, c: Box) -> fmt::Result + 'a>, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { self.style.paint_fn(c).fmt(f) } fn bold(&self) -> Box { Box::new(AnsiTermStyleWrapper { style: self.style }) } } pub struct AnsiTermStylesheet; impl Stylesheet for AnsiTermStylesheet { fn get_style(&self, class: StyleClass) -> Box { let ansi_term_style = match class { StyleClass::Error => Fixed(9).bold(), StyleClass::Warning => Fixed(11).bold(), StyleClass::Info => Fixed(12).bold(), StyleClass::Note => AnsiTermStyle::new().bold(), StyleClass::Help => Fixed(14).bold(), StyleClass::LineNo => Fixed(12).bold(), StyleClass::Emphasis => AnsiTermStyle::new().bold(), StyleClass::None => AnsiTermStyle::new(), }; Box::new(AnsiTermStyleWrapper { style: ansi_term_style, }) } } annotate-snippets-0.9.1/src/stylesheets/mod.rs000064400000000000000000000004630072674642500176320ustar 00000000000000//! List of stylesheets //! //! The list depends on what optional dependencies the crate has been //! compiled with. //! //! By default the `no_color` is available. If the crate gets compiled //! with `ansi_term`, the `color` stylesheet is added. #[cfg(feature = "color")] pub mod color; pub mod no_color; annotate-snippets-0.9.1/src/stylesheets/no_color.rs000064400000000000000000000012510072674642500206610ustar 00000000000000use std::fmt; use crate::formatter::style::{Style, StyleClass, Stylesheet}; pub struct NoOpStyle {} impl Style for NoOpStyle { fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(text) } fn paint_fn<'a>( &self, c: Box) -> fmt::Result + 'a>, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { c(f) } fn bold(&self) -> Box { Box::new(NoOpStyle {}) } } pub struct NoColorStylesheet; impl Stylesheet for NoColorStylesheet { fn get_style(&self, _class: StyleClass) -> Box { Box::new(NoOpStyle {}) } } annotate-snippets-0.9.1/tests/diff/mod.rs000064400000000000000000000031020072674642500165320ustar 00000000000000use difference::{Changeset, Difference}; use yansi_term::Color::{Black, Green, Red}; pub fn get_diff(left: &str, right: &str) -> String { let mut output = String::new(); let Changeset { diffs, .. } = Changeset::new(left, right, "\n"); for i in 0..diffs.len() { match diffs[i] { Difference::Same(ref x) => { output += &format!(" {}\n", x); } Difference::Add(ref x) => { match diffs[i - 1] { Difference::Rem(ref y) => { output += &format!("{}", Green.paint("+")); let Changeset { diffs, .. } = Changeset::new(y, x, " "); for c in diffs { match c { Difference::Same(ref z) => { output += &format!("{} ", Green.paint(z.as_str())); } Difference::Add(ref z) => { output += &format!("{} ", Black.on(Green).paint(z.as_str())); } _ => (), } } output += "\n"; } _ => { output += &format!("+{}\n", Green.paint(x.as_str())); } }; } Difference::Rem(ref x) => { output += &format!("-{}\n", Red.paint(x.as_str())); } } } output } annotate-snippets-0.9.1/tests/dl_from_snippet.rs000064400000000000000000000325450072674642500202440ustar 00000000000000use annotate_snippets::display_list::DisplayList; use annotate_snippets::{display_list as dl, formatter::get_term_style, snippet}; #[test] fn test_format_title() { let input = snippet::Snippet { title: Some(snippet::Annotation { id: Some("E0001"), label: Some("This is a title"), annotation_type: snippet::AnnotationType::Error, }), footer: vec![], slices: vec![], opt: Default::default(), }; let output = dl::DisplayList { body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { annotation: dl::Annotation { annotation_type: dl::DisplayAnnotationType::Error, id: Some("E0001"), label: vec![dl::DisplayTextFragment { content: "This is a title", style: dl::DisplayTextStyle::Emphasis, }], }, source_aligned: false, continuation: false, })], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } #[test] fn test_format_slice() { let line_1 = "This is line 1"; let line_2 = "This is line 2"; let source = vec![line_1, line_2].join("\n"); let input = snippet::Snippet { title: None, footer: vec![], slices: vec![snippet::Slice { source: &source, line_start: 5402, origin: None, annotations: vec![], fold: false, }], opt: Default::default(), }; let output = dl::DisplayList { body: vec![ dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, dl::DisplayLine::Source { lineno: Some(5402), inline_marks: vec![], line: dl::DisplaySourceLine::Content { text: line_1, range: (0, line_1.len()), }, }, dl::DisplayLine::Source { lineno: Some(5403), inline_marks: vec![], line: dl::DisplaySourceLine::Content { range: (line_1.len() + 1, source.len()), text: line_2, }, }, dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, ], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } #[test] fn test_format_slices_continuation() { let src_0 = "This is slice 1"; let src_0_len = src_0.len(); let src_1 = "This is slice 2"; let src_1_len = src_1.len(); let input = snippet::Snippet { title: None, footer: vec![], slices: vec![ snippet::Slice { source: src_0, line_start: 5402, origin: Some("file1.rs"), annotations: vec![], fold: false, }, snippet::Slice { source: src_1, line_start: 2, origin: Some("file2.rs"), annotations: vec![], fold: false, }, ], opt: Default::default(), }; let output = dl::DisplayList { body: vec![ dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { path: "file1.rs", pos: None, header_type: dl::DisplayHeaderType::Initial, }), dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, dl::DisplayLine::Source { lineno: Some(5402), inline_marks: vec![], line: dl::DisplaySourceLine::Content { text: src_0, range: (0, src_0_len), }, }, dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { path: "file2.rs", pos: None, header_type: dl::DisplayHeaderType::Continuation, }), dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, dl::DisplayLine::Source { lineno: Some(2), inline_marks: vec![], line: dl::DisplaySourceLine::Content { text: src_1, range: (0, src_1_len), }, }, dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, ], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } #[test] fn test_format_slice_annotation_standalone() { let line_1 = "This is line 1"; let line_2 = "This is line 2"; let source = vec![line_1, line_2].join("\n"); // In line 2 let range = (22, 24); let input = snippet::Snippet { title: None, footer: vec![], slices: vec![snippet::Slice { source: &source, line_start: 5402, origin: None, annotations: vec![snippet::SourceAnnotation { range, label: "Test annotation", annotation_type: snippet::AnnotationType::Info, }], fold: false, }], opt: Default::default(), }; let output = dl::DisplayList { body: vec![ dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, dl::DisplayLine::Source { lineno: Some(5402), inline_marks: vec![], line: dl::DisplaySourceLine::Content { range: (0, line_1.len()), text: line_1, }, }, dl::DisplayLine::Source { lineno: Some(5403), inline_marks: vec![], line: dl::DisplaySourceLine::Content { range: (line_1.len() + 1, source.len()), text: line_2, }, }, dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Annotation { annotation: dl::Annotation { annotation_type: dl::DisplayAnnotationType::Info, id: None, label: vec![dl::DisplayTextFragment { content: "Test annotation", style: dl::DisplayTextStyle::Regular, }], }, range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)), annotation_type: dl::DisplayAnnotationType::Info, annotation_part: dl::DisplayAnnotationPart::Standalone, }, }, dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, ], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } #[test] fn test_format_label() { let input = snippet::Snippet { title: None, footer: vec![snippet::Annotation { id: None, label: Some("This __is__ a title"), annotation_type: snippet::AnnotationType::Error, }], slices: vec![], opt: Default::default(), }; let output = dl::DisplayList { body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { annotation: dl::Annotation { annotation_type: dl::DisplayAnnotationType::Error, id: None, label: vec![ dl::DisplayTextFragment { content: "This ", style: dl::DisplayTextStyle::Regular, }, dl::DisplayTextFragment { content: "is", style: dl::DisplayTextStyle::Emphasis, }, dl::DisplayTextFragment { content: " a title", style: dl::DisplayTextStyle::Regular, }, ], }, source_aligned: true, continuation: false, })], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } #[test] #[should_panic] fn test_i26() { let source = "short"; let label = "label"; let input = snippet::Snippet { title: None, footer: vec![], slices: vec![snippet::Slice { annotations: vec![snippet::SourceAnnotation { range: (0, source.len() + 1), label, annotation_type: snippet::AnnotationType::Error, }], source, line_start: 0, origin: None, fold: false, }], opt: Default::default(), }; let _ = dl::DisplayList::from(input); } #[test] fn test_i_29() { let snippets = snippet::Snippet { title: Some(snippet::Annotation { id: None, label: Some("oops"), annotation_type: snippet::AnnotationType::Error, }), footer: vec![], slices: vec![snippet::Slice { source: "First line\r\nSecond oops line", line_start: 1, origin: Some(""), annotations: vec![snippet::SourceAnnotation { range: (19, 23), label: "oops", annotation_type: snippet::AnnotationType::Error, }], fold: true, }], opt: Default::default(), }; let expected = DisplayList { body: vec![ dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { annotation: dl::Annotation { annotation_type: dl::DisplayAnnotationType::Error, id: None, label: vec![dl::DisplayTextFragment { content: "oops", style: dl::DisplayTextStyle::Emphasis, }], }, source_aligned: false, continuation: false, }), dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { path: "", pos: Some((2, 8)), header_type: dl::DisplayHeaderType::Initial, }), dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, dl::DisplayLine::Source { lineno: Some(1), inline_marks: vec![], line: dl::DisplaySourceLine::Content { text: "First line", range: (0, 10), }, }, dl::DisplayLine::Source { lineno: Some(2), inline_marks: vec![], line: dl::DisplaySourceLine::Content { text: "Second oops line", range: (12, 28), }, }, dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Annotation { annotation: dl::Annotation { annotation_type: dl::DisplayAnnotationType::None, id: None, label: vec![dl::DisplayTextFragment { content: "oops", style: dl::DisplayTextStyle::Regular, }], }, range: (7, 11), annotation_type: dl::DisplayAnnotationType::Error, annotation_part: dl::DisplayAnnotationPart::Standalone, }, }, dl::DisplayLine::Source { lineno: None, inline_marks: vec![], line: dl::DisplaySourceLine::Empty, }, ], stylesheet: get_term_style(false), anonymized_line_numbers: false, margin: None, }; assert_eq!(DisplayList::from(snippets), expected); } annotate-snippets-0.9.1/tests/fixtures/no-color/issue_9.toml000064400000000000000000000012500072674642500223550ustar 00000000000000[title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" annotation_type = "Error" [[slices]] source = "let x = vec![1];" line_start = 4 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" [[slices.annotations]] label = "move occurs because `x` has type `std::vec::Vec`, which does not implement the `Copy` trait" annotation_type = "Warning" range = [4, 5] [[slices]] source = "let y = x;" line_start = 7 [[slices.annotations]] label = "value moved here" annotation_type = "Warning" range = [8, 9] [[slices]] source = "x;" line_start = 9 [[slices.annotations]] label = "value used here after move" annotation_type = "Error" range = [0, 1] annotate-snippets-0.9.1/tests/fixtures/no-color/issue_9.txt000064400000000000000000000005460072674642500222300ustar 00000000000000error: 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`, which does not implement the `Copy` trait | 7 | let y = x; | - value moved here | 9 | x; | ^ value used here after move |annotate-snippets-0.9.1/tests/fixtures/no-color/multiline_annotation.toml000064400000000000000000000022560072674642500252400ustar 00000000000000[[slices]] 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 [[slices.annotations]] label = "expected `std::option::Option` because of return type" annotation_type = "Warning" range = [5, 19] [[slices.annotations]] label = "expected enum `std::option::Option`, found ()" annotation_type = "Error" range = [22, 766] [title] label = "mismatched types" id = "E0308" annotation_type = "Error" annotate-snippets-0.9.1/tests/fixtures/no-color/multiline_annotation.txt000064400000000000000000000010130072674642500250720ustar 00000000000000error[E0308]: mismatched types --> src/format.rs:51:6 | 51 | ) -> Option { | -------------- expected `std::option::Option` because of return type 52 | / for ann in annotations { 53 | | match (ann.range.0, ann.range.1) { 54 | | (None, None) => continue, 55 | | (Some(start), Some(end)) if start > end_index || end < start_index => continue, ... | 71 | | } 72 | | } | |_____^ expected enum `std::option::Option`, found () | annotate-snippets-0.9.1/tests/fixtures/no-color/multiline_annotation2.toml000064400000000000000000000007110072674642500253140ustar 00000000000000[[slices]] source = """ if let DisplayLine::Source { ref mut inline_marks, } = body[body_idx] """ line_start = 139 origin = "src/display_list.rs" fold = false [[slices.annotations]] label = "missing fields `lineno`, `content`" annotation_type = "Error" range = [31, 128] [title] label = "pattern does not mention fields `lineno`, `content`" id = "E0027" annotation_type = "Error" annotate-snippets-0.9.1/tests/fixtures/no-color/multiline_annotation2.txt000064400000000000000000000006100072674642500251560ustar 00000000000000error[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.9.1/tests/fixtures/no-color/multiline_annotation3.toml000064400000000000000000000005630072674642500253220ustar 00000000000000[[slices]] source = """ This is an exampl e of an edge case of an annotation overflowing to exactly one character on next line. """ line_start = 26 origin = "foo.txt" fold = false [[slices.annotations]] label = "this should not be on separate lines" annotation_type = "Error" range = [11, 18] [title] label = "spacing error found" id = "E####" annotation_type = "Error" annotate-snippets-0.9.1/tests/fixtures/no-color/multiline_annotation3.txt000064400000000000000000000003760072674642500251700ustar 00000000000000error[E####]: spacing error found --> foo.txt:26:12 | 26 | This is an exampl | ____________^ 27 | | e of an edge case of an annotation overflowing | |_^ this should not be on separate lines 28 | to exactly one character on next line. |annotate-snippets-0.9.1/tests/fixtures/no-color/multiple_annotations.toml000064400000000000000000000011530072674642500252470ustar 00000000000000[[slices]] 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 [[slices.annotations]] label = "Variable defined here" annotation_type = "Error" range = [100, 110] [[slices.annotations]] label = "Referenced here" annotation_type = "Error" range = [184, 194] [[slices.annotations]] label = "Referenced again here" annotation_type = "Error" range = [243, 253] annotate-snippets-0.9.1/tests/fixtures/no-color/multiple_annotations.txt000064400000000000000000000007640072674642500251220ustar 00000000000000 | 96 | fn add_title_line(result: &mut Vec, 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.9.1/tests/fixtures/no-color/simple.toml000064400000000000000000000006750072674642500223000ustar 00000000000000[[slices]] source = """ }) for line in &self.body {""" line_start = 169 origin = "src/format_color.rs" [[slices.annotations]] label = "unexpected token" annotation_type = "Error" range = [20, 23] [[slices.annotations]] label = "expected one of `.`, `;`, `?`, or an operator here" annotation_type = "Warning" range = [10, 11] [title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" annotation_type = "Error" annotate-snippets-0.9.1/tests/fixtures/no-color/simple.txt000064400000000000000000000004260072674642500221360ustar 00000000000000error: 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.9.1/tests/fixtures/no-color/strip_line.toml000064400000000000000000000011650072674642500231520ustar 00000000000000[title] id = "E0308" label = "mismatched types" annotation_type = "Error" [[slices]] source = " let _: () = 42;" line_start = 4 origin = "$DIR/whitespace-trimming.rs" [[slices.annotations]] label = "expected (), found integer" annotation_type = "Error" range = [192, 194] [opt] color = false anonymized_line_numbers = true [opt.margin] whitespace_left = 180 span_left = 192 span_right = 194 label_right = 221 column_width = 140 max_line_len = 195 annotate-snippets-0.9.1/tests/fixtures/no-color/strip_line.txt000064400000000000000000000003010072674642500230050ustar 00000000000000error[E0308]: mismatched types --> $DIR/whitespace-trimming.rs:4:193 | LL | ... let _: () = 42; | ^^ expected (), found integer | annotate-snippets-0.9.1/tests/fixtures/no-color/strip_line_char.toml000064400000000000000000000011660072674642500241500ustar 00000000000000[title] id = "E0308" label = "mismatched types" annotation_type = "Error" [[slices]] source = " let _: () = 42ñ" line_start = 4 origin = "$DIR/whitespace-trimming.rs" [[slices.annotations]] label = "expected (), found integer" annotation_type = "Error" range = [192, 194] [opt] color = false anonymized_line_numbers = true [opt.margin] whitespace_left = 180 span_left = 192 span_right = 194 label_right = 221 column_width = 140 max_line_len = 195 annotate-snippets-0.9.1/tests/fixtures/no-color/strip_line_char.txt000064400000000000000000000003020072674642500240030ustar 00000000000000error[E0308]: mismatched types --> $DIR/whitespace-trimming.rs:4:193 | LL | ... let _: () = 42ñ | ^^ expected (), found integer | annotate-snippets-0.9.1/tests/fixtures/no-color/strip_line_non_ws.toml000064400000000000000000000014470072674642500245400ustar 00000000000000[title] id = "E0308" label = "mismatched types" annotation_type = "Error" [[slices]] source = " let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();" line_start = 4 origin = "$DIR/non-whitespace-trimming.rs" [[slices.annotations]] label = "expected (), found integer" annotation_type = "Error" range = [240, 242] [opt] color = false anonymized_line_numbers = true [opt.margin] whitespace_left = 4 span_left = 240 span_right = 242 label_right = 271 column_width = 140 max_line_len = 371 annotate-snippets-0.9.1/tests/fixtures/no-color/strip_line_non_ws.txt000064400000000000000000000005000072674642500243710ustar 00000000000000error[E0308]: mismatched types --> $DIR/non-whitespace-trimming.rs:4:241 | LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();... | ^^ expected (), found integer | annotate-snippets-0.9.1/tests/fixtures_test.rs000064400000000000000000000027250072674642500177650ustar 00000000000000mod diff; mod snippet; use crate::snippet::SnippetDef; use annotate_snippets::{display_list::DisplayList, snippet::Snippet}; use glob::glob; use std::{error::Error, fs::File, io, io::prelude::*}; fn read_file(path: &str) -> Result { let mut f = File::open(path)?; let mut s = String::new(); (f.read_to_string(&mut s))?; Ok(s.trim_end().to_string()) } fn read_fixture<'de>(src: &'de str) -> Result, Box> { Ok(toml::from_str(src).map(|a: SnippetDef| a.into())?) } #[test] fn test_fixtures() { for entry in glob("./tests/fixtures/no-color/**/*.toml").expect("Failed to read glob pattern") { let p = entry.expect("Error while getting an entry"); let path_in = p.to_str().expect("Can't print path"); let path_out = path_in.replace(".toml", ".txt"); let src = read_file(&path_in).expect("Failed to read file"); let snippet = read_fixture(&src).expect("Failed to read file"); let expected_out = read_file(&path_out).expect("Failed to read file"); let dl = DisplayList::from(snippet); let actual_out = dl.to_string(); println!("{}", expected_out); println!("{}", actual_out.trim_end()); assert_eq!( expected_out, actual_out.trim_end(), "\n\n\nWhile parsing: {}\nThe diff is:\n\n\n{}\n\n\n", path_in, diff::get_diff(expected_out.as_str(), actual_out.as_str()) ); } } annotate-snippets-0.9.1/tests/formatter.rs000064400000000000000000000515220072674642500170570ustar 00000000000000use annotate_snippets::display_list::*; use annotate_snippets::snippet::{self, Snippet}; #[test] fn test_source_empty() { let dl = DisplayList::from(vec![DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, }]); assert_eq!(dl.to_string(), " |"); } #[test] fn test_source_content() { let dl = DisplayList::from(vec![ DisplayLine::Source { lineno: Some(56), inline_marks: vec![], line: DisplaySourceLine::Content { text: "This is an example", range: (0, 19), }, }, DisplayLine::Source { lineno: Some(57), inline_marks: vec![], line: DisplaySourceLine::Content { text: "of content lines", range: (0, 19), }, }, ]); assert_eq!( dl.to_string(), "56 | This is an example\n57 | of content lines" ); } #[test] fn test_source_annotation_standalone_singleline() { let dl = DisplayList::from(vec![DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 5), annotation: Annotation { annotation_type: DisplayAnnotationType::None, id: None, label: vec![DisplayTextFragment { content: "Example string", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::Error, annotation_part: DisplayAnnotationPart::Standalone, }, }]); assert_eq!(dl.to_string(), " | ^^^^^ Example string"); } #[test] fn test_source_annotation_standalone_multiline() { let dl = DisplayList::from(vec![ DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 5), annotation: Annotation { annotation_type: DisplayAnnotationType::Help, id: None, label: vec![DisplayTextFragment { content: "Example string", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::Warning, annotation_part: DisplayAnnotationPart::Standalone, }, }, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 5), annotation: Annotation { annotation_type: DisplayAnnotationType::Help, id: None, label: vec![DisplayTextFragment { content: "Second line", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::Warning, annotation_part: DisplayAnnotationPart::LabelContinuation, }, }, ]); assert_eq!( dl.to_string(), " | ----- help: Example string\n | Second line" ); } #[test] fn test_source_annotation_standalone_multi_annotation() { let dl = DisplayList::from(vec![ DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 5), annotation: Annotation { annotation_type: DisplayAnnotationType::Info, id: None, label: vec![DisplayTextFragment { content: "Example string", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::Note, annotation_part: DisplayAnnotationPart::Standalone, }, }, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 5), annotation: Annotation { annotation_type: DisplayAnnotationType::Info, id: None, label: vec![DisplayTextFragment { content: "Second line", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::Note, annotation_part: DisplayAnnotationPart::LabelContinuation, }, }, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 5), annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, id: None, label: vec![DisplayTextFragment { content: "This is a note", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::Note, annotation_part: DisplayAnnotationPart::Consequitive, }, }, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 5), annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, id: None, label: vec![DisplayTextFragment { content: "Second line of the warning", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::Note, annotation_part: DisplayAnnotationPart::LabelContinuation, }, }, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 5), annotation: Annotation { annotation_type: DisplayAnnotationType::Info, id: None, label: vec![DisplayTextFragment { content: "This is an info", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::Info, annotation_part: DisplayAnnotationPart::Standalone, }, }, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 5), annotation: Annotation { annotation_type: DisplayAnnotationType::Help, id: None, label: vec![DisplayTextFragment { content: "This is help", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::Help, annotation_part: DisplayAnnotationPart::Standalone, }, }, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { range: (0, 0), annotation: Annotation { annotation_type: DisplayAnnotationType::None, id: None, label: vec![DisplayTextFragment { content: "This is an annotation of type none", style: DisplayTextStyle::Regular, }], }, annotation_type: DisplayAnnotationType::None, annotation_part: DisplayAnnotationPart::Standalone, }, }, ]); assert_eq!(dl.to_string(), " | ----- info: Example string\n | Second line\n | warning: This is a note\n | Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n | This is an annotation of type none"); } #[test] fn test_fold_line() { let dl = DisplayList::from(vec![ DisplayLine::Source { lineno: Some(5), inline_marks: vec![], line: DisplaySourceLine::Content { text: "This is line 5", range: (0, 19), }, }, DisplayLine::Fold { inline_marks: vec![], }, DisplayLine::Source { lineno: Some(10021), inline_marks: vec![], line: DisplaySourceLine::Content { text: "... and now we're at line 10021", range: (0, 19), }, }, ]); assert_eq!( dl.to_string(), " 5 | This is line 5\n...\n10021 | ... and now we're at line 10021" ); } #[test] fn test_raw_origin_initial_nopos() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { path: "src/test.rs", pos: None, header_type: DisplayHeaderType::Initial, })]); assert_eq!(dl.to_string(), "--> src/test.rs"); } #[test] fn test_raw_origin_initial_pos() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { path: "src/test.rs", pos: Some((23, 15)), header_type: DisplayHeaderType::Initial, })]); assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); } #[test] fn test_raw_origin_continuation() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { path: "src/test.rs", pos: Some((23, 15)), header_type: DisplayHeaderType::Continuation, })]); assert_eq!(dl.to_string(), "::: src/test.rs:23:15"); } #[test] fn test_raw_annotation_unaligned() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, id: Some("E0001"), label: vec![DisplayTextFragment { content: "This is an error", style: DisplayTextStyle::Regular, }], }, source_aligned: false, continuation: false, })]); assert_eq!(dl.to_string(), "error[E0001]: This is an error"); } #[test] fn test_raw_annotation_unaligned_multiline() { let dl = DisplayList::from(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, id: Some("E0001"), label: vec![DisplayTextFragment { content: "This is an error", style: DisplayTextStyle::Regular, }], }, source_aligned: false, continuation: false, }), DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, id: Some("E0001"), label: vec![DisplayTextFragment { content: "Second line of the error", style: DisplayTextStyle::Regular, }], }, source_aligned: false, continuation: true, }), ]); assert_eq!( dl.to_string(), "warning[E0001]: This is an error\n Second line of the error" ); } #[test] fn test_raw_annotation_aligned() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, id: Some("E0001"), label: vec![DisplayTextFragment { content: "This is an error", style: DisplayTextStyle::Regular, }], }, source_aligned: true, continuation: false, })]); assert_eq!(dl.to_string(), " = error[E0001]: This is an error"); } #[test] fn test_raw_annotation_aligned_multiline() { let dl = DisplayList::from(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, id: Some("E0001"), label: vec![DisplayTextFragment { content: "This is an error", style: DisplayTextStyle::Regular, }], }, source_aligned: true, continuation: false, }), DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, id: Some("E0001"), label: vec![DisplayTextFragment { content: "Second line of the error", style: DisplayTextStyle::Regular, }], }, source_aligned: true, continuation: true, }), ]); assert_eq!( dl.to_string(), " = warning[E0001]: This is an error\n Second line of the error" ); } #[test] fn test_different_annotation_types() { let dl = DisplayList::from(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Note, id: None, label: vec![DisplayTextFragment { content: "This is a note", style: DisplayTextStyle::Regular, }], }, source_aligned: false, continuation: false, }), DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::None, id: None, label: vec![DisplayTextFragment { content: "This is just a string", style: DisplayTextStyle::Regular, }], }, source_aligned: false, continuation: false, }), DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::None, id: None, label: vec![DisplayTextFragment { content: "Second line of none type annotation", style: DisplayTextStyle::Regular, }], }, source_aligned: false, continuation: true, }), ]); assert_eq!( dl.to_string(), "note: This is a note\nThis is just a string\n Second line of none type annotation", ); } #[test] fn test_inline_marks_empty_line() { let dl = DisplayList::from(vec![DisplayLine::Source { lineno: None, inline_marks: vec![DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, annotation_type: DisplayAnnotationType::Error, }], line: DisplaySourceLine::Empty, }]); assert_eq!(dl.to_string(), " | |",); } #[test] fn test_anon_lines() { let mut dl = DisplayList::from(vec![ DisplayLine::Source { lineno: Some(56), inline_marks: vec![], line: DisplaySourceLine::Content { text: "This is an example", range: (0, 19), }, }, DisplayLine::Source { lineno: Some(57), inline_marks: vec![], line: DisplaySourceLine::Content { text: "of content lines", range: (0, 19), }, }, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, }, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Content { text: "abc", range: (0, 19), }, }, ]); dl.anonymized_line_numbers = true; assert_eq!( dl.to_string(), "LL | This is an example\nLL | of content lines\n |\n | abc" ); } #[test] fn test_raw_origin_initial_pos_anon_lines() { let mut dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { path: "src/test.rs", pos: Some((23, 15)), header_type: DisplayHeaderType::Initial, })]); // Using anonymized_line_numbers should not affect the inital position dl.anonymized_line_numbers = true; assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); } #[test] fn test_i_29() { let snippets = Snippet { title: Some(snippet::Annotation { id: None, label: Some("oops"), annotation_type: snippet::AnnotationType::Error, }), footer: vec![], slices: vec![snippet::Slice { source: "First line\r\nSecond oops line", line_start: 1, origin: Some(""), annotations: vec![snippet::SourceAnnotation { range: (19, 23), label: "oops", annotation_type: snippet::AnnotationType::Error, }], fold: true, }], opt: Default::default(), }; let expected = r#"error: oops --> :2:8 | 1 | First line 2 | Second oops line | ^^^^ oops |"#; assert_eq!(DisplayList::from(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters() { let snippets = Snippet { slices: vec![snippet::Slice { source: "こんにちは、世界", line_start: 1, origin: Some(""), annotations: vec![snippet::SourceAnnotation { range: (6, 8), label: "world", annotation_type: snippet::AnnotationType::Error, }], fold: false, }], title: None, footer: vec![], opt: Default::default(), }; let expected = r#" --> :1:7 | 1 | こんにちは、世界 | ^^^^ world |"#; assert_eq!(DisplayList::from(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters_across_lines() { let snippets = Snippet { slices: vec![snippet::Slice { source: "おはよう\nございます", line_start: 1, origin: Some(""), annotations: vec![snippet::SourceAnnotation { range: (2, 8), label: "Good morning", annotation_type: snippet::AnnotationType::Error, }], fold: false, }], title: None, footer: vec![], opt: Default::default(), }; let expected = r#" --> :1:3 | 1 | おはよう | _____^ 2 | | ございます | |______^ Good morning |"#; assert_eq!(DisplayList::from(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters_multiple() { let snippets = Snippet { slices: vec![snippet::Slice { source: "お寿司\n食べたい🍣", line_start: 1, origin: Some(""), annotations: vec![ snippet::SourceAnnotation { range: (0, 3), label: "Sushi1", annotation_type: snippet::AnnotationType::Error, }, snippet::SourceAnnotation { range: (6, 8), label: "Sushi2", annotation_type: snippet::AnnotationType::Note, }, ], fold: false, }], title: None, footer: vec![], opt: Default::default(), }; let expected = r#" --> :1:1 | 1 | お寿司 | ^^^^^^ Sushi1 2 | 食べたい🍣 | ---- note: Sushi2 |"#; assert_eq!(DisplayList::from(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters_mixed() { let snippets = Snippet { slices: vec![snippet::Slice { source: "こんにちは、新しいWorld!", line_start: 1, origin: Some(""), annotations: vec![snippet::SourceAnnotation { range: (6, 14), label: "New world", annotation_type: snippet::AnnotationType::Error, }], fold: false, }], title: None, footer: vec![], opt: Default::default(), }; let expected = r#" --> :1:7 | 1 | こんにちは、新しいWorld! | ^^^^^^^^^^^ New world |"#; assert_eq!(DisplayList::from(snippets).to_string(), expected); } annotate-snippets-0.9.1/tests/snippet/mod.rs000064400000000000000000000121700072674642500173110ustar 00000000000000use serde::{Deserialize, Deserializer, Serialize}; use annotate_snippets::{ display_list::{FormatOptions, Margin}, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; #[derive(Deserialize)] pub struct SnippetDef<'a> { #[serde(deserialize_with = "deserialize_annotation")] #[serde(default)] #[serde(borrow)] pub title: Option>, #[serde(deserialize_with = "deserialize_annotations")] #[serde(default)] #[serde(borrow)] pub footer: Vec>, #[serde(deserialize_with = "deserialize_opt")] #[serde(default)] pub opt: FormatOptions, #[serde(deserialize_with = "deserialize_slices")] #[serde(borrow)] pub slices: Vec>, } impl<'a> Into> for SnippetDef<'a> { fn into(self) -> Snippet<'a> { let SnippetDef { title, footer, opt, slices, } = self; Snippet { title, footer, slices, opt, } } } fn deserialize_opt<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper(#[serde(with = "FormatOptionsDef")] FormatOptions); Wrapper::deserialize(deserializer).map(|w| w.0) } #[derive(Deserialize)] #[serde(remote = "FormatOptions")] pub struct FormatOptionsDef { #[serde(default)] pub color: bool, #[serde(default)] pub anonymized_line_numbers: bool, #[serde(deserialize_with = "deserialize_margin")] #[serde(default)] pub margin: Option, } fn deserialize_margin<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper { whitespace_left: usize, span_left: usize, span_right: usize, label_right: usize, column_width: usize, max_line_len: usize, } Option::::deserialize(deserializer).map(|opt_wrapped: Option| { opt_wrapped.map(|wrapped: Wrapper| { let Wrapper { whitespace_left, span_left, span_right, label_right, column_width, max_line_len, } = wrapped; Margin::new( whitespace_left, span_left, span_right, label_right, column_width, max_line_len, ) }) }) } fn deserialize_slices<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper<'a>( #[serde(with = "SliceDef")] #[serde(borrow)] Slice<'a>, ); let v = Vec::deserialize(deserializer)?; Ok(v.into_iter().map(|Wrapper(a)| a).collect()) } fn deserialize_annotation<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper<'a>( #[serde(with = "AnnotationDef")] #[serde(borrow)] Annotation<'a>, ); Option::::deserialize(deserializer) .map(|opt_wrapped: Option| opt_wrapped.map(|wrapped: Wrapper| wrapped.0)) } fn deserialize_annotations<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper<'a>( #[serde(with = "AnnotationDef")] #[serde(borrow)] Annotation<'a>, ); let v = Vec::deserialize(deserializer)?; Ok(v.into_iter().map(|Wrapper(a)| a).collect()) } #[derive(Deserialize)] #[serde(remote = "Slice")] pub struct SliceDef<'a> { #[serde(borrow)] pub source: &'a str, pub line_start: usize, #[serde(borrow)] pub origin: Option<&'a str>, #[serde(deserialize_with = "deserialize_source_annotations")] #[serde(borrow)] pub annotations: Vec>, #[serde(default)] pub fold: bool, } fn deserialize_source_annotations<'de, D>( deserializer: D, ) -> Result>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper<'a>( #[serde(with = "SourceAnnotationDef")] #[serde(borrow)] SourceAnnotation<'a>, ); let v = Vec::deserialize(deserializer)?; Ok(v.into_iter().map(|Wrapper(a)| a).collect()) } #[derive(Serialize, Deserialize)] #[serde(remote = "SourceAnnotation")] pub struct SourceAnnotationDef<'a> { pub range: (usize, usize), #[serde(borrow)] pub label: &'a str, #[serde(with = "AnnotationTypeDef")] pub annotation_type: AnnotationType, } #[derive(Serialize, Deserialize)] #[serde(remote = "Annotation")] pub struct AnnotationDef<'a> { #[serde(borrow)] pub id: Option<&'a str>, #[serde(borrow)] pub label: Option<&'a str>, #[serde(with = "AnnotationTypeDef")] pub annotation_type: AnnotationType, } #[allow(dead_code)] #[derive(Serialize, Deserialize)] #[serde(remote = "AnnotationType")] enum AnnotationTypeDef { Error, Warning, Info, Note, Help, }