annotate-snippets-0.11.5/.cargo_vcs_info.json0000644000000001360000000000100145740ustar { "git": { "sha1": "72dd8c7b9210bace1be990e3e3018fab46fd8291" }, "path_in_vcs": "" }annotate-snippets-0.11.5/Cargo.lock0000644000000577360000000000100125710ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "annotate-snippets" version = "0.11.5" dependencies = [ "anstream 0.6.18", "anstyle", "difference", "divan", "glob", "memchr", "serde", "snapbox", "toml", "tryfn", "unicode-width 0.2.0", ] [[package]] name = "anstream" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon 1.0.2", "colorchoice", "is-terminal", "utf8parse", ] [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon 3.0.6", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-lossy" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fcff6599f06e21b0165c85052ccd6e67dc388ddd1c516a9dc5f55dc8cacf004" dependencies = [ "anstyle", ] [[package]] name = "anstyle-parse" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-svg" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa" dependencies = [ "anstream 0.6.18", "anstyle", "anstyle-lossy", "html-escape", "unicode-width 0.1.13", ] [[package]] name = "anstyle-wincon" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", "windows-sys 0.59.0", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bstr" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487" dependencies = [ "clap_builder", "clap_derive", "once_cell", ] [[package]] name = "clap_builder" version = "4.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e" dependencies = [ "anstream 0.3.2", "anstyle", "clap_lex", "strsim", "terminal_size", ] [[package]] name = "clap_derive" version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "condtype" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "divan" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc40f214f0d9e897cfc72e2edfa5c225d3252f758c537f11ac0a80371c073a6" dependencies = [ "cfg-if", "clap", "condtype", "divan-macros", "libc", "regex-lite", ] [[package]] name = "divan-macros" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "escape8259" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba4f4911e3666fcd7826997b4745c8224295a6f3072f1418c3067b97a67557ee" dependencies = [ "rustversion", ] [[package]] name = "escargot" version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05a3ac187a16b5382fef8c69fd1bad123c67b7cf3932240a2d43dcdd32cded88" dependencies = [ "log", "once_cell", "serde", "serde_json", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata", "regex-syntax", ] [[package]] name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "html-escape" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" dependencies = [ "utf8-width", ] [[package]] name = "ignore" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ "globset", "lazy_static", "log", "memchr", "regex", "same-file", "thread_local", "walkdir", "winapi-util", ] [[package]] name = "indexmap" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", "windows-sys 0.48.0", ] [[package]] name = "is-terminal" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] [[package]] name = "is_terminal_polyfill" version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b52b2de84ed0341893ce61ca1af04fa54eea0a764ecc38c6855cc5db84dc1927" dependencies = [ "is-terminal", ] [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libtest-mimic" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58" dependencies = [ "clap", "escape8259", "termcolor", "threadpool", ] [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "os_pipe" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "proc-macro2" version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-lite" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" [[package]] name = "regex-syntax" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustix" version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys 0.48.0", ] [[package]] name = "rustversion" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "serde" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "similar" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "snapbox" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1373ce406dfad473059bbc31d807715642182bbc952a811952b58d1c9e41dcfa" dependencies = [ "anstream 0.6.18", "anstyle", "anstyle-svg", "escargot", "libc", "normalize-line-endings", "os_pipe", "serde_json", "similar", "snapbox-macros", "wait-timeout", "windows-sys 0.59.0", ] [[package]] name = "snapbox-macros" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" dependencies = [ "anstream 0.6.18", ] [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "2.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix", "windows-sys 0.48.0", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "threadpool" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" dependencies = [ "num_cpus", ] [[package]] name = "toml" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tryfn" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396" dependencies = [ "ignore", "libtest-mimic", "snapbox", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "winapi-util" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] annotate-snippets-0.11.5/Cargo.toml0000644000000117600000000000100125770ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.66.0" name = "annotate-snippets" version = "0.11.5" build = false include = [ "build.rs", "src/**/*", "Cargo.toml", "Cargo.lock", "LICENSE*", "README.md", "benches/**/*", "examples/**/*", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Library for building code annotations" readme = "README.md" keywords = [ "code", "analysis", "ascii", "errors", "debug", ] categories = [] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/annotate-snippets-rs" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", "--generate-link-to-definition", ] [package.metadata.release] tag-name = "{{version}}" [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" min = 1 replace = "{{version}}" search = "Unreleased" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = "...{{tag_name}}" search = '\.\.\.HEAD' [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" min = 1 replace = "{{date}}" search = "ReleaseDate" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = """ ## [Unreleased] - ReleaseDate """ search = "" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = """ [Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/{{tag_name}}...HEAD""" search = "" [lib] name = "annotate_snippets" path = "src/lib.rs" [[example]] name = "expected_type" path = "examples/expected_type.rs" [[example]] name = "footer" path = "examples/footer.rs" [[example]] name = "format" path = "examples/format.rs" [[example]] name = "multislice" path = "examples/multislice.rs" [[bench]] name = "bench" path = "benches/bench.rs" harness = false [dependencies.anstyle] version = "1.0.4" [dependencies.memchr] version = "2.7.4" optional = true [dependencies.unicode-width] version = "0.2.0" [dev-dependencies.anstream] version = "0.6.13" [dev-dependencies.difference] version = "2.0.0" [dev-dependencies.divan] version = "0.1.14" [dev-dependencies.glob] version = "0.3.1" [dev-dependencies.serde] version = "1.0.199" features = ["derive"] [dev-dependencies.snapbox] version = "0.6.0" features = [ "diff", "term-svg", "cmd", "examples", ] [dev-dependencies.toml] version = "0.8.0" [dev-dependencies.tryfn] version = "0.2.1" [features] default = [] simd = ["memchr"] testing-colors = [] [badges.maintenance] status = "actively-developed" [lints.clippy] bool_assert_comparison = "allow" branches_sharing_code = "allow" checked_conversions = "warn" collapsible_else_if = "allow" create_dir = "warn" dbg_macro = "warn" debug_assert_with_mut_call = "warn" doc_markdown = "warn" empty_enum = "warn" enum_glob_use = "warn" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" explicit_into_iter_loop = "warn" fallible_impl_from = "warn" filter_map_next = "warn" flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" from_iter_instead_of_collect = "warn" if_same_then_else = "allow" implicit_clone = "warn" imprecise_flops = "warn" inconsistent_struct_constructor = "warn" inefficient_to_string = "warn" infinite_loop = "warn" invalid_upcast_comparisons = "warn" large_digit_groups = "warn" large_stack_arrays = "warn" large_types_passed_by_value = "warn" let_and_return = "allow" linkedlist = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" mem_forget = "warn" mutex_integer = "warn" needless_continue = "warn" needless_for_each = "warn" negative_feature_names = "warn" path_buf_push_overwrite = "warn" ptr_as_ptr = "warn" rc_mutex = "warn" redundant_feature_names = "warn" ref_option_ref = "warn" rest_pat_in_fully_bound_structs = "warn" result_large_err = "allow" same_functions_in_if_condition = "warn" self_named_module_files = "warn" semicolon_if_nothing_returned = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" string_lit_as_bytes = "warn" string_to_string = "warn" todo = "warn" trait_duplication_in_bounds = "warn" uninlined_format_args = "warn" verbose_file_reads = "warn" wildcard_imports = "warn" zero_sized_map_values = "warn" [lints.rust] unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" [lints.rust.rust_2018_idioms] level = "warn" priority = -1 annotate-snippets-0.11.5/Cargo.toml.orig000064400000000000000000000077051046102023000162640ustar 00000000000000[workspace] resolver = "2" [workspace.package] repository = "https://github.com/rust-lang/annotate-snippets-rs" license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.66.0" # MSRV include = [ "build.rs", "src/**/*", "Cargo.toml", "Cargo.lock", "LICENSE*", "README.md", "benches/**/*", "examples/**/*" ] [workspace.lints.rust] rust_2018_idioms = { level = "warn", priority = -1 } unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" [workspace.lints.clippy] bool_assert_comparison = "allow" branches_sharing_code = "allow" checked_conversions = "warn" collapsible_else_if = "allow" create_dir = "warn" dbg_macro = "warn" debug_assert_with_mut_call = "warn" doc_markdown = "warn" empty_enum = "warn" enum_glob_use = "warn" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" explicit_into_iter_loop = "warn" fallible_impl_from = "warn" filter_map_next = "warn" flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" from_iter_instead_of_collect = "warn" if_same_then_else = "allow" implicit_clone = "warn" imprecise_flops = "warn" inconsistent_struct_constructor = "warn" inefficient_to_string = "warn" infinite_loop = "warn" invalid_upcast_comparisons = "warn" large_digit_groups = "warn" large_stack_arrays = "warn" large_types_passed_by_value = "warn" let_and_return = "allow" # sometimes good to name what you are returning linkedlist = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" mem_forget = "warn" mutex_integer = "warn" needless_continue = "warn" needless_for_each = "warn" negative_feature_names = "warn" path_buf_push_overwrite = "warn" ptr_as_ptr = "warn" rc_mutex = "warn" redundant_feature_names = "warn" ref_option_ref = "warn" rest_pat_in_fully_bound_structs = "warn" result_large_err = "allow" same_functions_in_if_condition = "warn" self_named_module_files = "warn" semicolon_if_nothing_returned = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" string_lit_as_bytes = "warn" string_to_string = "warn" todo = "warn" trait_duplication_in_bounds = "warn" uninlined_format_args = "warn" verbose_file_reads = "warn" wildcard_imports = "warn" zero_sized_map_values = "warn" [package] name = "annotate-snippets" version = "0.11.5" description = "Library for building code annotations" categories = [] keywords = ["code", "analysis", "ascii", "errors", "debug"] repository.workspace = true license.workspace = true edition.workspace = true rust-version.workspace = true include.workspace = true [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] [package.metadata.release] tag-name = "{{version}}" pre-release-replacements = [ {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/{{tag_name}}...HEAD", exactly=1}, ] [badges] maintenance = { status = "actively-developed" } [dependencies] anstyle = "1.0.4" memchr = { version = "2.7.4", optional = true } unicode-width = "0.2.0" [dev-dependencies] annotate-snippets = { path = ".", features = ["testing-colors"] } anstream = "0.6.13" difference = "2.0.0" divan = "0.1.14" glob = "0.3.1" serde = { version = "1.0.199", features = ["derive"] } snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] } toml = "0.8.0" tryfn = "0.2.1" [[bench]] name = "bench" harness = false [[test]] name = "fixtures" harness = false [features] default = [] simd = ["memchr"] testing-colors = [] [lints] workspace = true annotate-snippets-0.11.5/LICENSE-APACHE000064400000000000000000000261361046102023000153200ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. annotate-snippets-0.11.5/LICENSE-MIT000064400000000000000000000020461046102023000150220ustar 00000000000000Copyright (c) Individual contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. annotate-snippets-0.11.5/README.md000064400000000000000000000015441046102023000146470ustar 00000000000000# annotate-snippets `annotate-snippets` is a Rust library for annotation of programming code slices. [![crates.io](https://img.shields.io/crates/v/annotate-snippets.svg)](https://crates.io/crates/annotate-snippets) [![documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation] ![build status](https://github.com/rust-lang/annotate-snippets-rs/actions/workflows/ci.yml/badge.svg) The library helps visualize meta information annotating source code slices. It takes a data structure called `Snippet` on the input and produces a `String` which may look like this: ![Screenshot](./examples/expected_type.svg) Local Development ----------------- cargo build cargo test When submitting a PR please use [`cargo fmt`][] (nightly). [`cargo fmt`]: https://github.com/rust-lang/rustfmt [Documentation]: https://docs.rs/annotate-snippets/ annotate-snippets-0.11.5/benches/bench.rs000064400000000000000000000054501046102023000164240ustar 00000000000000use annotate_snippets::{Level, Renderer, Snippet}; #[divan::bench] fn simple() -> String { let source = r#") -> Option { for ann in annotations { match (ann.range.0, ann.range.1) { (None, None) => continue, (Some(start), Some(end)) if start > end_index => continue, (Some(start), Some(end)) if start >= start_index => { let label = if let Some(ref label) = ann.label { format!(" {}", label) } else { String::from("") }; return Some(format!( "{}{}{}", " ".repeat(start - start_index), "^".repeat(end - start), label )); } _ => continue, } }"#; let message = Level::Error.title("mismatched types").id("E0308").snippet( Snippet::source(source) .line_start(51) .origin("src/format.rs") .annotation( Level::Warning .span(5..19) .label("expected `Option` because of return type"), ) .annotation( Level::Error .span(26..724) .label("expected enum `std::option::Option`"), ), ); let renderer = Renderer::plain(); let rendered = renderer.render(message).to_string(); rendered } #[divan::bench(args=[0, 1, 10, 100, 1_000, 10_000, 100_000])] fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { bencher .with_inputs(|| { let line = "012345678901234567890123456789"; let mut input = String::new(); for _ in 1..=context { input.push_str(line); input.push('\n'); } let span_start = input.len() + line.len(); let span = span_start..span_start; input.push_str(line); input.push('\n'); for _ in 1..=context { input.push_str(line); input.push('\n'); } (input, span) }) .bench_values(|(input, span)| { let message = Level::Error.title("mismatched types").id("E0308").snippet( Snippet::source(&input) .fold(true) .origin("src/format.rs") .annotation( Level::Warning .span(span) .label("expected `Option` because of return type"), ), ); let renderer = Renderer::plain(); let rendered = renderer.render(message).to_string(); rendered }); } fn main() { divan::main(); } annotate-snippets-0.11.5/examples/expected_type.rs000064400000000000000000000015651046102023000204210ustar 00000000000000use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let source = r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , range: <22, 25>,"#; let message = Level::Error.title("expected type, found `22`").snippet( Snippet::source(source) .line_start(26) .origin("examples/footer.rs") .fold(true) .annotation( Level::Error .span(193..195) .label("expected struct `annotate_snippets::snippet::Slice`, found reference"), ) .annotation(Level::Info.span(34..50).label("while parsing this struct")), ); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); } annotate-snippets-0.11.5/examples/expected_type.svg000064400000000000000000000045141046102023000205710ustar 00000000000000 error: expected type, found `22` --> examples/footer.rs:29:25 | 26 | annotations: vec![SourceAnnotation { | ---------------- info: while parsing this struct 27 | label: "expected struct `annotate_snippets::snippet::Slice`, found reference" 28 | , 29 | range: <22, 25>, | ^^ expected struct `annotate_snippets::snippet::Slice`, found reference | annotate-snippets-0.11.5/examples/footer.rs000064400000000000000000000014241046102023000170470ustar 00000000000000use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let message = Level::Error .title("mismatched types") .id("E0308") .snippet( Snippet::source(" slices: vec![\"A\",") .line_start(13) .origin("src/multislice.rs") .annotation(Level::Error.span(21..24).label( "expected struct `annotate_snippets::snippet::Slice`, found reference", )), ) .footer(Level::Note.title( "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", )); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); } annotate-snippets-0.11.5/examples/footer.svg000064400000000000000000000036241046102023000172260ustar 00000000000000 error[E0308]: mismatched types --> src/multislice.rs:13:22 | 13 | slices: vec!["A", | ^^^ expected struct `annotate_snippets::snippet::Slice`, found reference | = note: expected type: `snippet::Annotation` found type: `__&__snippet::Annotation` annotate-snippets-0.11.5/examples/format.rs000064400000000000000000000026471046102023000170510ustar 00000000000000use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let source = r#") -> Option { for ann in annotations { match (ann.range.0, ann.range.1) { (None, None) => continue, (Some(start), Some(end)) if start > end_index => continue, (Some(start), Some(end)) if start >= start_index => { let label = if let Some(ref label) = ann.label { format!(" {}", label) } else { String::from("") }; return Some(format!( "{}{}{}", " ".repeat(start - start_index), "^".repeat(end - start), label )); } _ => continue, } }"#; let message = Level::Error.title("mismatched types").id("E0308").snippet( Snippet::source(source) .line_start(51) .origin("src/format.rs") .annotation( Level::Warning .span(5..19) .label("expected `Option` because of return type"), ) .annotation( Level::Error .span(26..724) .label("expected enum `std::option::Option`"), ), ); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); } annotate-snippets-0.11.5/examples/format.svg000064400000000000000000000134661046102023000172250ustar 00000000000000 error[E0308]: mismatched types --> src/format.rs:51:6 | 51 | ) -> Option<String> { | -------------- expected `Option<String>` because of return type 52 | / for ann in annotations { 53 | | match (ann.range.0, ann.range.1) { 54 | | (None, None) => continue, 55 | | (Some(start), Some(end)) if start > end_index => continue, 56 | | (Some(start), Some(end)) if start >= start_index => { 57 | | let label = if let Some(ref label) = ann.label { 58 | | format!(" {}", label) 59 | | } else { 60 | | String::from("") 61 | | }; 62 | | 63 | | return Some(format!( 64 | | "{}{}{}", 65 | | " ".repeat(start - start_index), 66 | | "^".repeat(end - start), 67 | | label 68 | | )); 69 | | } 70 | | _ => continue, 71 | | } 72 | | } | |____^ expected enum `std::option::Option` | annotate-snippets-0.11.5/examples/multislice.rs000064400000000000000000000007711046102023000177270ustar 00000000000000use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let message = Level::Error .title("mismatched types") .snippet( Snippet::source("Foo") .line_start(51) .origin("src/format.rs"), ) .snippet( Snippet::source("Faa") .line_start(129) .origin("src/display.rs"), ); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); } annotate-snippets-0.11.5/examples/multislice.svg000064400000000000000000000032301046102023000200730ustar 00000000000000 error: mismatched types --> src/format.rs | 51 | Foo | ::: src/display.rs | 129 | Faa | annotate-snippets-0.11.5/src/lib.rs000064400000000000000000000030611046102023000152670ustar 00000000000000//! A library for formatting of text or programming code snippets. //! //! It's primary purpose is to build an ASCII-graphical representation of the snippet //! with annotations. //! //! # Example //! //! ```rust #![doc = include_str!("../examples/expected_type.rs")] //! ``` //! #![doc = include_str!("../examples/expected_type.svg")] //! //! The crate uses a three stage process with two conversions between states: //! //! ```text //! Message --> Renderer --> impl Display //! ``` //! //! The input type - [Message] is a structure designed //! to align with likely output from any parser whose code snippet is to be //! annotated. //! //! The middle structure - [Renderer] is a structure designed //! to convert a snippet into an internal structure that is designed to store //! the snippet data in a way that is easy to format. //! [Renderer] also handles the user-configurable formatting //! options, such as color, or margins. //! //! Finally, `impl Display` into a final `String` output. //! //! # features //! - `testing-colors` - Makes [Renderer::styled] colors OS independent, which //! allows for easier testing when testing colored output. It should be added as //! a feature in `[dev-dependencies]`, which can be done with the following command: //! ```text //! cargo add annotate-snippets --dev --feature testing-colors //! ``` #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] #![warn(missing_debug_implementations)] pub mod renderer; mod snippet; #[doc(inline)] pub use renderer::Renderer; pub use snippet::*; annotate-snippets-0.11.5/src/renderer/display_list.rs000064400000000000000000002021461046102023000210340ustar 00000000000000//! `display_list` module stores the output model for the snippet. //! //! `DisplayList` is a central structure in the crate, which contains //! the structured list of lines to be displayed. //! //! It is made of two types of lines: `Source` and `Raw`. All `Source` lines //! are structured using four columns: //! //! ```text //! /------------ (1) Line number column. //! | /--------- (2) Line number column delimiter. //! | | /------- (3) Inline marks column. //! | | | /--- (4) Content column with the source and annotations for slices. //! | | | | //! ============================================================================= //! error[E0308]: mismatched types //! --> src/format.rs:51:5 //! | //! 151 | / fn test() -> String { //! 152 | | return "test"; //! 153 | | } //! | |___^ error: expected `String`, for `&str`. //! | //! ``` //! //! The first two lines of the example above are `Raw` lines, while the rest //! are `Source` lines. //! //! `DisplayList` does not store column alignment information, and those are //! only calculated by the implementation of `std::fmt::Display` using information such as //! styling. //! //! The above snippet has been built out of the following structure: use crate::snippet; use std::cmp::{max, min, Reverse}; use std::collections::HashMap; use std::fmt::Display; use std::ops::Range; use std::{cmp, fmt}; use crate::renderer::styled_buffer::StyledBuffer; use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH}; const ANONYMIZED_LINE_NUM: &str = "LL"; const ERROR_TXT: &str = "error"; const HELP_TXT: &str = "help"; const INFO_TXT: &str = "info"; const NOTE_TXT: &str = "note"; const WARNING_TXT: &str = "warning"; /// List of lines to be displayed. pub(crate) struct DisplayList<'a> { pub(crate) body: Vec>, pub(crate) stylesheet: &'a Stylesheet, pub(crate) anonymized_line_numbers: bool, } impl PartialEq for DisplayList<'_> { fn eq(&self, other: &Self) -> bool { self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers } } impl fmt::Debug for DisplayList<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DisplayList") .field("body", &self.body) .field("anonymized_line_numbers", &self.anonymized_line_numbers) .finish() } } impl Display for DisplayList<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let lineno_width = self.body.iter().fold(0, |max, set| { set.display_lines.iter().fold(max, |max, line| match line { DisplayLine::Source { lineno, .. } => cmp::max(lineno.unwrap_or(0), max), _ => max, }) }); let lineno_width = if lineno_width == 0 { lineno_width } else if self.anonymized_line_numbers { ANONYMIZED_LINE_NUM.len() } else { ((lineno_width as f64).log10().floor() as usize) + 1 }; let multiline_depth = self.body.iter().fold(0, |max, set| { set.display_lines.iter().fold(max, |max2, line| match line { DisplayLine::Source { annotations, .. } => cmp::max( annotations.iter().fold(max2, |max3, line| { cmp::max( match line.annotation_part { DisplayAnnotationPart::Standalone => 0, DisplayAnnotationPart::LabelContinuation => 0, DisplayAnnotationPart::MultilineStart(depth) => depth + 1, DisplayAnnotationPart::MultilineEnd(depth) => depth + 1, }, max3, ) }), max, ), _ => max2, }) }); let mut buffer = StyledBuffer::new(); for set in self.body.iter() { self.format_set(set, lineno_width, multiline_depth, &mut buffer)?; } write!(f, "{}", buffer.render(self.stylesheet)?) } } impl<'a> DisplayList<'a> { pub(crate) fn new( message: snippet::Message<'a>, stylesheet: &'a Stylesheet, anonymized_line_numbers: bool, term_width: usize, ) -> DisplayList<'a> { let body = format_message(message, term_width, anonymized_line_numbers, true); Self { body, stylesheet, anonymized_line_numbers, } } fn format_set( &self, set: &DisplaySet<'_>, lineno_width: usize, multiline_depth: usize, buffer: &mut StyledBuffer, ) -> fmt::Result { for line in &set.display_lines { set.format_line( line, lineno_width, multiline_depth, self.stylesheet, self.anonymized_line_numbers, buffer, )?; } Ok(()) } } #[derive(Debug, PartialEq)] pub(crate) struct DisplaySet<'a> { pub(crate) display_lines: Vec>, pub(crate) margin: Margin, } impl DisplaySet<'_> { fn format_label( &self, line_offset: usize, label: &[DisplayTextFragment<'_>], stylesheet: &Stylesheet, buffer: &mut StyledBuffer, ) -> fmt::Result { for fragment in label { let style = match fragment.style { DisplayTextStyle::Regular => stylesheet.none(), DisplayTextStyle::Emphasis => stylesheet.emphasis(), }; buffer.append(line_offset, fragment.content, *style); } Ok(()) } fn format_annotation( &self, line_offset: usize, annotation: &Annotation<'_>, continuation: bool, stylesheet: &Stylesheet, buffer: &mut StyledBuffer, ) -> fmt::Result { let color = get_annotation_style(&annotation.annotation_type, stylesheet); let formatted_len = if let Some(id) = &annotation.id { 2 + id.len() + annotation_type_len(&annotation.annotation_type) } else { annotation_type_len(&annotation.annotation_type) }; if continuation { for _ in 0..formatted_len + 2 { buffer.append(line_offset, " ", Style::new()); } return self.format_label(line_offset, &annotation.label, stylesheet, buffer); } if formatted_len == 0 { self.format_label(line_offset, &annotation.label, stylesheet, buffer) } else { let id = match &annotation.id { Some(id) => format!("[{id}]"), None => String::new(), }; buffer.append( line_offset, &format!("{}{}", annotation_type_str(&annotation.annotation_type), id), *color, ); if !is_annotation_empty(annotation) { buffer.append(line_offset, ": ", stylesheet.none); self.format_label(line_offset, &annotation.label, stylesheet, buffer)?; } Ok(()) } } #[inline] fn format_raw_line( &self, line_offset: usize, line: &DisplayRawLine<'_>, lineno_width: usize, stylesheet: &Stylesheet, buffer: &mut StyledBuffer, ) -> fmt::Result { match line { DisplayRawLine::Origin { path, pos, header_type, } => { let header_sigil = match header_type { DisplayHeaderType::Initial => "-->", DisplayHeaderType::Continuation => ":::", }; let lineno_color = stylesheet.line_no(); buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color); buffer.puts(line_offset, lineno_width + 4, path, stylesheet.none); if let Some((col, row)) = pos { buffer.append(line_offset, ":", stylesheet.none); buffer.append(line_offset, col.to_string().as_str(), stylesheet.none); buffer.append(line_offset, ":", stylesheet.none); buffer.append(line_offset, row.to_string().as_str(), stylesheet.none); } Ok(()) } DisplayRawLine::Annotation { annotation, source_aligned, continuation, } => { if *source_aligned { if *continuation { for _ in 0..lineno_width + 3 { buffer.append(line_offset, " ", stylesheet.none); } } else { let lineno_color = stylesheet.line_no(); for _ in 0..lineno_width + 1 { buffer.append(line_offset, " ", stylesheet.none); } buffer.append(line_offset, "=", *lineno_color); buffer.append(line_offset, " ", *lineno_color); } } self.format_annotation(line_offset, annotation, *continuation, stylesheet, buffer) } } } // Adapted from https://github.com/rust-lang/rust/blob/d371d17496f2ce3a56da76aa083f4ef157572c20/compiler/rustc_errors/src/emitter.rs#L706-L1211 #[inline] fn format_line( &self, dl: &DisplayLine<'_>, lineno_width: usize, multiline_depth: usize, stylesheet: &Stylesheet, anonymized_line_numbers: bool, buffer: &mut StyledBuffer, ) -> fmt::Result { let line_offset = buffer.num_lines(); match dl { DisplayLine::Source { lineno, inline_marks, line, annotations, } => { let lineno_color = stylesheet.line_no(); if anonymized_line_numbers && lineno.is_some() { let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$} |"); buffer.puts(line_offset, 0, &num, *lineno_color); } else { match lineno { Some(n) => { let num = format!("{n:>lineno_width$} |"); buffer.puts(line_offset, 0, &num, *lineno_color); } None => { buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color); } }; } if let DisplaySourceLine::Content { text, .. } = line { // The width of the line number, a space, pipe, and a space // `123 | ` is `lineno_width + 3`. let width_offset = lineno_width + 3; let code_offset = if multiline_depth == 0 { width_offset } else { width_offset + multiline_depth + 1 }; // Add any inline marks to the code line if !inline_marks.is_empty() || 0 < multiline_depth { format_inline_marks( line_offset, inline_marks, lineno_width, stylesheet, buffer, )?; } let text = normalize_whitespace(text); let line_len = text.as_bytes().len(); let left = self.margin.left(line_len); let right = self.margin.right(line_len); // On long lines, we strip the source line, accounting for unicode. let mut taken = 0; let code: String = text .chars() .skip(left) .take_while(|ch| { // Make sure that the trimming on the right will fall within the terminal width. // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` // is. For now, just accept that sometimes the code line will be longer than // desired. let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); if taken + next > right - left { return false; } taken += next; true }) .collect(); buffer.puts(line_offset, code_offset, &code, Style::new()); if self.margin.was_cut_left() { // We have stripped some code/whitespace from the beginning, make it clear. buffer.puts(line_offset, code_offset, "...", *lineno_color); } if self.margin.was_cut_right(line_len) { buffer.puts(line_offset, code_offset + taken - 3, "...", *lineno_color); } let left: usize = text .chars() .take(left) .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)) .sum(); let mut annotations = annotations.clone(); annotations.sort_by_key(|a| Reverse(a.range.0)); let mut annotations_positions = vec![]; let mut line_len: usize = 0; let mut p = 0; for (i, annotation) in annotations.iter().enumerate() { for (j, next) in annotations.iter().enumerate() { // This label overlaps with another one and both take space ( // they have text and are not multiline lines). if overlaps(next, annotation, 0) && annotation.has_label() && j > i && p == 0 // We're currently on the first line, move the label one line down { // If we're overlapping with an un-labelled annotation with the same span // we can just merge them in the output if next.range.0 == annotation.range.0 && next.range.1 == annotation.range.1 && !next.has_label() { continue; } // This annotation needs a new line in the output. p += 1; break; } } annotations_positions.push((p, annotation)); for (j, next) in annotations.iter().enumerate() { if j > i { let l = next .annotation .label .iter() .map(|label| label.content) .collect::>() .join("") .len() + 2; // Do not allow two labels to be in the same line if they // overlap including padding, to avoid situations like: // // fn foo(x: u32) { // -------^------ // | | // fn_spanx_span // // Both labels must have some text, otherwise they are not // overlapping. Do not add a new line if this annotation or // the next are vertical line placeholders. If either this // or the next annotation is multiline start/end, move it // to a new line so as not to overlap the horizontal lines. if (overlaps(next, annotation, l) && annotation.has_label() && next.has_label()) || (annotation.takes_space() && next.has_label()) || (annotation.has_label() && next.takes_space()) || (annotation.takes_space() && next.takes_space()) || (overlaps(next, annotation, l) && next.range.1 <= annotation.range.1 && next.has_label() && p == 0) // Avoid #42595. { // This annotation needs a new line in the output. p += 1; break; } } } line_len = max(line_len, p); } if line_len != 0 { line_len += 1; } if annotations_positions.iter().all(|(_, ann)| { matches!( ann.annotation_part, DisplayAnnotationPart::MultilineStart(_) ) }) { if let Some(max_pos) = annotations_positions.iter().map(|(pos, _)| *pos).max() { // Special case the following, so that we minimize overlapping multiline spans. // // 3 │ X0 Y0 Z0 // │ ┏━━━━━┛ │ │ < We are writing these lines // │ ┃┌───────┘ │ < by reverting the "depth" of // │ ┃│┌─────────┘ < their multilne spans. // 4 │ ┃││ X1 Y1 Z1 // 5 │ ┃││ X2 Y2 Z2 // │ ┃│└────╿──│──┘ `Z` label // │ ┃└─────│──┤ // │ ┗━━━━━━┥ `Y` is a good letter too // ╰╴ `X` is a good letter for (pos, _) in &mut annotations_positions { *pos = max_pos - *pos; } // We know then that we don't need an additional line for the span label, saving us // one line of vertical space. line_len = line_len.saturating_sub(1); } } // This is a special case where we have a multiline // annotation that is at the start of the line disregarding // any leading whitespace, and no other multiline // annotations overlap it. In this case, we want to draw // // 2 | fn foo() { // | _^ // 3 | | // 4 | | } // | |_^ test // // we simplify the output to: // // 2 | / fn foo() { // 3 | | // 4 | | } // | |_^ test if multiline_depth == 1 && annotations_positions.len() == 1 && annotations_positions .first() .map_or(false, |(_, annotation)| { matches!( annotation.annotation_part, DisplayAnnotationPart::MultilineStart(_) ) && text .chars() .take(annotation.range.0) .all(|c| c.is_whitespace()) }) { let (_, ann) = annotations_positions.remove(0); let style = get_annotation_style(&ann.annotation_type, stylesheet); buffer.putc(line_offset, 3 + lineno_width, '/', *style); } // Draw the column separator for any extra lines that were // created // // After this we will have: // // 2 | fn foo() { // | // | // | // 3 | // 4 | } // | if !annotations_positions.is_empty() { for pos in 0..=line_len { buffer.putc( line_offset + pos + 1, lineno_width + 1, '|', stylesheet.line_no, ); } } // Write the horizontal lines for multiline annotations // (only the first and last lines need this). // // After this we will have: // // 2 | fn foo() { // | __________ // | // | // 3 | // 4 | } // | _ for &(pos, annotation) in &annotations_positions { let style = get_annotation_style(&annotation.annotation_type, stylesheet); let pos = pos + 1; match annotation.annotation_part { DisplayAnnotationPart::MultilineStart(depth) | DisplayAnnotationPart::MultilineEnd(depth) => { for col in width_offset + depth ..(code_offset + annotation.range.0).saturating_sub(left) { buffer.putc(line_offset + pos, col + 1, '_', *style); } } _ => {} } } // Write the vertical lines for labels that are on a different line as the underline. // // After this we will have: // // 2 | fn foo() { // | __________ // | | | // | | // 3 | | // 4 | | } // | |_ for &(pos, annotation) in &annotations_positions { let style = get_annotation_style(&annotation.annotation_type, stylesheet); let pos = pos + 1; if pos > 1 && (annotation.has_label() || annotation.takes_space()) { for p in line_offset + 2..=line_offset + pos { buffer.putc( p, (code_offset + annotation.range.0).saturating_sub(left), '|', *style, ); } } match annotation.annotation_part { DisplayAnnotationPart::MultilineStart(depth) => { for p in line_offset + pos + 1..line_offset + line_len + 2 { buffer.putc(p, width_offset + depth, '|', *style); } } DisplayAnnotationPart::MultilineEnd(depth) => { for p in line_offset..=line_offset + pos { buffer.putc(p, width_offset + depth, '|', *style); } } _ => {} } } // Add in any inline marks for any extra lines that have // been created. Output should look like above. for inline_mark in inline_marks { let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type; let style = get_annotation_style(&inline_mark.annotation_type, stylesheet); if annotations_positions.is_empty() { buffer.putc(line_offset, width_offset + depth, '|', *style); } else { for p in line_offset..=line_offset + line_len + 1 { buffer.putc(p, width_offset + depth, '|', *style); } } } // Write the labels on the annotations that actually have a label. // // After this we will have: // // 2 | fn foo() { // | __________ // | | // | something about `foo` // 3 | // 4 | } // | _ test for &(pos, annotation) in &annotations_positions { if !is_annotation_empty(&annotation.annotation) { let style = get_annotation_style(&annotation.annotation_type, stylesheet); let mut formatted_len = if let Some(id) = &annotation.annotation.id { 2 + id.len() + annotation_type_len(&annotation.annotation.annotation_type) } else { annotation_type_len(&annotation.annotation.annotation_type) }; let (pos, col) = if pos == 0 { (pos + 1, (annotation.range.1 + 1).saturating_sub(left)) } else { (pos + 2, annotation.range.0.saturating_sub(left)) }; if annotation.annotation_part == DisplayAnnotationPart::LabelContinuation { formatted_len = 0; } else if formatted_len != 0 { formatted_len += 2; let id = match &annotation.annotation.id { Some(id) => format!("[{id}]"), None => String::new(), }; buffer.puts( line_offset + pos, col + code_offset, &format!( "{}{}: ", annotation_type_str(&annotation.annotation_type), id ), *style, ); } else { formatted_len = 0; } let mut before = 0; for fragment in &annotation.annotation.label { let inner_col = before + formatted_len + col + code_offset; buffer.puts(line_offset + pos, inner_col, fragment.content, *style); before += fragment.content.len(); } } } // Sort from biggest span to smallest span so that smaller spans are // represented in the output: // // x | fn foo() // | ^^^---^^ // | | | // | | something about `foo` // | something about `fn foo()` annotations_positions.sort_by_key(|(_, ann)| { // Decreasing order. When annotations share the same length, prefer `Primary`. Reverse(ann.len()) }); // Write the underlines. // // After this we will have: // // 2 | fn foo() { // | ____-_____^ // | | // | something about `foo` // 3 | // 4 | } // | _^ test for &(_, annotation) in &annotations_positions { let mark = match annotation.annotation_type { DisplayAnnotationType::Error => '^', DisplayAnnotationType::Warning => '-', DisplayAnnotationType::Info => '-', DisplayAnnotationType::Note => '-', DisplayAnnotationType::Help => '-', DisplayAnnotationType::None => ' ', }; let style = get_annotation_style(&annotation.annotation_type, stylesheet); for p in annotation.range.0..annotation.range.1 { buffer.putc( line_offset + 1, (code_offset + p).saturating_sub(left), mark, *style, ); } } } else if !inline_marks.is_empty() { format_inline_marks( line_offset, inline_marks, lineno_width, stylesheet, buffer, )?; } Ok(()) } DisplayLine::Fold { inline_marks } => { buffer.puts(line_offset, 0, "...", *stylesheet.line_no()); if !inline_marks.is_empty() || 0 < multiline_depth { format_inline_marks( line_offset, inline_marks, lineno_width, stylesheet, buffer, )?; } Ok(()) } DisplayLine::Raw(line) => { self.format_raw_line(line_offset, line, lineno_width, stylesheet, buffer) } } } } /// Inline annotation which can be used in either Raw or Source line. #[derive(Clone, Debug, PartialEq)] pub(crate) struct Annotation<'a> { pub(crate) annotation_type: DisplayAnnotationType, pub(crate) id: Option<&'a str>, pub(crate) label: Vec>, } /// A single line used in `DisplayList`. #[derive(Debug, PartialEq)] pub(crate) enum DisplayLine<'a> { /// A line with `lineno` portion of the slice. Source { lineno: Option, inline_marks: Vec, line: DisplaySourceLine<'a>, annotations: Vec>, }, /// A line indicating a folded part of the slice. Fold { inline_marks: Vec }, /// A line which is displayed outside of slices. Raw(DisplayRawLine<'a>), } /// A source line. #[derive(Debug, PartialEq)] pub(crate) enum DisplaySourceLine<'a> { /// A line with the content of the Snippet. Content { text: &'a str, range: (usize, usize), // meta information for annotation placement. end_line: EndLine, }, /// An empty source line. Empty, } #[derive(Clone, Debug, PartialEq)] pub(crate) struct DisplaySourceAnnotation<'a> { pub(crate) annotation: Annotation<'a>, pub(crate) range: (usize, usize), pub(crate) annotation_type: DisplayAnnotationType, pub(crate) annotation_part: DisplayAnnotationPart, } impl DisplaySourceAnnotation<'_> { fn has_label(&self) -> bool { !self .annotation .label .iter() .all(|label| label.content.is_empty()) } // Length of this annotation as displayed in the stderr output fn len(&self) -> usize { // Account for usize underflows if self.range.1 > self.range.0 { self.range.1 - self.range.0 } else { self.range.0 - self.range.1 } } fn takes_space(&self) -> bool { // Multiline annotations always have to keep vertical space. matches!( self.annotation_part, DisplayAnnotationPart::MultilineStart(_) | DisplayAnnotationPart::MultilineEnd(_) ) } } /// Raw line - a line which does not have the `lineno` part and is not considered /// a part of the snippet. #[derive(Debug, PartialEq)] pub(crate) enum DisplayRawLine<'a> { /// A line which provides information about the location of the given /// slice in the project structure. Origin { path: &'a str, pos: Option<(usize, usize)>, header_type: DisplayHeaderType, }, /// An annotation line which is not part of any snippet. Annotation { annotation: Annotation<'a>, /// If set to `true`, the annotation will be aligned to the /// lineno delimiter of the snippet. source_aligned: bool, /// If set to `true`, only the label of the `Annotation` will be /// displayed. It allows for a multiline annotation to be aligned /// without displaying the meta information (`type` and `id`) to be /// displayed on each line. continuation: bool, }, } /// An inline text fragment which any label is composed of. #[derive(Clone, Debug, PartialEq)] pub(crate) struct DisplayTextFragment<'a> { pub(crate) content: &'a str, pub(crate) style: DisplayTextStyle, } /// A style for the `DisplayTextFragment` which can be visually formatted. /// /// This information may be used to emphasis parts of the label. #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum DisplayTextStyle { Regular, Emphasis, } /// An indicator of what part of the annotation a given `Annotation` is. #[derive(Debug, Clone, PartialEq)] pub(crate) enum DisplayAnnotationPart { /// A standalone, single-line annotation. Standalone, /// A continuation of a multi-line label of an annotation. LabelContinuation, /// A line starting a multiline annotation. MultilineStart(usize), /// A line ending a multiline annotation. MultilineEnd(usize), } /// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. #[derive(Debug, Clone, PartialEq)] pub(crate) struct DisplayMark { pub(crate) mark_type: DisplayMarkType, pub(crate) annotation_type: DisplayAnnotationType, } /// A type of the `DisplayMark`. #[derive(Debug, Clone, PartialEq)] pub(crate) enum DisplayMarkType { /// A mark indicating a multiline annotation going through the current line. AnnotationThrough(usize), } /// A type of the `Annotation` which may impact the sigils, style or text displayed. /// /// There are several ways to uses this information when formatting the `DisplayList`: /// /// * An annotation may display the name of the type like `error` or `info`. /// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`. /// * `ColorStylesheet` may use different colors for different annotations. #[derive(Debug, Clone, PartialEq)] pub(crate) enum DisplayAnnotationType { None, Error, Warning, Info, Note, Help, } impl From for DisplayAnnotationType { fn from(at: snippet::Level) -> Self { match at { snippet::Level::Error => DisplayAnnotationType::Error, snippet::Level::Warning => DisplayAnnotationType::Warning, snippet::Level::Info => DisplayAnnotationType::Info, snippet::Level::Note => DisplayAnnotationType::Note, snippet::Level::Help => DisplayAnnotationType::Help, } } } /// Information whether the header is the initial one or a consequitive one /// for multi-slice cases. // TODO: private #[derive(Debug, Clone, PartialEq)] pub(crate) enum DisplayHeaderType { /// Initial header is the first header in the snippet. Initial, /// Continuation marks all headers of following slices in the snippet. Continuation, } struct CursorLines<'a>(&'a str); impl CursorLines<'_> { fn new(src: &str) -> CursorLines<'_> { CursorLines(src) } } #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) enum EndLine { Eof, Lf, Crlf, } impl EndLine { /// The number of characters this line ending occupies in bytes. pub(crate) fn len(self) -> usize { match self { EndLine::Eof => 0, EndLine::Lf => 1, EndLine::Crlf => 2, } } } impl<'a> Iterator for CursorLines<'a> { type Item = (&'a str, EndLine); fn next(&mut self) -> Option { if self.0.is_empty() { None } else { self.0 .find('\n') .map(|x| { let ret = if 0 < x { if self.0.as_bytes()[x - 1] == b'\r' { (&self.0[..x - 1], EndLine::Crlf) } else { (&self.0[..x], EndLine::Lf) } } else { ("", EndLine::Lf) }; self.0 = &self.0[x + 1..]; ret }) .or_else(|| { let ret = Some((self.0, EndLine::Eof)); self.0 = ""; ret }) } } } fn format_message( message: snippet::Message<'_>, term_width: usize, anonymized_line_numbers: bool, primary: bool, ) -> Vec> { let snippet::Message { level, id, title, footer, snippets, } = message; let mut sets = vec![]; let body = if !snippets.is_empty() || primary { vec![format_title(level, id, title)] } else { format_footer(level, id, title) }; for (idx, snippet) in snippets.into_iter().enumerate() { let snippet = fold_prefix_suffix(snippet); sets.push(format_snippet( snippet, idx == 0, !footer.is_empty(), term_width, anonymized_line_numbers, )); } if let Some(first) = sets.first_mut() { for line in body { first.display_lines.insert(0, line); } } else { sets.push(DisplaySet { display_lines: body, margin: Margin::new(0, 0, 0, 0, DEFAULT_TERM_WIDTH, 0), }); } for annotation in footer { sets.extend(format_message( annotation, term_width, anonymized_line_numbers, false, )); } sets } fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::from(level), id, label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)), }, source_aligned: false, continuation: false, }) } fn format_footer<'a>( level: crate::Level, id: Option<&'a str>, label: &'a str, ) -> Vec> { let mut result = vec![]; for (i, line) in label.lines().enumerate() { result.push(DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::from(level), id, label: format_label(Some(line), None), }, source_aligned: true, continuation: i != 0, })); } result } fn format_label( label: Option<&str>, style: Option, ) -> Vec> { let mut result = vec![]; if let Some(label) = label { let element_style = style.unwrap_or(DisplayTextStyle::Regular); result.push(DisplayTextFragment { content: label, style: element_style, }); } result } fn format_snippet( snippet: snippet::Snippet<'_>, is_first: bool, has_footer: bool, term_width: usize, anonymized_line_numbers: bool, ) -> DisplaySet<'_> { let main_range = snippet.annotations.first().map(|x| x.range.start); let origin = snippet.origin; let need_empty_header = origin.is_some() || is_first; let mut body = format_body( snippet, need_empty_header, has_footer, term_width, anonymized_line_numbers, ); let header = format_header(origin, main_range, &body.display_lines, is_first); if let Some(header) = header { body.display_lines.insert(0, header); } body } #[inline] // TODO: option_zip fn zip_opt(a: Option, b: Option) -> Option<(A, B)> { a.and_then(|a| b.map(|b| (a, b))) } fn format_header<'a>( origin: Option<&'a str>, main_range: Option, body: &[DisplayLine<'_>], is_first: bool, ) -> Option> { let display_header = if is_first { DisplayHeaderType::Initial } else { DisplayHeaderType::Continuation }; if let Some((main_range, path)) = zip_opt(main_range, origin) { let mut col = 1; let mut line_offset = 1; for item in body { if let DisplayLine::Source { line: DisplaySourceLine::Content { text, range, end_line, }, lineno, .. } = item { if main_range >= range.0 && main_range < range.1 + max(*end_line as usize, 1) { let char_column = text[0..(main_range - range.0).min(text.len())] .chars() .count(); col = char_column + 1; line_offset = lineno.unwrap_or(1); break; } } } return Some(DisplayLine::Raw(DisplayRawLine::Origin { path, pos: Some((line_offset, col)), header_type: display_header, })); } if let Some(path) = origin { return Some(DisplayLine::Raw(DisplayRawLine::Origin { path, pos: None, header_type: display_header, })); } None } fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_> { if !snippet.fold { return snippet; } let ann_start = snippet .annotations .iter() .map(|ann| ann.range.start) .min() .unwrap_or(0); if let Some(before_new_start) = snippet.source[0..ann_start].rfind('\n') { let new_start = before_new_start + 1; let line_offset = newline_count(&snippet.source[..new_start]); snippet.line_start += line_offset; snippet.source = &snippet.source[new_start..]; for ann in &mut snippet.annotations { let range_start = ann.range.start - new_start; let range_end = ann.range.end - new_start; ann.range = range_start..range_end; } } let ann_end = snippet .annotations .iter() .map(|ann| ann.range.end) .max() .unwrap_or(snippet.source.len()); if let Some(end_offset) = snippet.source[ann_end..].find('\n') { let new_end = ann_end + end_offset; snippet.source = &snippet.source[..new_end]; } snippet } fn newline_count(body: &str) -> usize { #[cfg(feature = "simd")] { memchr::memchr_iter(b'\n', body.as_bytes()).count() } #[cfg(not(feature = "simd"))] { body.lines().count() } } fn fold_body(body: Vec>) -> Vec> { const INNER_CONTEXT: usize = 1; const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1; let mut lines = vec![]; let mut unhighlighed_lines = vec![]; for line in body { match &line { DisplayLine::Source { annotations, .. } => { if annotations.is_empty() { unhighlighed_lines.push(line); } else { if lines.is_empty() { // Ignore leading unhighlighed lines unhighlighed_lines.clear(); } match unhighlighed_lines.len() { 0 => {} n if n <= INNER_UNFOLD_SIZE => { // Rather than render `...`, don't fold lines.append(&mut unhighlighed_lines); } _ => { lines.extend(unhighlighed_lines.drain(..INNER_CONTEXT)); let inline_marks = lines .last() .and_then(|line| { if let DisplayLine::Source { ref inline_marks, .. } = line { let inline_marks = inline_marks.clone(); Some(inline_marks) } else { None } }) .unwrap_or_default(); lines.push(DisplayLine::Fold { inline_marks: inline_marks.clone(), }); unhighlighed_lines .drain(..unhighlighed_lines.len().saturating_sub(INNER_CONTEXT)); lines.append(&mut unhighlighed_lines); } } lines.push(line); } } _ => { unhighlighed_lines.push(line); } } } lines } fn format_body( snippet: snippet::Snippet<'_>, need_empty_header: bool, has_footer: bool, term_width: usize, anonymized_line_numbers: bool, ) -> DisplaySet<'_> { let source_len = snippet.source.len(); if let Some(bigger) = snippet.annotations.iter().find_map(|x| { // Allow highlighting one past the last character in the source. if source_len + 1 < x.range.end { Some(&x.range) } else { None } }) { panic!("SourceAnnotation range `{bigger:?}` is beyond the end of buffer `{source_len}`") } let mut body = vec![]; let mut current_line = snippet.line_start; let mut current_index = 0; let mut whitespace_margin = usize::MAX; let mut span_left_margin = usize::MAX; let mut span_right_margin = 0; let mut label_right_margin = 0; let mut max_line_len = 0; let mut depth_map: HashMap = HashMap::new(); let mut current_depth = 0; let mut annotations = snippet.annotations; let ranges = annotations .iter() .map(|a| a.range.clone()) .collect::>(); // We want to merge multiline annotations that have the same range into one // multiline annotation to save space. This is done by making any duplicate // multiline annotations into a single-line annotation pointing at the end // of the range. // // 3 | X0 Y0 Z0 // | _____^ // | | ____| // | || ___| // | ||| // 4 | ||| X1 Y1 Z1 // 5 | ||| X2 Y2 Z2 // | ||| ^ // | |||____| // | ||____`X` is a good letter // | |____`Y` is a good letter too // | `Z` label // Should be // error: foo // --> test.rs:3:3 // | // 3 | / X0 Y0 Z0 // 4 | | X1 Y1 Z1 // 5 | | X2 Y2 Z2 // | | ^ // | |____| // | `X` is a good letter // | `Y` is a good letter too // | `Z` label // | ranges.iter().enumerate().for_each(|(r_idx, range)| { annotations .iter_mut() .enumerate() .skip(r_idx + 1) .for_each(|(ann_idx, ann)| { // Skip if the annotation's index matches the range index if ann_idx != r_idx // We only want to merge multiline annotations && snippet.source[ann.range.clone()].lines().count() > 1 // We only want to merge annotations that have the same range && ann.range.start == range.start && ann.range.end == range.end { ann.range.start = ann.range.end.saturating_sub(1); } }); }); annotations.sort_by_key(|a| a.range.start); let mut annotations = annotations.into_iter().enumerate().collect::>(); for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() { let line_length: usize = line.len(); let line_range = (current_index, current_index + line_length); let end_line_size = end_line.len(); body.push(DisplayLine::Source { lineno: Some(current_line), inline_marks: vec![], line: DisplaySourceLine::Content { text: line, range: line_range, end_line, }, annotations: vec![], }); let leading_whitespace = line .chars() .take_while(|c| c.is_whitespace()) .map(|c| { match c { // Tabs are displayed as 4 spaces '\t' => 4, _ => 1, } }) .sum(); if line.chars().any(|c| !c.is_whitespace()) { whitespace_margin = min(whitespace_margin, leading_whitespace); } max_line_len = max(max_line_len, line_length); let line_start_index = line_range.0; let line_end_index = line_range.1; current_line += 1; current_index += line_length + end_line_size; // It would be nice to use filter_drain here once it's stable. annotations.retain(|(key, annotation)| { let body_idx = idx; let annotation_type = match annotation.level { snippet::Level::Error => DisplayAnnotationType::None, snippet::Level::Warning => DisplayAnnotationType::None, _ => DisplayAnnotationType::from(annotation.level), }; let label_right = annotation.label.map_or(0, |label| label.len() + 1); match annotation.range { // This handles if the annotation is on the next line. We add // the `end_line_size` to account for annotating the line end. Range { start, .. } if start > line_end_index + end_line_size => true, // This handles the case where an annotation is contained // within the current line including any line-end characters. Range { start, end } if start >= line_start_index // We add at least one to `line_end_index` to allow // highlighting the end of a file && end <= line_end_index + max(end_line_size, 1) => { if let DisplayLine::Source { ref mut annotations, .. } = body[body_idx] { let annotation_start_col = line [0..(start - line_start_index).min(line_length)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::(); let mut annotation_end_col = line [0..(end - line_start_index).min(line_length)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::(); if annotation_start_col == annotation_end_col { // At least highlight something annotation_end_col += 1; } span_left_margin = min(span_left_margin, annotation_start_col); span_right_margin = max(span_right_margin, annotation_end_col); label_right_margin = max(label_right_margin, annotation_end_col + label_right); let range = (annotation_start_col, annotation_end_col); annotations.push(DisplaySourceAnnotation { annotation: Annotation { annotation_type, id: None, label: format_label(annotation.label, None), }, range, annotation_type: DisplayAnnotationType::from(annotation.level), annotation_part: DisplayAnnotationPart::Standalone, }); } false } // This handles the case where a multiline annotation starts // somewhere on the current line, including any line-end chars Range { start, end } if start >= line_start_index // The annotation can start on a line ending && start <= line_end_index + end_line_size.saturating_sub(1) && end > line_end_index => { if let DisplayLine::Source { ref mut annotations, .. } = body[body_idx] { let annotation_start_col = line [0..(start - line_start_index).min(line_length)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::(); let annotation_end_col = annotation_start_col + 1; span_left_margin = min(span_left_margin, annotation_start_col); span_right_margin = max(span_right_margin, annotation_end_col); label_right_margin = max(label_right_margin, annotation_end_col + label_right); let range = (annotation_start_col, annotation_end_col); annotations.push(DisplaySourceAnnotation { annotation: Annotation { annotation_type, id: None, label: vec![], }, range, annotation_type: DisplayAnnotationType::from(annotation.level), annotation_part: DisplayAnnotationPart::MultilineStart(current_depth), }); depth_map.insert(*key, current_depth); current_depth += 1; } true } // This handles the case where a multiline annotation starts // somewhere before this line and ends after it as well Range { start, end } if start < line_start_index && end > line_end_index + max(end_line_size, 1) => { if let DisplayLine::Source { ref mut inline_marks, .. } = body[body_idx] { let depth = depth_map.get(key).cloned().unwrap_or_default(); inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationThrough(depth), annotation_type: DisplayAnnotationType::from(annotation.level), }); } true } // This handles the case where a multiline annotation ends // somewhere on the current line, including any line-end chars Range { start, end } if start < line_start_index && end >= line_start_index // We add at least one to `line_end_index` to allow // highlighting the end of a file && end <= line_end_index + max(end_line_size, 1) => { if let DisplayLine::Source { ref mut annotations, .. } = body[body_idx] { let end_mark = line[0..(end - line_start_index).min(line_length)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::() .saturating_sub(1); // If the annotation ends on a line-end character, we // need to annotate one past the end of the line let (end_mark, end_plus_one) = if end > line_end_index // Special case for highlighting the end of a file || (end == line_end_index + 1 && end_line_size == 0) { (end_mark + 1, end_mark + 2) } else { (end_mark, end_mark + 1) }; span_left_margin = min(span_left_margin, end_mark); span_right_margin = max(span_right_margin, end_plus_one); label_right_margin = max(label_right_margin, end_plus_one + label_right); let range = (end_mark, end_plus_one); let depth = depth_map.remove(key).unwrap_or(0); annotations.push(DisplaySourceAnnotation { annotation: Annotation { annotation_type, id: None, label: format_label(annotation.label, None), }, range, annotation_type: DisplayAnnotationType::from(annotation.level), annotation_part: DisplayAnnotationPart::MultilineEnd(depth), }); } false } _ => true, } }); // Reset the depth counter, but only after we've processed all // annotations for a given line. let max = depth_map.len(); if current_depth > max { current_depth = max; } } if snippet.fold { body = fold_body(body); } if need_empty_header { body.insert( 0, DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, annotations: vec![], }, ); } if has_footer { body.push(DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, annotations: vec![], }); } else if let Some(DisplayLine::Source { .. }) = body.last() { body.push(DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, annotations: vec![], }); } let max_line_num_len = if anonymized_line_numbers { ANONYMIZED_LINE_NUM.len() } else { current_line.to_string().len() }; let width_offset = 3 + max_line_num_len; if span_left_margin == usize::MAX { span_left_margin = 0; } let margin = Margin::new( whitespace_margin, span_left_margin, span_right_margin, label_right_margin, term_width.saturating_sub(width_offset), max_line_len, ); DisplaySet { display_lines: body, margin, } } #[inline] fn annotation_type_str(annotation_type: &DisplayAnnotationType) -> &'static str { match annotation_type { DisplayAnnotationType::Error => ERROR_TXT, DisplayAnnotationType::Help => HELP_TXT, DisplayAnnotationType::Info => INFO_TXT, DisplayAnnotationType::Note => NOTE_TXT, DisplayAnnotationType::Warning => WARNING_TXT, DisplayAnnotationType::None => "", } } fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize { match annotation_type { DisplayAnnotationType::Error => ERROR_TXT.len(), DisplayAnnotationType::Help => HELP_TXT.len(), DisplayAnnotationType::Info => INFO_TXT.len(), DisplayAnnotationType::Note => NOTE_TXT.len(), DisplayAnnotationType::Warning => WARNING_TXT.len(), DisplayAnnotationType::None => 0, } } fn get_annotation_style<'a>( annotation_type: &DisplayAnnotationType, stylesheet: &'a Stylesheet, ) -> &'a Style { match annotation_type { DisplayAnnotationType::Error => stylesheet.error(), DisplayAnnotationType::Warning => stylesheet.warning(), DisplayAnnotationType::Info => stylesheet.info(), DisplayAnnotationType::Note => stylesheet.note(), DisplayAnnotationType::Help => stylesheet.help(), DisplayAnnotationType::None => stylesheet.none(), } } #[inline] fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { annotation .label .iter() .all(|fragment| fragment.content.is_empty()) } // We replace some characters so the CLI output is always consistent and underlines aligned. const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[ ('\t', " "), // We do our own tab replacement ('\u{200D}', ""), // Replace ZWJ with nothing for consistent terminal output of grapheme clusters. ('\u{202A}', ""), // The following unicode text flow control characters are inconsistently ('\u{202B}', ""), // supported across CLIs and can cause confusion due to the bytes on disk ('\u{202D}', ""), // not corresponding to the visible source code, so we replace them always. ('\u{202E}', ""), ('\u{2066}', ""), ('\u{2067}', ""), ('\u{2068}', ""), ('\u{202C}', ""), ('\u{2069}', ""), ]; fn normalize_whitespace(str: &str) -> String { let mut s = str.to_owned(); for (c, replacement) in OUTPUT_REPLACEMENTS { s = s.replace(*c, replacement); } s } fn overlaps( a1: &DisplaySourceAnnotation<'_>, a2: &DisplaySourceAnnotation<'_>, padding: usize, ) -> bool { (a2.range.0..a2.range.1).contains(&a1.range.0) || (a1.range.0..a1.range.1 + padding).contains(&a2.range.0) } fn format_inline_marks( line: usize, inline_marks: &[DisplayMark], lineno_width: usize, stylesheet: &Stylesheet, buf: &mut StyledBuffer, ) -> fmt::Result { for mark in inline_marks.iter() { let annotation_style = get_annotation_style(&mark.annotation_type, stylesheet); match mark.mark_type { DisplayMarkType::AnnotationThrough(depth) => { buf.putc(line, 3 + lineno_width + depth, '|', *annotation_style); } }; } Ok(()) } annotate-snippets-0.11.5/src/renderer/margin.rs000064400000000000000000000113701046102023000176060ustar 00000000000000use std::cmp::{max, min}; const ELLIPSIS_PASSING: usize = 6; const LONG_WHITESPACE: usize = 20; const LONG_WHITESPACE_PADDING: usize = 4; #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct Margin { /// The available whitespace in the left that can be consumed when centering. whitespace_left: usize, /// The column of the beginning of left-most span. span_left: usize, /// The column of the end of right-most span. span_right: usize, /// The beginning of the line to be displayed. computed_left: usize, /// The end of the line to be displayed. computed_right: usize, /// The current width of the terminal. 140 by default and in tests. term_width: usize, /// The end column of a span label, including the span. Doesn't account for labels not in the /// same line as the span. label_right: usize, } impl Margin { pub(crate) fn new( whitespace_left: usize, span_left: usize, span_right: usize, label_right: usize, term_width: usize, max_line_len: usize, ) -> Self { // The 6 is padding to give a bit of room for `...` when displaying: // ``` // error: message // --> file.rs:16:58 // | // 16 | ... fn foo(self) -> Self::Bar { // | ^^^^^^^^^ // ``` let mut m = Margin { whitespace_left: whitespace_left.saturating_sub(ELLIPSIS_PASSING), span_left: span_left.saturating_sub(ELLIPSIS_PASSING), span_right: span_right + ELLIPSIS_PASSING, computed_left: 0, computed_right: 0, term_width, label_right: label_right + ELLIPSIS_PASSING, }; m.compute(max_line_len); m } pub(crate) fn was_cut_left(&self) -> bool { self.computed_left > 0 } pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { let right = if self.computed_right == self.span_right || self.computed_right == self.label_right { // Account for the "..." padding given above. Otherwise we end up with code lines that // do fit but end in "..." as if they were trimmed. self.computed_right - ELLIPSIS_PASSING } else { self.computed_right }; right < line_len && self.computed_left + self.term_width < line_len } fn compute(&mut self, max_line_len: usize) { // When there's a lot of whitespace (>20), we want to trim it as it is useless. self.computed_left = if self.whitespace_left > LONG_WHITESPACE { self.whitespace_left - (LONG_WHITESPACE - LONG_WHITESPACE_PADDING) // We want some padding. } else { 0 }; // We want to show as much as possible, max_line_len is the right-most boundary for the // relevant code. self.computed_right = max(max_line_len, self.computed_left); if self.computed_right - self.computed_left > self.term_width { // Trimming only whitespace isn't enough, let's get craftier. if self.label_right - self.whitespace_left <= self.term_width { // Attempt to fit the code window only trimming whitespace. self.computed_left = self.whitespace_left; self.computed_right = self.computed_left + self.term_width; } else if self.label_right - self.span_left <= self.term_width { // Attempt to fit the code window considering only the spans and labels. let padding_left = (self.term_width - (self.label_right - self.span_left)) / 2; self.computed_left = self.span_left.saturating_sub(padding_left); self.computed_right = self.computed_left + self.term_width; } else if self.span_right - self.span_left <= self.term_width { // Attempt to fit the code window considering the spans and labels plus padding. let padding_left = (self.term_width - (self.span_right - self.span_left)) / 5 * 2; self.computed_left = self.span_left.saturating_sub(padding_left); self.computed_right = self.computed_left + self.term_width; } else { // Mostly give up but still don't show the full line. self.computed_left = self.span_left; self.computed_right = self.span_right; } } } pub(crate) fn left(&self, line_len: usize) -> usize { min(self.computed_left, line_len) } pub(crate) fn right(&self, line_len: usize) -> usize { if line_len.saturating_sub(self.computed_left) <= self.term_width { line_len } else { min(line_len, self.computed_right) } } } annotate-snippets-0.11.5/src/renderer/mod.rs000064400000000000000000000113671046102023000171160ustar 00000000000000//! The renderer for [`Message`]s //! //! # Example //! ``` //! use annotate_snippets::{Renderer, Snippet, Level}; //! let snippet = Level::Error.title("mismatched types") //! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs")) //! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs")); //! //! let renderer = Renderer::styled(); //! println!("{}", renderer.render(snippet)); mod display_list; mod margin; mod styled_buffer; pub(crate) mod stylesheet; use crate::snippet::Message; pub use anstyle::*; use display_list::DisplayList; use margin::Margin; use std::fmt::Display; use stylesheet::Stylesheet; pub const DEFAULT_TERM_WIDTH: usize = 140; /// A renderer for [`Message`]s #[derive(Clone, Debug)] pub struct Renderer { anonymized_line_numbers: bool, term_width: usize, stylesheet: Stylesheet, } impl Renderer { /// No terminal styling pub const fn plain() -> Self { Self { anonymized_line_numbers: false, term_width: DEFAULT_TERM_WIDTH, stylesheet: Stylesheet::plain(), } } /// Default terminal styling /// /// # Note /// When testing styled terminal output, see the [`testing-colors` feature](crate#features) pub const fn styled() -> Self { const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors"); const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS { AnsiColor::BrightCyan.on_default() } else { AnsiColor::BrightBlue.on_default() }; Self { stylesheet: Stylesheet { error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD), warning: if USE_WINDOWS_COLORS { AnsiColor::BrightYellow.on_default() } else { AnsiColor::Yellow.on_default() } .effects(Effects::BOLD), info: BRIGHT_BLUE.effects(Effects::BOLD), note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD), help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD), line_no: BRIGHT_BLUE.effects(Effects::BOLD), emphasis: if USE_WINDOWS_COLORS { AnsiColor::BrightWhite.on_default() } else { Style::new() } .effects(Effects::BOLD), none: Style::new(), }, ..Self::plain() } } /// Anonymize line numbers /// /// This enables (or disables) line number anonymization. When enabled, line numbers are replaced /// with `LL`. /// /// # Example /// /// ```text /// --> $DIR/whitespace-trimming.rs:4:193 /// | /// LL | ... let _: () = 42; /// | ^^ expected (), found integer /// | /// ``` pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self { self.anonymized_line_numbers = anonymized_line_numbers; self } // Set the terminal width pub const fn term_width(mut self, term_width: usize) -> Self { self.term_width = term_width; self } /// Set the output style for `error` pub const fn error(mut self, style: Style) -> Self { self.stylesheet.error = style; self } /// Set the output style for `warning` pub const fn warning(mut self, style: Style) -> Self { self.stylesheet.warning = style; self } /// Set the output style for `info` pub const fn info(mut self, style: Style) -> Self { self.stylesheet.info = style; self } /// Set the output style for `note` pub const fn note(mut self, style: Style) -> Self { self.stylesheet.note = style; self } /// Set the output style for `help` pub const fn help(mut self, style: Style) -> Self { self.stylesheet.help = style; self } /// Set the output style for line numbers pub const fn line_no(mut self, style: Style) -> Self { self.stylesheet.line_no = style; self } /// Set the output style for emphasis pub const fn emphasis(mut self, style: Style) -> Self { self.stylesheet.emphasis = style; self } /// Set the output style for none pub const fn none(mut self, style: Style) -> Self { self.stylesheet.none = style; self } /// Render a snippet into a `Display`able object pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a { DisplayList::new( msg, &self.stylesheet, self.anonymized_line_numbers, self.term_width, ) } } annotate-snippets-0.11.5/src/renderer/styled_buffer.rs000064400000000000000000000061631046102023000211720ustar 00000000000000//! Adapted from [styled_buffer] //! //! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs use crate::renderer::stylesheet::Stylesheet; use anstyle::Style; use std::fmt; use std::fmt::Write; #[derive(Debug)] pub(crate) struct StyledBuffer { lines: Vec>, } #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct StyledChar { ch: char, style: Style, } impl StyledChar { pub(crate) const SPACE: Self = StyledChar::new(' ', Style::new()); pub(crate) const fn new(ch: char, style: Style) -> StyledChar { StyledChar { ch, style } } } impl StyledBuffer { pub(crate) fn new() -> StyledBuffer { StyledBuffer { lines: vec![] } } fn ensure_lines(&mut self, line: usize) { if line >= self.lines.len() { self.lines.resize(line + 1, Vec::new()); } } pub(crate) fn render(&self, stylesheet: &Stylesheet) -> Result { let mut str = String::new(); for (i, line) in self.lines.iter().enumerate() { let mut current_style = stylesheet.none; for ch in line { if ch.style != current_style { if !line.is_empty() { write!(str, "{}", current_style.render_reset())?; } current_style = ch.style; write!(str, "{}", current_style.render())?; } write!(str, "{}", ch.ch)?; } write!(str, "{}", current_style.render_reset())?; if i != self.lines.len() - 1 { writeln!(str)?; } } Ok(str) } /// Sets `chr` with `style` for given `line`, `col`. /// If `line` does not exist in our buffer, adds empty lines up to the given /// and fills the last line with unstyled whitespace. pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) { self.ensure_lines(line); if col >= self.lines[line].len() { self.lines[line].resize(col + 1, StyledChar::SPACE); } self.lines[line][col] = StyledChar::new(chr, style); } /// Sets `string` with `style` for given `line`, starting from `col`. /// If `line` does not exist in our buffer, adds empty lines up to the given /// and fills the last line with unstyled whitespace. pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) { let mut n = col; for c in string.chars() { self.putc(line, n, c, style); n += 1; } } /// For given `line` inserts `string` with `style` after old content of that line, /// adding lines if needed pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) { if line >= self.lines.len() { self.puts(line, 0, string, style); } else { let col = self.lines[line].len(); self.puts(line, col, string, style); } } pub(crate) fn num_lines(&self) -> usize { self.lines.len() } } annotate-snippets-0.11.5/src/renderer/stylesheet.rs000064400000000000000000000025121046102023000205200ustar 00000000000000use anstyle::Style; #[derive(Clone, Copy, Debug)] pub(crate) struct Stylesheet { pub(crate) error: Style, pub(crate) warning: Style, pub(crate) info: Style, pub(crate) note: Style, pub(crate) help: Style, pub(crate) line_no: Style, pub(crate) emphasis: Style, pub(crate) none: Style, } impl Default for Stylesheet { fn default() -> Self { Self::plain() } } impl Stylesheet { pub(crate) const fn plain() -> Self { Self { error: Style::new(), warning: Style::new(), info: Style::new(), note: Style::new(), help: Style::new(), line_no: Style::new(), emphasis: Style::new(), none: Style::new(), } } } impl Stylesheet { pub(crate) fn error(&self) -> &Style { &self.error } pub(crate) fn warning(&self) -> &Style { &self.warning } pub(crate) fn info(&self) -> &Style { &self.info } pub(crate) fn note(&self) -> &Style { &self.note } pub(crate) fn help(&self) -> &Style { &self.help } pub(crate) fn line_no(&self) -> &Style { &self.line_no } pub(crate) fn emphasis(&self) -> &Style { &self.emphasis } pub(crate) fn none(&self) -> &Style { &self.none } } annotate-snippets-0.11.5/src/snippet.rs000064400000000000000000000074451046102023000162150ustar 00000000000000//! Structures used as an input for the library. //! //! Example: //! //! ``` //! use annotate_snippets::*; //! //! Level::Error.title("mismatched types") //! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs")) //! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs")); //! ``` use std::ops::Range; /// Primary structure provided for formatting /// /// See [`Level::title`] to create a [`Message`] #[derive(Debug)] pub struct Message<'a> { pub(crate) level: Level, pub(crate) id: Option<&'a str>, pub(crate) title: &'a str, pub(crate) snippets: Vec>, pub(crate) footer: Vec>, } impl<'a> Message<'a> { pub fn id(mut self, id: &'a str) -> Self { self.id = Some(id); self } pub fn snippet(mut self, slice: Snippet<'a>) -> Self { self.snippets.push(slice); self } pub fn snippets(mut self, slice: impl IntoIterator>) -> Self { self.snippets.extend(slice); self } pub fn footer(mut self, footer: Message<'a>) -> Self { self.footer.push(footer); self } pub fn footers(mut self, footer: impl IntoIterator>) -> Self { self.footer.extend(footer); self } } /// Structure containing the slice of text to be annotated and /// basic information about the location of the slice. /// /// One `Snippet` is meant to represent a single, continuous, /// slice of source code that you want to annotate. #[derive(Debug)] pub struct Snippet<'a> { pub(crate) origin: Option<&'a str>, pub(crate) line_start: usize, pub(crate) source: &'a str, pub(crate) annotations: Vec>, pub(crate) fold: bool, } impl<'a> Snippet<'a> { pub fn source(source: &'a str) -> Self { Self { origin: None, line_start: 1, source, annotations: vec![], fold: false, } } pub fn line_start(mut self, line_start: usize) -> Self { self.line_start = line_start; self } pub fn origin(mut self, origin: &'a str) -> Self { self.origin = Some(origin); self } pub fn annotation(mut self, annotation: Annotation<'a>) -> Self { self.annotations.push(annotation); self } pub fn annotations(mut self, annotation: impl IntoIterator>) -> Self { self.annotations.extend(annotation); self } /// Hide lines without [`Annotation`]s pub fn fold(mut self, fold: bool) -> Self { self.fold = fold; self } } /// An annotation for a [`Snippet`]. /// /// See [`Level::span`] to create a [`Annotation`] #[derive(Debug)] pub struct Annotation<'a> { /// The byte range of the annotation in the `source` string pub(crate) range: Range, pub(crate) label: Option<&'a str>, pub(crate) level: Level, } impl<'a> Annotation<'a> { pub fn label(mut self, label: &'a str) -> Self { self.label = Some(label); self } } /// Types of annotations. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Level { /// Error annotations are displayed using red color and "^" character. Error, /// Warning annotations are displayed using blue color and "-" character. Warning, Info, Note, Help, } impl Level { pub fn title(self, title: &str) -> Message<'_> { Message { level: self, id: None, title, snippets: vec![], footer: vec![], } } /// Create a [`Annotation`] with the given span for a [`Snippet`] pub fn span<'a>(self, span: Range) -> Annotation<'a> { Annotation { range: span, label: None, level: self, } } }