diff --git a/Cargo.lock b/Cargo.lock index 487c23e..912f34f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aligned-vec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" - [[package]] name = "allocator-api2" version = "0.2.21" @@ -44,15 +38,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" -[[package]] -name = "ansi_colours" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14eec43e0298190790f41679fe69ef7a829d2a2ddd78c8c00339e84710e435fe" -dependencies = [ - "rgb", -] - [[package]] name = "anstream" version = "0.6.18" @@ -109,58 +94,12 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" -[[package]] -name = "arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" - -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "av1-grain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" -dependencies = [ - "arrayvec", -] - [[package]] name = "backtrace" version = "0.3.74" @@ -182,43 +121,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bat" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab792c2ad113a666f08856c88cdec0a62d732559b1f3982eedf0142571e669a" -dependencies = [ - "ansi_colours", - "anyhow", - "bincode", - "bytesize", - "clircle", - "console", - "content_inspector", - "encoding_rs", - "flate2", - "globset", - "home", - "indexmap", - "itertools 0.13.0", - "nu-ansi-term 0.50.1", - "once_cell", - "path_abs", - "plist", - "regex", - "semver", - "serde", - "serde_derive", - "serde_with", - "serde_yaml", - "syntect", - "terminal-colorsaurus", - "thiserror 1.0.69", - "toml", - "unicode-width 0.1.14", - "walkdir", -] - [[package]] name = "better-panic" version = "0.3.0" @@ -229,42 +131,6 @@ dependencies = [ "console", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.9.0" @@ -274,58 +140,18 @@ dependencies = [ "serde", ] -[[package]] -name = "bitstream-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" - -[[package]] -name = "bstr" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "built" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" - [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" -[[package]] -name = "bytemuck" -version = "1.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -[[package]] -name = "bytesize" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" - [[package]] name = "cassowary" version = "0.3.0" @@ -347,27 +173,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "cc" -version = "1.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" -dependencies = [ - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -460,22 +265,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "clircle" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d9334f725b46fb9bed8580b9b47a932587e044fadb344ed7fa98774b067ac1a" -dependencies = [ - "cfg-if", - "windows", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "colorchoice" version = "1.0.3" @@ -506,28 +295,9 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.0", "windows-sys 0.59.0", ] -[[package]] -name = "content_inspector" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" -dependencies = [ - "memchr", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - [[package]] name = "criterion" version = "0.5.1" @@ -597,7 +367,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.0", + "bitflags", "crossterm_winapi", "filedescriptor", "mio", @@ -659,15 +429,6 @@ dependencies = [ "syn", ] -[[package]] -name = "deranged" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" -dependencies = [ - "powerfmt", -] - [[package]] name = "devicons" version = "0.6.12" @@ -710,15 +471,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -741,46 +493,12 @@ version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" -[[package]] -name = "exr" -version = "1.73.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" -dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fancy-regex" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" -dependencies = [ - "bit-set", - "regex", -] - [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - [[package]] name = "filedescriptor" version = "0.8.3" @@ -792,16 +510,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "flate2" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "fnv" version = "1.0.7" @@ -875,16 +583,6 @@ dependencies = [ "pin-utils", ] -[[package]] -name = "gag" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a713bee13966e9fbffdf7193af71d54a6b35a0bb34997cd6c9519ebeb5005972" -dependencies = [ - "filedescriptor", - "tempfile", -] - [[package]] name = "getrandom" version = "0.2.15" @@ -908,35 +606,12 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] -[[package]] -name = "gif" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "globset" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - [[package]] name = "half" version = "2.6.0" @@ -970,15 +645,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "human-panic" version = "2.0.2" @@ -1001,45 +667,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "image" -version = "0.25.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "exr", - "gif", - "image-webp", - "num-traits", - "png", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imgref" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" - [[package]] name = "indexmap" version = "2.9.0" @@ -1048,7 +675,6 @@ checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", - "serde", ] [[package]] @@ -1070,17 +696,6 @@ dependencies = [ "syn", ] -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "is-terminal" version = "0.4.16" @@ -1107,15 +722,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -1131,22 +737,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "jobserver" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" -dependencies = [ - "getrandom 0.3.2", - "libc", -] - -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - [[package]] name = "js-sys" version = "0.3.77" @@ -1157,41 +747,48 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-regex" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex-lite", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - [[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" -[[package]] -name = "libfuzzer-sys" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" -dependencies = [ - "arbitrary", - "cc", -] - [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags", "libc", ] @@ -1223,15 +820,6 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - [[package]] name = "lru" version = "0.12.5" @@ -1250,16 +838,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - [[package]] name = "memchr" version = "2.7.4" @@ -1279,7 +857,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", - "simd-adler32", ] [[package]] @@ -1294,12 +871,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - [[package]] name = "nom" version = "7.1.3" @@ -1310,12 +881,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1326,15 +891,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "nu-ansi-term" -version = "0.50.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "nucleo" version = "0.5.0" @@ -1356,53 +912,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1427,28 +936,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "onig" -version = "6.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" -dependencies = [ - "bitflags 1.3.2", - "libc", - "once_cell", - "onig_sys", -] - -[[package]] -name = "onig_sys" -version = "69.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "oorandom" version = "11.1.5" @@ -1507,15 +994,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "path_abs" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ef02f6342ac01d8a93b65f96db53fe68a92a15f41144f97fb00a9e669633c3" -dependencies = [ - "std_prelude", -] - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1528,25 +1006,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plist" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" -dependencies = [ - "base64", - "indexmap", - "quick-xml", - "serde", - "time", -] - [[package]] name = "plotters" version = "0.3.7" @@ -1575,34 +1034,6 @@ dependencies = [ "plotters-backend", ] -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro2" version = "1.0.94" @@ -1612,49 +1043,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "profiling" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quick-xml" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" -dependencies = [ - "memchr", -] - [[package]] name = "quote" version = "1.0.40" @@ -1670,43 +1058,13 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] - [[package]] name = "ratatui" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.9.0", + "bitflags", "cassowary", "compact_str", "crossterm", @@ -1722,56 +1080,6 @@ dependencies = [ "unicode-width 0.2.0", ] -[[package]] -name = "rav1e" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" -dependencies = [ - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools 0.12.1", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "once_cell", - "paste", - "profiling", - "rand", - "rand_chacha", - "simd_helpers", - "system-deps", - "thiserror 1.0.69", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", -] - [[package]] name = "rayon" version = "1.10.0" @@ -1798,7 +1106,7 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ - "bitflags 2.9.0", + "bitflags", ] [[package]] @@ -1844,6 +1152,12 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[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.6.29" @@ -1856,15 +1170,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "rgb" -version = "0.8.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] - [[package]] name = "roff" version = "0.2.2" @@ -1889,7 +1194,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1902,7 +1207,7 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ - "bitflags 2.9.0", + "bitflags", "errno", "libc", "linux-raw-sys 0.9.4", @@ -1936,12 +1241,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" - [[package]] name = "serde" version = "1.0.219" @@ -1983,42 +1282,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" -dependencies = [ - "serde", - "serde_derive", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -2028,12 +1291,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook" version = "0.3.17" @@ -2064,21 +1321,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - [[package]] name = "simdutf8" version = "0.1.5" @@ -2107,12 +1349,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "std_prelude" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe" - [[package]] name = "strsim" version = "0.11.1" @@ -2152,53 +1388,12 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syntect" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" -dependencies = [ - "bincode", - "bitflags 1.3.2", - "fancy-regex", - "flate2", - "fnv", - "once_cell", - "onig", - "regex-syntax 0.8.5", - "serde", - "serde_derive", - "serde_json", - "thiserror 1.0.69", - "walkdir", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "television" version = "0.11.9" dependencies = [ "anyhow", "base64", - "bat", "better-panic", "clap", "clap_mangen", @@ -2207,21 +1402,18 @@ dependencies = [ "crossterm", "devicons", "directories", - "gag", "human-panic", - "image", + "lazy-regex", "nom", "nucleo", "parking_lot", "ratatui", - "regex", "rustc-hash", "serde", "signal-hook", "simdutf8", "smallvec", "strum", - "syntect", "television-derive", "tempfile", "thiserror 2.0.12", @@ -2255,32 +1447,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "terminal-colorsaurus" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7afe4c174a3cbfb52ebcb11b28965daf74fe9111d4e07e40689d05af06e26e8" -dependencies = [ - "cfg-if", - "libc", - "memchr", - "mio", - "terminal-trx", - "windows-sys 0.59.0", - "xterm-color", -] - -[[package]] -name = "terminal-trx" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975b4233aefa1b02456d5e53b22c61653c743e308c51cf4181191d8ce41753ab" -dependencies = [ - "cfg-if", - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -2331,48 +1497,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tiff" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - -[[package]] -name = "time" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" - -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -2418,7 +1542,6 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ - "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -2497,7 +1620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", - "nu-ansi-term 0.46.0", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", @@ -2543,12 +1666,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "utf8parse" version = "0.2.2" @@ -2564,29 +1681,12 @@ dependencies = [ "getrandom 0.3.2", ] -[[package]] -name = "v_frame" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - [[package]] name = "walkdir" version = "2.5.0" @@ -2680,12 +1780,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "weezl" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" - [[package]] name = "winapi" version = "0.3.9" @@ -2717,59 +1811,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" -dependencies = [ - "windows-core", - "windows-targets", -] - -[[package]] -name = "windows-core" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-targets", -] - -[[package]] -name = "windows-implement" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -2867,55 +1908,5 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", -] - -[[package]] -name = "xterm-color" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de5f056fb9dc8b7908754867544e26145767187aaac5a98495e88ad7cb8a80f" - -[[package]] -name = "zerocopy" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "zune-jpeg" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" -dependencies = [ - "zune-core", + "bitflags", ] diff --git a/Cargo.toml b/Cargo.toml index f216d07..6152ed0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,19 +48,18 @@ ratatui = { version = "0.29", features = ["serde", "macros"] } better-panic = "0.3" signal-hook = "0.3" human-panic = "2.0" +# FIXME: we probably don't need strum anymore strum = { version = "0.26", features = ["derive"] } -regex = "1.11" parking_lot = "0.12" nom = "7.1" thiserror = "2.0" simdutf8 = { version = "0.1", optional = true } smallvec = { version = "1.13", features = ["const_generics"] } -gag = "1.0" nucleo = "0.5" toml = "0.8" -image = "0.25" -syntect = { package = "syntect", version = "5.2", default-features = false } -bat = { package = "bat", version = "0.25", default-features = false } +lazy-regex = { version = "3.4.1", features = [ + "lite", +], default-features = false } # target specific dependencies @@ -82,11 +81,7 @@ tempfile = "3.16.0" [features] simd = ["dep:simdutf8"] zero-copy = [] -# Use fancy-regex for aarch64 Linux -fancy = ["syntect/regex-fancy", "bat/regex-fancy"] -# Use oniguruma for other platforms -onig = ["syntect/regex-onig", "bat/regex-onig"] -default = ["zero-copy", "simd", "onig"] +default = ["zero-copy", "simd"] [build-dependencies] diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..781deb1 --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ +- checkout the `which` crate for searching binaries diff --git a/television/app.rs b/television/app.rs index b7554c2..95ba068 100644 --- a/television/app.rs +++ b/television/app.rs @@ -4,9 +4,7 @@ use anyhow::Result; use tokio::sync::mpsc; use tracing::{debug, trace}; -use crate::channels::{ - entry::Entry, preview::PreviewType, OnAir, TelevisionChannel, -}; +use crate::channels::{entry::Entry, OnAir, TelevisionChannel}; use crate::config::{default_tick_rate, Config}; use crate::keymap::Keymap; use crate::render::UiState; @@ -123,7 +121,6 @@ impl From for AppOutput { ActionOutcome::Input(input) => Self { selected_entries: Some(FxHashSet::from_iter([Entry::new( input, - PreviewType::None, )])), }, ActionOutcome::None => Self { @@ -445,7 +442,7 @@ mod test { #[test] fn test_maybe_select_1() { let mut app = App::new( - TelevisionChannel::Stdin(StdinChannel::new(PreviewType::None)), + TelevisionChannel::Stdin(StdinChannel::new(None)), Config::default(), None, AppOptions::default(), @@ -453,14 +450,13 @@ mod test { app.television .results_picker .entries - .push(Entry::new("test".to_string(), PreviewType::None)); + .push(Entry::new("test".to_string())); let outcome = app.maybe_select_1(); assert!(outcome.is_some()); assert_eq!( outcome.unwrap(), ActionOutcome::Entries(FxHashSet::from_iter([Entry::new( "test".to_string(), - PreviewType::None )])) ); } diff --git a/television/channels/cable.rs b/television/channels/cable.rs index a90e9b1..4710cd5 100644 --- a/television/channels/cable.rs +++ b/television/channels/cable.rs @@ -6,12 +6,7 @@ use prototypes::{CableChannelPrototype, DEFAULT_DELIMITER}; use rustc_hash::{FxBuildHasher, FxHashSet}; use tracing::debug; -use crate::channels::preview::parse_preview_type; -use crate::channels::{ - entry::Entry, - preview::{PreviewCommand, PreviewType}, - OnAir, -}; +use crate::channels::{entry::Entry, preview::PreviewCommand, OnAir}; use crate::matcher::Matcher; use crate::matcher::{config::Config, injector::Injector}; use crate::utils::command::shell_command; @@ -23,7 +18,7 @@ pub struct Channel { pub name: String, matcher: Matcher, entries_command: String, - preview_type: PreviewType, + preview_command: Option, selected_entries: FxHashSet, crawl_handle: tokio::task::JoinHandle<()>, } @@ -72,19 +67,10 @@ impl Channel { interactive, injector, )); - let preview_kind = match preview_command { - Some(command) => { - parse_preview_type(&command).unwrap_or_else(|_| { - panic!("Invalid preview command: {command}") - }) - } - None => PreviewType::None, - }; - debug!("Preview kind: {:?}", preview_kind); Self { matcher, entries_command: entries_command.to_string(), - preview_type: preview_kind, + preview_command, name: name.to_string(), selected_entries: HashSet::with_hasher(FxBuildHasher), crawl_handle, @@ -149,8 +135,7 @@ impl OnAir for Channel { .into_iter() .map(|item| { let path = item.matched_string; - Entry::new(path, self.preview_type.clone()) - .with_name_match_indices(&item.match_indices) + Entry::new(path).with_name_match_indices(&item.match_indices) }) .collect() } @@ -158,7 +143,7 @@ impl OnAir for Channel { fn get_result(&self, index: u32) -> Option { self.matcher.get_result(index).map(|item| { let path = item.matched_string; - Entry::new(path, self.preview_type.clone()) + Entry::new(path) }) } @@ -189,6 +174,6 @@ impl OnAir for Channel { fn shutdown(&self) {} fn supports_preview(&self) -> bool { - self.preview_type != PreviewType::None + self.preview_command.is_some() } } diff --git a/television/channels/entry.rs b/television/channels/entry.rs index 9f95ad8..7a2d553 100644 --- a/television/channels/entry.rs +++ b/television/channels/entry.rs @@ -2,8 +2,6 @@ use std::hash::{Hash, Hasher}; use devicons::FileIcon; -use crate::channels::preview::PreviewType; - // NOTE: having an enum for entry types would be nice since it would allow // having a nicer implementation for transitions between channels. This would // permit implementing `From` for channels which would make the @@ -24,8 +22,6 @@ pub struct Entry { pub icon: Option, /// The optional line number associated with the entry. pub line_number: Option, - /// The type of preview associated with the entry. - pub preview_type: PreviewType, } impl Hash for Entry { @@ -85,10 +81,10 @@ impl Entry { /// /// Additional fields can be set using the builder pattern. /// ``` - /// use television::channels::{entry::Entry, preview::PreviewType}; + /// use television::channels::entry::Entry; /// use devicons::FileIcon; /// - /// let entry = Entry::new("name".to_string(), PreviewType::EnvVar) + /// let entry = Entry::new("name".to_string()) /// .with_value("value".to_string()) /// .with_name_match_indices(&vec![0]) /// .with_value_match_indices(&vec![0]) @@ -98,12 +94,11 @@ impl Entry { /// /// # Arguments /// * `name` - The name of the entry. - /// * `preview_type` - The type of preview associated with the entry. /// /// # Returns /// A new entry with the given name and preview type. /// The other fields are set to `None` by default. - pub fn new(name: String, preview_type: PreviewType) -> Self { + pub fn new(name: String) -> Self { Self { name, value: None, @@ -111,7 +106,6 @@ impl Entry { value_match_ranges: None, icon: None, line_number: None, - preview_type, } } @@ -156,7 +150,6 @@ pub const ENTRY_PLACEHOLDER: Entry = Entry { value_match_ranges: None, icon: None, line_number: None, - preview_type: PreviewType::EnvVar, }; #[cfg(test)] @@ -196,7 +189,6 @@ mod tests { value_match_ranges: None, icon: None, line_number: None, - preview_type: PreviewType::Basic, }; assert_eq!(entry.stdout_repr(), "test name with spaces"); } @@ -210,7 +202,6 @@ mod tests { value_match_ranges: None, icon: None, line_number: Some(a), - preview_type: PreviewType::Basic, }; assert_eq!(entry.stdout_repr(), "test_file_name.rs:10"); } diff --git a/television/channels/preview.rs b/television/channels/preview.rs index bc1c303..2539fd0 100644 --- a/television/channels/preview.rs +++ b/television/channels/preview.rs @@ -1,9 +1,10 @@ use std::fmt::Display; -use anyhow::Result; -use regex::Regex; -use strum::EnumString; -use tracing::debug; +use lazy_regex::{regex, Lazy, Regex}; + +use super::entry::Entry; + +static CMD_RE: &Lazy = regex!(r"\{(\d+)\}"); #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] pub struct PreviewCommand { @@ -18,6 +19,40 @@ impl PreviewCommand { delimiter: delimiter.to_string(), } } + + /// Format the command with the entry name and provided placeholders. + /// + /// # Example + /// ``` + /// use television::channels::{preview::PreviewCommand, entry::Entry}; + /// + /// let command = PreviewCommand { + /// command: "something {} {2} {0}".to_string(), + /// delimiter: ":".to_string(), + /// }; + /// let entry = Entry::new("a:given:entry:to:preview".to_string()); + /// + /// let formatted_command = command.format_with(&entry); + /// + /// assert_eq!(formatted_command, "something 'a:given:entry:to:preview' 'entry' 'a'"); + /// ``` + pub fn format_with(&self, entry: &Entry) -> String { + let parts = entry.name.split(&self.delimiter).collect::>(); + + let mut formatted_command = self + .command + .replace("{}", format!("'{}'", entry.name).as_str()); + + formatted_command = CMD_RE + .replace_all(&formatted_command, |caps: ®ex::Captures| { + let index = + caps.get(1).unwrap().as_str().parse::().unwrap(); + format!("'{}'", parts[index]) + }) + .to_string(); + + formatted_command + } } impl Display for PreviewCommand { @@ -26,38 +61,59 @@ impl Display for PreviewCommand { } } -#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, EnumString)] -#[strum(serialize_all = "snake_case")] -pub enum PreviewType { - Basic, - EnvVar, - Files, - #[strum(disabled)] - Command(PreviewCommand), - #[default] - None, -} +#[cfg(test)] +mod tests { + use super::*; + use crate::channels::entry::Entry; -/// Parses the preview command to determine the preview type. -/// -/// This checks if the command matches the builtin pattern `:{preview_type}:` -/// and then falls back to the command type if it doesn't. -/// -/// # Example: -/// ``` -/// use television::channels::preview::{parse_preview_type, PreviewCommand, PreviewType}; -/// -/// let command = PreviewCommand::new("cat {0}", ":"); -/// let preview_type = parse_preview_type(&command).unwrap(); -/// assert_eq!(preview_type, PreviewType::Command(command)); -/// ``` -pub fn parse_preview_type(command: &PreviewCommand) -> Result { - debug!("Parsing preview kind for command: {:?}", command); - let re = Regex::new(r"^\:(\w+)\:$").unwrap(); - if let Some(captures) = re.captures(&command.command) { - let preview_type = PreviewType::try_from(&captures[1])?; - Ok(preview_type) - } else { - Ok(PreviewType::Command(command.clone())) + #[test] + fn test_format_command() { + let command = PreviewCommand { + command: "something {} {2} {0}".to_string(), + delimiter: ":".to_string(), + }; + let entry = Entry::new("an:entry:to:preview".to_string()); + let formatted_command = command.format_with(&entry); + + assert_eq!( + formatted_command, + "something 'an:entry:to:preview' 'to' 'an'" + ); + } + + #[test] + fn test_format_command_no_placeholders() { + let command = PreviewCommand { + command: "something".to_string(), + delimiter: ":".to_string(), + }; + let entry = Entry::new("an:entry:to:preview".to_string()); + let formatted_command = command.format_with(&entry); + + assert_eq!(formatted_command, "something"); + } + + #[test] + fn test_format_command_with_global_placeholder_only() { + let command = PreviewCommand { + command: "something {}".to_string(), + delimiter: ":".to_string(), + }; + let entry = Entry::new("an:entry:to:preview".to_string()); + let formatted_command = command.format_with(&entry); + + assert_eq!(formatted_command, "something 'an:entry:to:preview'"); + } + + #[test] + fn test_format_command_with_positional_placeholders_only() { + let command = PreviewCommand { + command: "something {0} -t {2}".to_string(), + delimiter: ":".to_string(), + }; + let entry = Entry::new("an:entry:to:preview".to_string()); + let formatted_command = command.format_with(&entry); + + assert_eq!(formatted_command, "something 'an' -t 'to'"); } } diff --git a/television/channels/remote_control.rs b/television/channels/remote_control.rs index 01cdd4b..f32484d 100644 --- a/television/channels/remote_control.rs +++ b/television/channels/remote_control.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use crate::channels::cable::prototypes::CableChannels; -use crate::channels::{entry::Entry, preview::PreviewType}; +use crate::channels::entry::Entry; use crate::channels::{OnAir, TelevisionChannel}; use crate::matcher::{config::Config, Matcher}; use anyhow::Result; @@ -83,7 +83,7 @@ impl OnAir for RemoteControl { .into_iter() .map(|item| { let path = item.matched_string; - Entry::new(path, PreviewType::Basic) + Entry::new(path) .with_name_match_indices(&item.match_indices) .with_icon(CABLE_ICON) }) @@ -93,7 +93,7 @@ impl OnAir for RemoteControl { fn get_result(&self, index: u32) -> Option { self.matcher.get_result(index).map(|item| { let path = item.matched_string; - Entry::new(path, PreviewType::Basic).with_icon(TV_ICON) + Entry::new(path).with_icon(TV_ICON) }) } diff --git a/television/channels/stdin.rs b/television/channels/stdin.rs index cf97b9b..c29af12 100644 --- a/television/channels/stdin.rs +++ b/television/channels/stdin.rs @@ -7,19 +7,19 @@ use std::{ use rustc_hash::{FxBuildHasher, FxHashSet}; use tracing::debug; -use super::OnAir; -use crate::channels::{entry::Entry, preview::PreviewType}; +use super::{preview::PreviewCommand, OnAir}; +use crate::channels::entry::Entry; use crate::matcher::{config::Config, injector::Injector, Matcher}; pub struct Channel { matcher: Matcher, - preview_type: PreviewType, + preview_command: Option, selected_entries: FxHashSet, instream_handle: std::thread::JoinHandle<()>, } impl Channel { - pub fn new(preview_type: PreviewType) -> Self { + pub fn new(preview_command: Option) -> Self { let matcher = Matcher::new(Config::default()); let injector = matcher.injector(); @@ -27,7 +27,7 @@ impl Channel { Self { matcher, - preview_type, + preview_command, selected_entries: HashSet::with_hasher(FxBuildHasher), instream_handle, } @@ -36,7 +36,7 @@ impl Channel { impl Default for Channel { fn default() -> Self { - Self::new(PreviewType::default()) + Self::new(None) } } @@ -60,7 +60,7 @@ where Self { matcher, - preview_type: PreviewType::default(), + preview_command: None, selected_entries: HashSet::with_hasher(FxBuildHasher), instream_handle, } @@ -112,16 +112,16 @@ impl OnAir for Channel { .map(|item| { // NOTE: we're passing `PreviewType::Basic` here just as a placeholder // to avoid storing the preview command multiple times for each item. - Entry::new(item.matched_string, PreviewType::Basic) + Entry::new(item.matched_string) .with_name_match_indices(&item.match_indices) }) .collect() } fn get_result(&self, index: u32) -> Option { - self.matcher.get_result(index).map(|item| { - Entry::new(item.matched_string, self.preview_type.clone()) - }) + self.matcher + .get_result(index) + .map(|item| Entry::new(item.matched_string)) } fn selected_entries(&self) -> &FxHashSet { @@ -151,6 +151,6 @@ impl OnAir for Channel { fn shutdown(&self) {} fn supports_preview(&self) -> bool { - self.preview_type != PreviewType::None + self.preview_command.is_some() } } diff --git a/television/cli/mod.rs b/television/cli/mod.rs index 4d8056b..6a56035 100644 --- a/television/cli/mod.rs +++ b/television/cli/mod.rs @@ -7,9 +7,7 @@ use tracing::debug; use crate::channels::cable::prototypes::{ CableChannelPrototype, CableChannels, }; -use crate::channels::preview::{ - parse_preview_type, PreviewCommand, PreviewType, -}; +use crate::channels::preview::PreviewCommand; use crate::cli::args::{Cli, Command}; use crate::config::{KeyBindings, DEFAULT_CHANNEL}; use crate::{ @@ -23,7 +21,7 @@ pub mod args; #[derive(Debug, Clone)] pub struct PostProcessedCli { pub channel: CableChannelPrototype, - pub preview_kind: PreviewType, + pub preview_command: Option, pub no_preview: bool, pub tick_rate: Option, pub frame_rate: Option, @@ -44,7 +42,7 @@ impl Default for PostProcessedCli { fn default() -> Self { Self { channel: CableChannelPrototype::default(), - preview_kind: PreviewType::None, + preview_command: None, no_preview: false, tick_rate: None, frame_rate: None, @@ -75,19 +73,10 @@ impl From for PostProcessedCli { }); // parse the preview command if provided - let preview_kind = cli - .preview - .map(|preview| PreviewCommand { - command: preview, - delimiter: cli.delimiter.clone(), - }) - .map_or(PreviewType::None, |preview_command| { - parse_preview_type(&preview_command) - .map_err(|e| { - cli_parsing_error_exit(&e.to_string()); - }) - .unwrap() - }); + let preview_command = cli.preview.map(|preview| PreviewCommand { + command: preview, + delimiter: cli.delimiter.clone(), + }); let channel: CableChannelPrototype; let working_directory: Option; @@ -129,7 +118,7 @@ impl From for PostProcessedCli { Self { channel, - preview_kind, + preview_command, no_preview: cli.no_preview, tick_rate: cli.tick_rate, frame_rate: cli.frame_rate, @@ -303,10 +292,7 @@ Data directory: {data_dir_path}" #[cfg(test)] mod tests { - use crate::{ - action::Action, channels::preview::PreviewType, config::Binding, - event::Key, - }; + use crate::{action::Action, config::Binding, event::Key}; use super::*; @@ -328,8 +314,8 @@ mod tests { CableChannelPrototype::default(), ); assert_eq!( - post_processed_cli.preview_kind, - PreviewType::Command(PreviewCommand { + post_processed_cli.preview_command, + Some(PreviewCommand { command: "bat -n --color=always {}".to_string(), delimiter: ":".to_string() }) @@ -364,34 +350,6 @@ mod tests { assert_eq!(post_processed_cli.command, None); } - #[test] - fn test_builtin_previewer_files() { - let cli = Cli { - channel: Some("files".to_string()), - preview: Some(":files:".to_string()), - delimiter: ":".to_string(), - ..Default::default() - }; - - let post_processed_cli: PostProcessedCli = cli.into(); - - assert_eq!(post_processed_cli.preview_kind, PreviewType::Files); - } - - #[test] - fn test_builtin_previewer_env() { - let cli = Cli { - channel: Some("files".to_string()), - preview: Some(":env_var:".to_string()), - delimiter: ":".to_string(), - ..Default::default() - }; - - let post_processed_cli: PostProcessedCli = cli.into(); - - assert_eq!(post_processed_cli.preview_kind, PreviewType::EnvVar); - } - #[test] fn test_custom_keybindings() { let cli = Cli { diff --git a/television/config/mod.rs b/television/config/mod.rs index 25907ba..b8b5ca2 100644 --- a/television/config/mod.rs +++ b/television/config/mod.rs @@ -9,7 +9,6 @@ use anyhow::{Context, Result}; use directories::ProjectDirs; pub use keybindings::merge_keybindings; pub use keybindings::{parse_key, Binding, KeyBindings}; -use previewers::PreviewersConfig; use serde::{Deserialize, Serialize}; use shell_integration::ShellIntegrationConfig; pub use themes::Theme; @@ -17,7 +16,6 @@ use tracing::{debug, warn}; pub use ui::UiConfig; mod keybindings; -mod previewers; pub mod shell_integration; mod themes; mod ui; @@ -70,9 +68,6 @@ pub struct Config { /// UI configuration #[serde(default)] pub ui: UiConfig, - /// Previewers configuration - #[serde(default)] - pub previewers: PreviewersConfig, /// Shell integration configuration #[serde(default)] pub shell_integration: ShellIntegrationConfig, @@ -201,7 +196,6 @@ impl Config { application: user.application, keybindings: user.keybindings, ui: user.ui, - previewers: user.previewers, shell_integration: user.shell_integration, } } @@ -330,7 +324,6 @@ mod tests { assert_eq!(config.application, default_config.application); assert_eq!(config.keybindings, default_config.keybindings); assert_eq!(config.ui, default_config.ui); - assert_eq!(config.previewers, default_config.previewers); // backwards compatibility assert_eq!( config.shell_integration.commands, @@ -384,8 +377,6 @@ mod tests { default_config.application.frame_rate = 30.0; default_config.ui.ui_scale = 40; default_config.ui.theme = "television".to_string(); - default_config.previewers.file.theme = - "Visual Studio Dark".to_string(); default_config.keybindings.extend({ let mut map = FxHashMap::default(); map.insert( @@ -408,7 +399,6 @@ mod tests { assert_eq!(config.application, default_config.application); assert_eq!(config.keybindings, default_config.keybindings); assert_eq!(config.ui, default_config.ui); - assert_eq!(config.previewers, default_config.previewers); assert_eq!( config.shell_integration.commands, [(&String::from("git add"), &String::from("git-diff"))] diff --git a/television/config/previewers.rs b/television/config/previewers.rs deleted file mode 100644 index d891232..0000000 --- a/television/config/previewers.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::preview::{previewers, PreviewerConfig}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, Hash)] -pub struct PreviewersConfig { - #[serde(default)] - pub basic: BasicPreviewerConfig, - pub file: FilePreviewerConfig, - #[serde(default)] - pub env_var: EnvVarPreviewerConfig, -} - -impl From for PreviewerConfig { - fn from(val: PreviewersConfig) -> Self { - PreviewerConfig::default() - .file(previewers::files::FilePreviewerConfig::new(val.file.theme)) - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, Hash)] -pub struct BasicPreviewerConfig {} - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Hash)] -#[serde(default)] -pub struct FilePreviewerConfig { - //pub max_file_size: u64, - pub theme: String, -} - -impl Default for FilePreviewerConfig { - fn default() -> Self { - Self { - //max_file_size: 1024 * 1024, - theme: String::from("TwoDark"), - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, Hash)] -pub struct EnvVarPreviewerConfig {} diff --git a/television/preview/previewers/meta.rs b/television/preview/meta.rs similarity index 50% rename from television/preview/previewers/meta.rs rename to television/preview/meta.rs index 67a24ec..a8becab 100644 --- a/television/preview/previewers/meta.rs +++ b/television/preview/meta.rs @@ -1,33 +1,12 @@ use crate::preview::{Preview, PreviewContent}; use std::sync::Arc; -pub fn not_supported(title: &str) -> Arc { - Arc::new(Preview::new( - title.to_string(), - PreviewContent::NotSupported, - None, - None, - 1, - )) -} - -pub fn file_too_large(title: &str) -> Arc { - Arc::new(Preview::new( - title.to_string(), - PreviewContent::FileTooLarge, - None, - None, - 1, - )) -} - #[allow(dead_code)] pub fn loading(title: &str) -> Arc { Arc::new(Preview::new( title.to_string(), PreviewContent::Loading, None, - None, 1, )) } @@ -37,7 +16,6 @@ pub fn timeout(title: &str) -> Arc { title.to_string(), PreviewContent::Timeout, None, - None, 1, )) } diff --git a/television/preview/mod.rs b/television/preview/mod.rs index 9b66517..f8ab079 100644 --- a/television/preview/mod.rs +++ b/television/preview/mod.rs @@ -1,55 +1,26 @@ use std::sync::Arc; -use crate::channels::{entry::Entry, preview::PreviewType}; use devicons::FileIcon; -use ratatui::layout::Rect; pub mod ansi; pub mod cache; -pub mod previewers; - -// previewer types -use crate::utils::cache::RingSet; -use crate::utils::image::ImagePreviewWidget; -use crate::utils::syntax::HighlightedLines; -pub use previewers::basic::BasicPreviewer; -pub use previewers::basic::BasicPreviewerConfig; -pub use previewers::command::CommandPreviewer; -pub use previewers::command::CommandPreviewerConfig; -pub use previewers::env::EnvVarPreviewer; -pub use previewers::env::EnvVarPreviewerConfig; -pub use previewers::files::FilePreviewer; -pub use previewers::files::FilePreviewerConfig; +pub mod meta; +pub mod previewer; #[derive(Clone, Debug, PartialEq, Hash)] pub enum PreviewContent { Empty, - FileTooLarge, - SyntectHighlightedText(HighlightedLines), Loading, Timeout, - NotSupported, - PlainText(Vec), - PlainTextWrapped(String), AnsiText(String), - Image(ImagePreviewWidget), } impl PreviewContent { pub fn total_lines(&self) -> u16 { match self { - PreviewContent::SyntectHighlightedText(hl_lines) => { - hl_lines.lines.len().try_into().unwrap_or(u16::MAX) - } - PreviewContent::PlainText(lines) => { - lines.len().try_into().unwrap_or(u16::MAX) - } PreviewContent::AnsiText(text) => { text.lines().count().try_into().unwrap_or(u16::MAX) } - PreviewContent::Image(image) => { - image.height().try_into().unwrap_or(u16::MAX) - } _ => 0, } } @@ -71,9 +42,6 @@ pub struct Preview { pub title: String, pub content: PreviewContent, pub icon: Option, - /// If the preview is partial, this field contains the byte offset - /// up to which the preview holds. - pub partial_offset: Option, pub total_lines: u16, } @@ -83,7 +51,6 @@ impl Default for Preview { title: String::new(), content: PreviewContent::Empty, icon: None, - partial_offset: None, total_lines: 0, } } @@ -94,14 +61,12 @@ impl Preview { title: String, content: PreviewContent, icon: Option, - partial_offset: Option, total_lines: u16, ) -> Self { Preview { title, content, icon, - partial_offset, total_lines, } } @@ -174,110 +139,3 @@ impl PreviewState { } } } - -#[derive(Debug, Default)] -pub struct Previewer { - basic: BasicPreviewer, - file: FilePreviewer, - env_var: EnvVarPreviewer, - command: CommandPreviewer, - requests: RingSet, -} - -#[derive(Debug, Default)] -pub struct PreviewerConfig { - basic: BasicPreviewerConfig, - file: FilePreviewerConfig, - env_var: EnvVarPreviewerConfig, - command: CommandPreviewerConfig, -} - -impl PreviewerConfig { - pub fn basic(mut self, config: BasicPreviewerConfig) -> Self { - self.basic = config; - self - } - - pub fn file(mut self, config: FilePreviewerConfig) -> Self { - self.file = config; - self - } - - pub fn env_var(mut self, config: EnvVarPreviewerConfig) -> Self { - self.env_var = config; - self - } -} - -const REQUEST_STACK_SIZE: usize = 10; - -impl Previewer { - pub fn new(config: Option) -> Self { - let config = config.unwrap_or_default(); - Previewer { - basic: BasicPreviewer::new(Some(config.basic)), - file: FilePreviewer::new(Some(config.file)), - env_var: EnvVarPreviewer::new(Some(config.env_var)), - command: CommandPreviewer::new(Some(config.command)), - requests: RingSet::with_capacity(REQUEST_STACK_SIZE), - } - } - - fn dispatch_request( - &mut self, - entry: &Entry, - preview_window: Option, - ) -> Option> { - match &entry.preview_type { - PreviewType::Basic => Some(self.basic.preview(entry)), - PreviewType::EnvVar => Some(self.env_var.preview(entry)), - PreviewType::Files => self.file.preview(entry, preview_window), - PreviewType::Command(cmd) => self.command.preview(entry, cmd), - PreviewType::None => Some(Arc::new(Preview::default())), - } - } - - fn cached(&self, entry: &Entry) -> Option> { - match &entry.preview_type { - PreviewType::Basic => Some(self.basic.preview(entry)), - PreviewType::EnvVar => Some(self.env_var.preview(entry)), - PreviewType::Files => self.file.cached(entry), - PreviewType::Command(_) => self.command.cached(entry), - PreviewType::None => None, - } - } - - // we could use a target scroll here to make the previewer - // faster, but since it's already running in the background and quite - // fast for most standard file sizes, plus we're caching the previews, - // I'm not sure the extra complexity is worth it. - pub fn preview( - &mut self, - entry: &Entry, - preview_window: Option, - ) -> Option> { - // check if we have a preview for the current request - if let Some(preview) = self.cached(entry) { - return Some(preview); - } - - // otherwise, if we haven't acknowledged the request yet, acknowledge it - self.requests.push(entry.clone()); - - // lookup request stack and return the most recent preview available - for request in self.requests.back_to_front() { - if let Some(preview) = - self.dispatch_request(&request, preview_window) - { - return Some(preview); - } - } - None - } - - pub fn set_config(&mut self, config: PreviewerConfig) { - self.basic = BasicPreviewer::new(Some(config.basic)); - self.file = FilePreviewer::new(Some(config.file)); - self.env_var = EnvVarPreviewer::new(Some(config.env_var)); - } -} diff --git a/television/preview/previewer.rs b/television/preview/previewer.rs new file mode 100644 index 0000000..6767e55 --- /dev/null +++ b/television/preview/previewer.rs @@ -0,0 +1,152 @@ +use crate::preview::{Preview, PreviewContent}; +use crate::utils::cache::RingSet; +use crate::utils::command::shell_command; +use crate::{ + channels::{entry::Entry, preview::PreviewCommand}, + preview::cache::PreviewCache, +}; +use parking_lot::Mutex; +use rustc_hash::FxHashSet; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::Arc; +use tracing::debug; + +#[allow(dead_code)] +#[derive(Debug)] +pub struct Previewer { + cache: Arc>, + requests: RingSet, + concurrent_preview_tasks: Arc, + in_flight_previews: Arc>>, + command: PreviewCommand, +} + +const REQUEST_STACK_SIZE: usize = 10; + +impl Previewer { + // we could use a target scroll here to make the previewer + // faster, but since it's already running in the background and quite + // fast for most standard file sizes, plus we're caching the previews, + // I'm not sure the extra complexity is worth it. + pub fn handle_request(&mut self, entry: &Entry) -> Option> { + // check if we have a preview for the current request + if let Some(preview) = self.preview(entry) { + return Some(preview); + } + + // otherwise, if we haven't acknowledged the request yet, acknowledge it + self.requests.push(entry.clone()); + + // lookup request stack and return the most recent preview available + for entry in self.requests.back_to_front() { + if let Some(preview) = self.preview(&entry) { + return Some(preview); + } + } + None + } +} + +const MAX_CONCURRENT_PREVIEW_TASKS: u8 = 3; + +impl Previewer { + pub fn new(command: PreviewCommand) -> Self { + Previewer { + cache: Arc::new(Mutex::new(PreviewCache::default())), + requests: RingSet::with_capacity(REQUEST_STACK_SIZE), + concurrent_preview_tasks: Arc::new(AtomicU8::new(0)), + in_flight_previews: Arc::new(Mutex::new(FxHashSet::default())), + command, + } + } + + pub fn cached(&self, entry: &Entry) -> Option> { + self.cache.lock().get(&entry.name) + } + + pub fn preview(&mut self, entry: &Entry) -> Option> { + if let Some(preview) = self.cached(entry) { + Some(preview) + } else { + // preview is not in cache, spawn a task to compute the preview + debug!("Preview cache miss for {:?}", entry.name); + self.handle_preview_request(entry); + None + } + } + + pub fn handle_preview_request(&mut self, entry: &Entry) { + if self.in_flight_previews.lock().contains(&entry.name) { + debug!("Preview already in flight for {:?}", entry.name); + return; + } + + if self.concurrent_preview_tasks.load(Ordering::Relaxed) + < MAX_CONCURRENT_PREVIEW_TASKS + { + self.in_flight_previews.lock().insert(entry.name.clone()); + self.concurrent_preview_tasks + .fetch_add(1, Ordering::Relaxed); + let cache = self.cache.clone(); + let entry_c = entry.clone(); + let concurrent_tasks = self.concurrent_preview_tasks.clone(); + let command = self.command.clone(); + let in_flight_previews = self.in_flight_previews.clone(); + tokio::spawn(async move { + try_preview( + &command, + &entry_c, + &cache, + &concurrent_tasks, + &in_flight_previews, + ); + }); + } else { + debug!( + "Too many concurrent preview tasks, skipping {:?}", + entry.name + ); + } + } +} + +pub fn try_preview( + command: &PreviewCommand, + entry: &Entry, + cache: &Arc>, + concurrent_tasks: &Arc, + in_flight_previews: &Arc>>, +) { + debug!("Computing preview for {:?}", entry.name); + let command = command.format_with(entry); + debug!("Formatted preview command: {:?}", command); + + let child = shell_command(false) + .arg(&command) + .output() + .expect("failed to execute process"); + + if child.status.success() { + let content = String::from_utf8_lossy(&child.stdout); + let preview = Arc::new(Preview::new( + entry.name.clone(), + PreviewContent::AnsiText(content.to_string()), + None, + u16::try_from(content.lines().count()).unwrap_or(u16::MAX), + )); + + cache.lock().insert(entry.name.clone(), &preview); + } else { + let content = String::from_utf8_lossy(&child.stderr); + let preview = Arc::new(Preview::new( + entry.name.clone(), + PreviewContent::AnsiText(content.to_string()), + None, + u16::try_from(content.lines().count()).unwrap_or(u16::MAX), + )); + cache.lock().insert(entry.name.clone(), &preview); + } + + concurrent_tasks.fetch_sub(1, Ordering::Relaxed); + in_flight_previews.lock().remove(&entry.name); +} diff --git a/television/preview/previewers/basic.rs b/television/preview/previewers/basic.rs deleted file mode 100644 index 9cddec9..0000000 --- a/television/preview/previewers/basic.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::sync::Arc; - -use crate::channels::entry::Entry; -use crate::preview::{Preview, PreviewContent}; - -#[derive(Debug, Default)] -pub struct BasicPreviewer { - _config: BasicPreviewerConfig, -} - -#[derive(Debug, Default)] -pub struct BasicPreviewerConfig {} - -impl BasicPreviewer { - pub fn new(config: Option) -> Self { - BasicPreviewer { - _config: config.unwrap_or_default(), - } - } - - pub fn preview(&self, entry: &Entry) -> Arc { - Arc::new(Preview { - title: entry.name.clone(), - content: PreviewContent::PlainTextWrapped(entry.name.clone()), - icon: entry.icon, - partial_offset: None, - total_lines: 1, - }) - } -} diff --git a/television/preview/previewers/command.rs b/television/preview/previewers/command.rs deleted file mode 100644 index d67c99f..0000000 --- a/television/preview/previewers/command.rs +++ /dev/null @@ -1,294 +0,0 @@ -use crate::preview::{Preview, PreviewContent}; -use crate::utils::command::shell_command; -use crate::{ - channels::{entry::Entry, preview::PreviewCommand}, - preview::cache::PreviewCache, -}; -use parking_lot::Mutex; -use regex::Regex; -use rustc_hash::FxHashSet; -use std::sync::atomic::{AtomicU8, Ordering}; -use std::sync::Arc; -use tracing::debug; - -#[allow(dead_code)] -#[derive(Debug)] -pub struct CommandPreviewer { - cache: Arc>, - config: CommandPreviewerConfig, - concurrent_preview_tasks: Arc, - in_flight_previews: Arc>>, - command_re: Regex, -} - -impl Default for CommandPreviewer { - fn default() -> Self { - CommandPreviewer::new(None) - } -} - -#[allow(dead_code)] -#[derive(Debug, Clone)] -pub struct CommandPreviewerConfig { - delimiter: String, -} - -const DEFAULT_DELIMITER: &str = " "; - -impl Default for CommandPreviewerConfig { - fn default() -> Self { - CommandPreviewerConfig { - delimiter: String::from(DEFAULT_DELIMITER), - } - } -} - -impl CommandPreviewerConfig { - pub fn new(delimiter: &str) -> Self { - CommandPreviewerConfig { - delimiter: String::from(delimiter), - } - } -} - -const MAX_CONCURRENT_PREVIEW_TASKS: u8 = 3; - -impl CommandPreviewer { - pub fn new(config: Option) -> Self { - let config = config.unwrap_or_default(); - CommandPreviewer { - cache: Arc::new(Mutex::new(PreviewCache::default())), - config, - concurrent_preview_tasks: Arc::new(AtomicU8::new(0)), - in_flight_previews: Arc::new(Mutex::new(FxHashSet::default())), - command_re: Regex::new(r"\{(\d+)\}").unwrap(), - } - } - - pub fn cached(&self, entry: &Entry) -> Option> { - self.cache.lock().get(&entry.name) - } - - pub fn preview( - &mut self, - entry: &Entry, - command: &PreviewCommand, - ) -> Option> { - if let Some(preview) = self.cached(entry) { - Some(preview) - } else { - // preview is not in cache, spawn a task to compute the preview - debug!("Preview cache miss for {:?}", entry.name); - self.handle_preview_request(entry, command); - None - } - } - - pub fn handle_preview_request( - &mut self, - entry: &Entry, - command: &PreviewCommand, - ) { - if self.in_flight_previews.lock().contains(&entry.name) { - debug!("Preview already in flight for {:?}", entry.name); - return; - } - - if self.concurrent_preview_tasks.load(Ordering::Relaxed) - < MAX_CONCURRENT_PREVIEW_TASKS - { - self.in_flight_previews.lock().insert(entry.name.clone()); - self.concurrent_preview_tasks - .fetch_add(1, Ordering::Relaxed); - let cache = self.cache.clone(); - let entry_c = entry.clone(); - let concurrent_tasks = self.concurrent_preview_tasks.clone(); - let command = command.clone(); - let in_flight_previews = self.in_flight_previews.clone(); - let command_re = self.command_re.clone(); - tokio::spawn(async move { - try_preview( - &command, - &entry_c, - &cache, - &concurrent_tasks, - &in_flight_previews, - &command_re, - ); - }); - } else { - debug!( - "Too many concurrent preview tasks, skipping {:?}", - entry.name - ); - } - } -} - -/// Format the command with the entry name and provided placeholders -/// -/// # Example -/// ``` -/// use television::channels::{preview::{PreviewCommand, PreviewType}, entry::Entry}; -/// use television::preview::previewers::command::format_command; -/// -/// let command = PreviewCommand { -/// command: "something {} {2} {0}".to_string(), -/// delimiter: ":".to_string(), -/// }; -/// let entry = Entry::new("a:given:entry:to:preview".to_string(), PreviewType::Command(command.clone())); -/// let formatted_command = format_command(&command, &entry, ®ex::Regex::new(r"\{(\d+)\}").unwrap()); -/// -/// assert_eq!(formatted_command, "something 'a:given:entry:to:preview' 'entry' 'a'"); -/// ``` -pub fn format_command( - command: &PreviewCommand, - entry: &Entry, - command_re: &Regex, -) -> String { - let parts = entry.name.split(&command.delimiter).collect::>(); - debug!("Parts: {:?}", parts); - - let mut formatted_command = command - .command - .replace("{}", format!("'{}'", entry.name).as_str()); - - formatted_command = command_re - .replace_all(&formatted_command, |caps: ®ex::Captures| { - let index = - caps.get(1).unwrap().as_str().parse::().unwrap(); - format!("'{}'", parts[index]) - }) - .to_string(); - - formatted_command -} - -pub fn try_preview( - command: &PreviewCommand, - entry: &Entry, - cache: &Arc>, - concurrent_tasks: &Arc, - in_flight_previews: &Arc>>, - command_re: &Regex, -) { - debug!("Computing preview for {:?}", entry.name); - let command = format_command(command, entry, command_re); - debug!("Formatted preview command: {:?}", command); - - let child = shell_command(false) - .arg(&command) - .output() - .expect("failed to execute process"); - - if child.status.success() { - let content = String::from_utf8_lossy(&child.stdout); - let preview = Arc::new(Preview::new( - entry.name.clone(), - PreviewContent::AnsiText(content.to_string()), - None, - None, - u16::try_from(content.lines().count()).unwrap_or(u16::MAX), - )); - - cache.lock().insert(entry.name.clone(), &preview); - } else { - let content = String::from_utf8_lossy(&child.stderr); - let preview = Arc::new(Preview::new( - entry.name.clone(), - PreviewContent::AnsiText(content.to_string()), - None, - None, - u16::try_from(content.lines().count()).unwrap_or(u16::MAX), - )); - cache.lock().insert(entry.name.clone(), &preview); - } - - concurrent_tasks.fetch_sub(1, Ordering::Relaxed); - in_flight_previews.lock().remove(&entry.name); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::channels::{entry::Entry, preview::PreviewType}; - - #[test] - fn test_format_command() { - let command = PreviewCommand { - command: "something {} {2} {0}".to_string(), - delimiter: ":".to_string(), - }; - let entry = Entry::new( - "an:entry:to:preview".to_string(), - PreviewType::Command(command.clone()), - ); - let formatted_command = format_command( - &command, - &entry, - &Regex::new(r"\{(\d+)\}").unwrap(), - ); - - assert_eq!( - formatted_command, - "something 'an:entry:to:preview' 'to' 'an'" - ); - } - - #[test] - fn test_format_command_no_placeholders() { - let command = PreviewCommand { - command: "something".to_string(), - delimiter: ":".to_string(), - }; - let entry = Entry::new( - "an:entry:to:preview".to_string(), - PreviewType::Command(command.clone()), - ); - let formatted_command = format_command( - &command, - &entry, - &Regex::new(r"\{(\d+)\}").unwrap(), - ); - - assert_eq!(formatted_command, "something"); - } - - #[test] - fn test_format_command_with_global_placeholder_only() { - let command = PreviewCommand { - command: "something {}".to_string(), - delimiter: ":".to_string(), - }; - let entry = Entry::new( - "an:entry:to:preview".to_string(), - PreviewType::Command(command.clone()), - ); - let formatted_command = format_command( - &command, - &entry, - &Regex::new(r"\{(\d+)\}").unwrap(), - ); - - assert_eq!(formatted_command, "something 'an:entry:to:preview'"); - } - - #[test] - fn test_format_command_with_positional_placeholders_only() { - let command = PreviewCommand { - command: "something {0} -t {2}".to_string(), - delimiter: ":".to_string(), - }; - let entry = Entry::new( - "an:entry:to:preview".to_string(), - PreviewType::Command(command.clone()), - ); - let formatted_command = format_command( - &command, - &entry, - &Regex::new(r"\{(\d+)\}").unwrap(), - ); - - assert_eq!(formatted_command, "something 'an' -t 'to'"); - } -} diff --git a/television/preview/previewers/env.rs b/television/preview/previewers/env.rs deleted file mode 100644 index 1f9bae4..0000000 --- a/television/preview/previewers/env.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::sync::Arc; - -use crate::channels::entry; -use crate::preview::{Preview, PreviewContent}; - -#[derive(Debug, Default)] -pub struct EnvVarPreviewer { - _config: EnvVarPreviewerConfig, -} - -#[derive(Debug, Default)] -pub struct EnvVarPreviewerConfig {} - -impl EnvVarPreviewer { - pub fn new(config: Option) -> Self { - EnvVarPreviewer { - _config: config.unwrap_or_default(), - } - } - - pub fn preview(&self, entry: &entry::Entry) -> Arc { - let content = entry.value.as_ref().map(|preview| { - maybe_add_newline_after_colon(preview, &entry.name) - }); - let total_lines = content.as_ref().map_or_else( - || 1, - |c| u16::try_from(c.lines().count()).unwrap_or(u16::MAX), - ); - - Arc::new(Preview { - title: entry.name.clone(), - content: match content { - Some(content) => PreviewContent::PlainTextWrapped(content), - None => PreviewContent::Empty, - }, - icon: entry.icon, - partial_offset: None, - total_lines, - }) - } -} - -const PATH: &str = "PATH"; - -fn maybe_add_newline_after_colon(s: &str, name: &str) -> String { - if name.contains(PATH) { - return s.replace(':', "\n"); - } - s.to_string() -} diff --git a/television/preview/previewers/files.rs b/television/preview/previewers/files.rs deleted file mode 100644 index 216ace3..0000000 --- a/television/preview/previewers/files.rs +++ /dev/null @@ -1,379 +0,0 @@ -use crate::utils::files::{read_into_lines_capped, ReadResult}; -use crate::utils::syntax::HighlightedLines; -use image::ImageReader; -use parking_lot::Mutex; -use ratatui::layout::Rect; -use rustc_hash::{FxBuildHasher, FxHashSet}; -use std::collections::HashSet; -use std::fs::File; -use std::io::{BufRead, BufReader, Seek}; -use std::path::PathBuf; -use std::sync::{ - atomic::{AtomicU8, Ordering}, - Arc, -}; -use syntect::{highlighting::Theme, parsing::SyntaxSet}; -use tracing::{debug, trace, warn}; - -use crate::channels::entry; -use crate::preview::cache::PreviewCache; -use crate::preview::{previewers::meta, Preview, PreviewContent}; -use crate::utils::image::ImagePreviewWidget; -use crate::utils::{ - files::FileType, - strings::preprocess_line, - syntax::{self, load_highlighting_assets, HighlightingAssetsExt}, -}; - -#[derive(Debug, Default)] -pub struct FilePreviewer { - cache: Arc>, - pub syntax_set: Arc, - pub syntax_theme: Arc, - concurrent_preview_tasks: Arc, - in_flight_previews: Arc>>, -} - -#[derive(Debug, Clone, Default)] -pub struct FilePreviewerConfig { - pub theme: String, -} - -impl FilePreviewerConfig { - pub fn new(theme: String) -> Self { - FilePreviewerConfig { theme } - } -} - -const MAX_CONCURRENT_PREVIEW_TASKS: u8 = 3; - -const BAT_THEME_ENV_VAR: &str = "BAT_THEME"; - -impl FilePreviewer { - pub fn new(config: Option) -> Self { - let hl_assets = load_highlighting_assets(); - let syntax_set = hl_assets.get_syntax_set().unwrap().clone(); - - let theme_name = match std::env::var(BAT_THEME_ENV_VAR) { - Ok(t) => t, - Err(_) => match config { - Some(c) => c.theme, - // this will error and default back nicely - None => "unknown".to_string(), - }, - }; - - let theme = hl_assets.get_theme_no_output(&theme_name).clone(); - - FilePreviewer { - cache: Arc::new(Mutex::new(PreviewCache::default())), - syntax_set: Arc::new(syntax_set), - syntax_theme: Arc::new(theme), - concurrent_preview_tasks: Arc::new(AtomicU8::new(0)), - in_flight_previews: Arc::new(Mutex::new(HashSet::with_hasher( - FxBuildHasher, - ))), - } - } - - pub fn cached(&self, entry: &entry::Entry) -> Option> { - self.cache.lock().get(&entry.name) - } - - pub fn preview( - &mut self, - entry: &entry::Entry, - preview_window: Option, - ) -> Option> { - if let Some(preview) = self.cached(entry) { - trace!("Preview cache hit for {:?}", entry.name); - if preview.partial_offset.is_some() { - // preview is partial, spawn a task to compute the next chunk - // and return the partial preview - debug!("Spawning partial preview task for {:?}", entry.name); - self.handle_preview_request( - entry, - Some(preview.clone()), - preview_window, - ); - } - Some(preview) - } else { - // preview is not in cache, spawn a task to compute the preview - trace!("Preview cache miss for {:?}", entry.name); - self.handle_preview_request(entry, None, preview_window); - None - } - } - - pub fn handle_preview_request( - &mut self, - entry: &entry::Entry, - partial_preview: Option>, - preview_window: Option, - ) { - if self.in_flight_previews.lock().contains(&entry.name) { - trace!("Preview already in flight for {:?}", entry.name); - return; - } - - if self.concurrent_preview_tasks.load(Ordering::Relaxed) - < MAX_CONCURRENT_PREVIEW_TASKS - { - self.in_flight_previews.lock().insert(entry.name.clone()); - self.concurrent_preview_tasks - .fetch_add(1, Ordering::Relaxed); - let cache = self.cache.clone(); - let entry_c = entry.clone(); - let syntax_set = self.syntax_set.clone(); - let syntax_theme = self.syntax_theme.clone(); - let concurrent_tasks = self.concurrent_preview_tasks.clone(); - let in_flight_previews = self.in_flight_previews.clone(); - tokio::spawn(async move { - try_preview( - &entry_c, - partial_preview, - &cache, - &syntax_set, - &syntax_theme, - &concurrent_tasks, - &in_flight_previews, - preview_window, - ); - }); - } - } - - #[allow(dead_code)] - fn cache_preview(&mut self, key: String, preview: &Arc) { - self.cache.lock().insert(key, preview); - } -} - -/// The size of the buffer used to read the file in bytes. -/// This ends up being the max size of partial previews. -const PARTIAL_BUFREAD_SIZE: usize = 5 * 1024 * 1024; - -#[allow(clippy::too_many_arguments)] -pub fn try_preview( - entry: &entry::Entry, - partial_preview: Option>, - cache: &Arc>, - syntax_set: &Arc, - syntax_theme: &Arc, - concurrent_tasks: &Arc, - in_flight_previews: &Arc>>, - preview_window: Option, -) { - debug!("Computing preview for {:?}", entry.name); - let path = PathBuf::from(&entry.name); - - // if we're dealing with a partial preview, no need to re-check for textual content - if partial_preview.is_some() - || matches!(FileType::from(&path), FileType::Text) - { - debug!("File is text-based: {:?}", entry.name); - match File::open(path) { - Ok(mut file) => { - // if we're dealing with a partial preview, seek to the provided offset - // and use the previous state to compute the next chunk of the preview - let cached_lines = if let Some(p) = partial_preview { - if let PreviewContent::SyntectHighlightedText(hl) = - &p.content - { - let _ = file.seek(std::io::SeekFrom::Start( - // this is always Some in this case - p.partial_offset.unwrap() as u64, - )); - Some(hl.clone()) - } else { - None - } - } else { - None - }; - // compute the highlighted version in the background - match read_into_lines_capped(file, PARTIAL_BUFREAD_SIZE) { - ReadResult::Full(lines) => { - if let Some(content) = compute_highlighted_text_preview( - entry, - &lines - .iter() - .map(|l| preprocess_line(l).0 + "\n") - .collect::>(), - syntax_set, - syntax_theme, - cached_lines.as_ref(), - ) { - let total_lines = content.total_lines(); - let preview = Arc::new(Preview::new( - entry.name.clone(), - content, - entry.icon, - None, - total_lines, - )); - cache.lock().insert(entry.name.clone(), &preview); - } - } - ReadResult::Partial(p) => { - if let Some(content) = compute_highlighted_text_preview( - entry, - &p.lines - .iter() - .map(|l| preprocess_line(l).0 + "\n") - .collect::>(), - syntax_set, - syntax_theme, - cached_lines.as_ref(), - ) { - let total_lines = content.total_lines(); - let preview = Arc::new(Preview::new( - entry.name.clone(), - content, - entry.icon, - Some(p.bytes_read), - total_lines, - )); - cache.lock().insert(entry.name.clone(), &preview); - } - } - ReadResult::Error(e) => { - warn!("Error reading file: {:?}", e); - let p = meta::not_supported(&entry.name); - cache.lock().insert(entry.name.clone(), &p); - } - } - } - Err(e) => { - warn!("Error opening file: {:?}", e); - let p = meta::not_supported(&entry.name); - cache.lock().insert(entry.name.clone(), &p); - } - } - } else if matches!(FileType::from(&path), FileType::Image) { - cache.lock().insert( - entry.name.clone(), - &meta::loading(&format!("Loading {}", entry.name)), - ); - - debug!("File {:?} is an image", entry.name); - let option_image = match ImageReader::open(path) { - Ok(reader) => match reader.with_guessed_format() { - Ok(reader) => match reader.decode() { - Ok(image) => Some(image), - Err(e) => { - warn!( - "Error impossible to decode {}: {:?}", - entry.name, e - ); - None - } - }, - Err(e) => { - warn!( - "Error impossible to guess the format of {}: {:?}", - entry.name, e - ); - None - } - }, - Err(e) => { - warn!("Error opening image {}: {:?}", entry.name, e); - None - } - }; - if let Some(image) = option_image { - let preview_window_dimension = preview_window.map(|rect| { - ( - u32::from(rect.width.saturating_sub(2)), - u32::from(rect.height.saturating_sub(2)), - ) // - 2 for the margin - }); - let image_preview_widget = ImagePreviewWidget::from_dynamic_image( - image, - preview_window_dimension, - ); - let total_lines = - image_preview_widget.height().try_into().unwrap_or(u16::MAX); - let content = PreviewContent::Image(image_preview_widget); - let preview = Arc::new(Preview::new( - entry.name.clone(), - content, - entry.icon, - None, - total_lines, - )); - cache.lock().insert(entry.name.clone(), &preview); - } else { - let p = meta::not_supported(&entry.name); - cache.lock().insert(entry.name.clone(), &p); - } - } else { - debug!("File format isn't supported for preview: {:?}", entry.name); - let preview = meta::not_supported(&entry.name); - cache.lock().insert(entry.name.clone(), &preview); - } - concurrent_tasks.fetch_sub(1, Ordering::Relaxed); - in_flight_previews.lock().remove(&entry.name); -} - -fn compute_highlighted_text_preview( - entry: &entry::Entry, - lines: &[String], - syntax_set: &SyntaxSet, - syntax_theme: &Theme, - previous_lines: Option<&HighlightedLines>, -) -> Option { - debug!( - "Computing highlights in the background for {:?}", - entry.name - ); - - match syntax::compute_highlights_incremental( - &PathBuf::from(&entry.name), - lines, - syntax_set, - syntax_theme, - previous_lines, - ) { - Ok(highlighted_lines) => { - Some(PreviewContent::SyntectHighlightedText(highlighted_lines)) - } - Err(e) => { - warn!("Error computing highlights: {:?}", e); - None - } - } -} - -/// This should be enough for most terminal sizes -const TEMP_PLAIN_TEXT_PREVIEW_HEIGHT: usize = 200; - -#[allow(dead_code)] -fn plain_text_preview(title: &str, reader: BufReader<&File>) -> Arc { - debug!("Creating plain text preview for {:?}", title); - let mut lines = Vec::with_capacity(TEMP_PLAIN_TEXT_PREVIEW_HEIGHT); - // PERF: instead of using lines(), maybe check for the length of the first line instead and - // truncate accordingly (since this is just a temp preview) - for maybe_line in reader.lines() { - match maybe_line { - Ok(line) => lines.push(preprocess_line(&line).0), - Err(e) => { - warn!("Error reading file: {:?}", e); - return meta::not_supported(title); - } - } - if lines.len() >= TEMP_PLAIN_TEXT_PREVIEW_HEIGHT { - break; - } - } - let total_lines = u16::try_from(lines.len()).unwrap_or(u16::MAX); - Arc::new(Preview::new( - title.to_string(), - PreviewContent::PlainText(lines), - None, - None, - total_lines, - )) -} diff --git a/television/preview/previewers/mod.rs b/television/preview/previewers/mod.rs deleted file mode 100644 index fb06d47..0000000 --- a/television/preview/previewers/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod basic; -pub mod command; -pub mod env; -pub mod files; -pub mod meta; diff --git a/television/screen/preview.rs b/television/screen/preview.rs index 529e5b5..c4c8fa9 100644 --- a/television/screen/preview.rs +++ b/television/screen/preview.rs @@ -1,20 +1,15 @@ use crate::preview::PreviewState; use crate::preview::{ - ansi::IntoText, PreviewContent, FILE_TOO_LARGE_MSG, LOADING_MSG, - PREVIEW_NOT_SUPPORTED_MSG, TIMEOUT_MSG, + ansi::IntoText, PreviewContent, LOADING_MSG, TIMEOUT_MSG, }; use crate::screen::colors::{Colorscheme, PreviewColorscheme}; -use crate::utils::image::ImagePreviewWidget; use crate::utils::strings::{ replace_non_printable, shrink_with_ellipsis, ReplaceNonPrintableConfig, EMPTY_STRING, }; use anyhow::Result; use devicons::FileIcon; -use ratatui::buffer::Buffer; -use ratatui::widgets::{ - Block, BorderType, Borders, Padding, Paragraph, Widget, Wrap, -}; +use ratatui::widgets::{Block, BorderType, Borders, Padding, Paragraph, Wrap}; use ratatui::Frame; use ratatui::{ layout::{Alignment, Rect}, @@ -26,22 +21,6 @@ use std::str::FromStr; const FILL_CHAR_SLANTED: char = '╱'; const FILL_CHAR_EMPTY: char = ' '; -pub enum PreviewWidget<'a> { - Paragraph(Paragraph<'a>), - Image(ImagePreviewWidget), -} -impl Widget for PreviewWidget<'_> { - fn render(self, area: Rect, buf: &mut Buffer) - where - Self: Sized, - { - match self { - PreviewWidget::Paragraph(p) => p.render(area, buf), - PreviewWidget::Image(image) => image.render(area, buf), - } - } -} - #[allow(clippy::too_many_arguments)] pub fn draw_preview_content_block( f: &mut Frame, @@ -59,25 +38,23 @@ pub fn draw_preview_content_block( use_nerd_font_icons, )?; // render the preview content - let rp = build_preview_widget( + let rp = build_preview_paragraph( inner, &preview_state.preview.content, preview_state.target_line, preview_state.scroll, - colorscheme, ); f.render_widget(rp, inner); Ok(()) } -pub fn build_preview_widget<'a>( +pub fn build_preview_paragraph<'a>( inner: Rect, preview_content: &'a PreviewContent, target_line: Option, preview_scroll: u16, - colorscheme: &'a Colorscheme, -) -> PreviewWidget<'a> { +) -> Paragraph<'a> { let preview_block = Block::default().style(Style::default()).padding(Padding { top: 0, @@ -87,75 +64,23 @@ pub fn build_preview_widget<'a>( }); match preview_content { - PreviewContent::AnsiText(text) => PreviewWidget::Paragraph( - build_ansi_text_paragraph(text, preview_block, preview_scroll), - ), - PreviewContent::PlainText(content) => { - PreviewWidget::Paragraph(build_plain_text_paragraph( - content, - preview_block, - target_line, - preview_scroll, - colorscheme.preview, - )) + PreviewContent::AnsiText(text) => { + build_ansi_text_paragraph(text, preview_block, preview_scroll) } - PreviewContent::PlainTextWrapped(content) => PreviewWidget::Paragraph( - build_plain_text_wrapped_paragraph( - content, - preview_block, - colorscheme.preview, - ) - .scroll((preview_scroll, 0)), - ), - PreviewContent::SyntectHighlightedText(highlighted_lines) => { - PreviewWidget::Paragraph(build_syntect_highlighted_paragraph( - &highlighted_lines.lines, - preview_block, - target_line, - preview_scroll, - colorscheme.preview, - inner.height, - )) - } - PreviewContent::Image(image) => PreviewWidget::Image(image.clone()), - // meta - PreviewContent::Loading => PreviewWidget::Paragraph( + PreviewContent::Loading => { build_meta_preview_paragraph(inner, LOADING_MSG, FILL_CHAR_EMPTY) .block(preview_block) .alignment(Alignment::Left) - .style(Style::default().add_modifier(Modifier::ITALIC)), - ), - PreviewContent::NotSupported => PreviewWidget::Paragraph( - build_meta_preview_paragraph( - inner, - PREVIEW_NOT_SUPPORTED_MSG, - FILL_CHAR_EMPTY, - ) - .block(preview_block) - .alignment(Alignment::Left) - .style(Style::default().add_modifier(Modifier::ITALIC)), - ), - PreviewContent::FileTooLarge => PreviewWidget::Paragraph( - build_meta_preview_paragraph( - inner, - FILE_TOO_LARGE_MSG, - FILL_CHAR_EMPTY, - ) - .block(preview_block) - .alignment(Alignment::Left) - .style(Style::default().add_modifier(Modifier::ITALIC)), - ), - - PreviewContent::Timeout => PreviewWidget::Paragraph( + .style(Style::default().add_modifier(Modifier::ITALIC)) + } + PreviewContent::Timeout => { build_meta_preview_paragraph(inner, TIMEOUT_MSG, FILL_CHAR_EMPTY) .block(preview_block) .alignment(Alignment::Left) - .style(Style::default().add_modifier(Modifier::ITALIC)), - ), - PreviewContent::Empty => { - PreviewWidget::Paragraph(Paragraph::new(Text::raw(EMPTY_STRING))) + .style(Style::default().add_modifier(Modifier::ITALIC)) } + PreviewContent::Empty => Paragraph::new(Text::raw(EMPTY_STRING)), } } @@ -253,26 +178,6 @@ fn build_plain_text_wrapped_paragraph<'a>( .wrap(Wrap { trim: true }) } -fn build_syntect_highlighted_paragraph<'a>( - highlighted_lines: &'a [Vec<(syntect::highlighting::Style, String)>], - preview_block: Block<'a>, - target_line: Option, - preview_scroll: u16, - colorscheme: PreviewColorscheme, - height: u16, -) -> Paragraph<'a> { - compute_paragraph_from_highlighted_lines( - highlighted_lines, - target_line.map(|l| l as usize), - preview_scroll, - colorscheme, - height, - ) - .block(preview_block) - .alignment(Alignment::Left) - //.scroll((preview_scroll, 0)) -} - pub fn build_meta_preview_paragraph<'a>( inner: Rect, message: &str, @@ -385,77 +290,3 @@ fn draw_content_outer_block( fn build_line_number_span<'a>(line_number: usize) -> Span<'a> { Span::from(format!("{line_number:5} ")) } - -fn compute_paragraph_from_highlighted_lines( - highlighted_lines: &[Vec<(syntect::highlighting::Style, String)>], - line_specifier: Option, - preview_scroll: u16, - colorscheme: PreviewColorscheme, - height: u16, -) -> Paragraph<'static> { - let preview_lines: Vec = highlighted_lines - .iter() - .enumerate() - .skip(preview_scroll.saturating_sub(1).into()) - .take(height.into()) - .map(|(i, l)| { - let line_number = - build_line_number_span(i + 1).style(Style::default().fg( - if line_specifier.is_some() - && i == line_specifier.unwrap().saturating_sub(1) - { - colorscheme.gutter_selected_fg - } else { - colorscheme.gutter_fg - }, - )); - Line::from_iter( - std::iter::once(line_number) - .chain(std::iter::once(Span::styled( - " │ ", - Style::default().fg(colorscheme.gutter_fg).dim(), - ))) - .chain(l.iter().cloned().map(|sr| { - convert_syn_region_to_span( - &(sr.0, sr.1), - if line_specifier.is_some() - && i == line_specifier - .unwrap() - .saturating_sub(1) - { - Some(colorscheme.highlight_bg) - } else { - None - }, - ) - })), - ) - }) - .collect(); - - Paragraph::new(preview_lines) -} - -pub fn convert_syn_region_to_span<'a>( - syn_region: &(syntect::highlighting::Style, String), - background: Option, -) -> Span<'a> { - let mut style = Style::default() - .fg(convert_syn_color_to_ratatui_color(syn_region.0.foreground)); - if let Some(background) = background { - style = style.bg(background); - } - style = match syn_region.0.font_style { - syntect::highlighting::FontStyle::BOLD => style.bold(), - syntect::highlighting::FontStyle::ITALIC => style.italic(), - syntect::highlighting::FontStyle::UNDERLINE => style.underlined(), - _ => style, - }; - Span::styled(syn_region.1.clone(), style) -} - -fn convert_syn_color_to_ratatui_color( - color: syntect::highlighting::Color, -) -> Color { - Color::Rgb(color.r, color.g, color.b) -} diff --git a/television/screen/results.rs b/television/screen/results.rs index c7847eb..473d330 100644 --- a/television/screen/results.rs +++ b/television/screen/results.rs @@ -294,20 +294,17 @@ pub fn draw_results_list( #[cfg(test)] mod tests { - use crate::channels::preview::PreviewType; - use super::*; #[test] fn test_build_result_line() { - let entry = - Entry::new(String::from("something nice"), PreviewType::None) - .with_name_match_indices( - // something nice - // 012345678901234 - // om ni - &[1, 2, 10, 11], - ); + let entry = Entry::new(String::from("something nice")) + .with_name_match_indices( + // something nice + // 012345678901234 + // om ni + &[1, 2, 10, 11], + ); let result_line = build_result_line( &entry, None, @@ -331,7 +328,7 @@ mod tests { fn test_build_result_line_multibyte_chars() { let entry = // See https://github.com/alexpasmantier/television/issues/439 - Entry::new(String::from("ジェイムス下地 - REDLINE Original Soundtrack - 06 - ROBOWORLD TV.mp3"), PreviewType::None) + Entry::new(String::from("ジェイムス下地 - REDLINE Original Soundtrack - 06 - ROBOWORLD TV.mp3")) .with_name_match_indices(&[27, 28, 29, 30, 31]); let result_line = build_result_line( &entry, diff --git a/television/television.rs b/television/television.rs index 0bac525..b075f90 100644 --- a/television/television.rs +++ b/television/television.rs @@ -1,9 +1,6 @@ use crate::action::Action; use crate::cable::load_cable_channels; -use crate::channels::{ - entry::{Entry, ENTRY_PLACEHOLDER}, - preview::PreviewType, -}; +use crate::channels::entry::{Entry, ENTRY_PLACEHOLDER}; use crate::channels::{ remote_control::RemoteControl, OnAir, TelevisionChannel, }; @@ -11,7 +8,7 @@ use crate::config::{Config, Theme}; use crate::draw::{ChannelState, Ctx, TvState}; use crate::input::convert_action_to_input_request; use crate::picker::Picker; -use crate::preview::{Preview, PreviewState, Previewer}; +use crate::preview::{previewer::Previewer, Preview, PreviewState}; use crate::render::UiState; use crate::screen::colors::Colorscheme; use crate::screen::layout::InputPosition; @@ -74,7 +71,8 @@ impl Television { if config.ui.input_bar_position == InputPosition::Bottom { results_picker = results_picker.inverted(); } - let previewer = Previewer::new(Some(config.previewers.clone().into())); + // FIXME: fix this + let previewer = Previewer::new(); let cable_channels = load_cable_channels().unwrap_or_default(); let app_metadata = AppMetadata::new( @@ -371,15 +369,10 @@ impl Television { &mut self, selected_entry: &Entry, ) -> Result<()> { - if self.config.ui.show_preview_panel - && self.channel.supports_preview() - && !matches!(selected_entry.preview_type, PreviewType::None) + if self.config.ui.show_preview_panel && self.channel.supports_preview() { // preview content - if let Some(preview) = self - .previewer - .preview(selected_entry, self.ui_state.layout.preview_window) - { + if let Some(preview) = self.previewer.preview(selected_entry) { // only update if the preview content has changed if self.preview_state.preview.title != preview.title { self.preview_state.update( diff --git a/television/utils/image.rs b/television/utils/image.rs deleted file mode 100644 index f657db1..0000000 --- a/television/utils/image.rs +++ /dev/null @@ -1,185 +0,0 @@ -use image::imageops::FilterType; -use image::{DynamicImage, Pixel, Rgba}; -use ratatui::buffer::{Buffer, Cell}; -use ratatui::layout::{Position, Rect}; -use ratatui::prelude::Color; -use ratatui::widgets::Widget; -use std::fmt::Debug; -use std::hash::Hash; - -static PIXEL_STRING: &str = "▀"; -const FILTER_TYPE: FilterType = FilterType::Lanczos3; - -// use to reduce the size of the image before storing it -const DEFAULT_CACHED_WIDTH: u32 = 50; -const DEFAULT_CACHED_HEIGHT: u32 = 100; - -const GRAY: Rgba = Rgba([242, 242, 242, 255]); -const WHITE: Rgba = Rgba([255, 255, 255, 255]); - -#[derive(Clone, Debug, Hash, PartialEq)] -pub struct ImagePreviewWidget { - cells: Vec>, -} - -impl Widget for &ImagePreviewWidget { - fn render(self, area: Rect, buf: &mut Buffer) { - let height = self.height(); - let width = self.width(); - // offset of the left top corner where the image is centered - let total_width = usize::from(area.width) + 2 * usize::from(area.x); - let x_offset = total_width.saturating_sub(width) / 2 + 1; - let total_height = usize::from(area.height) + 2 * usize::from(area.y); - let y_offset = total_height.saturating_sub(height) / 2; - - let (area_border_up, area_border_down) = - (area.y, area.y + area.height); - let (area_border_left, area_border_right) = - (area.x, area.x + area.width); - for (y, row) in self.cells.iter().enumerate() { - let pos_y = u16::try_from(y_offset + y).unwrap_or(u16::MAX); - if pos_y >= area_border_up && pos_y < area_border_down { - for (x, cell) in row.iter().enumerate() { - let pos_x = - u16::try_from(x_offset + x).unwrap_or(u16::MAX); - if pos_x >= area_border_left && pos_x <= area_border_right - { - if let Some(buf_cell) = - buf.cell_mut(Position::new(pos_x, pos_y)) - { - *buf_cell = cell.clone(); - } - } - } - } - } - } -} -impl ImagePreviewWidget { - pub fn new(cells: Vec>) -> ImagePreviewWidget { - ImagePreviewWidget { cells } - } - - pub fn height(&self) -> usize { - self.cells.len() - } - pub fn width(&self) -> usize { - if self.height() > 0 { - self.cells[0].len() - } else { - 0 - } - } - - pub fn from_dynamic_image( - dynamic_image: DynamicImage, - dimension: Option<(u32, u32)>, - ) -> Self { - let (window_width, window_height) = - dimension.unwrap_or((DEFAULT_CACHED_WIDTH, DEFAULT_CACHED_HEIGHT)); - let (max_width, max_height) = (window_width, window_height * 2 - 2); // -2 to have some space with the title - - // first quick resize - let big_resized_image = if dynamic_image.width() > max_width * 4 - || dynamic_image.height() > max_height * 4 - { - dynamic_image.resize( - max_width * 4, - max_height * 4, - FilterType::Nearest, - ) - } else { - dynamic_image - }; - - // this time resize with the filter - let resized_image = if big_resized_image.width() > max_width - || big_resized_image.height() > max_height - { - big_resized_image.resize(max_width, max_height, FILTER_TYPE) - } else { - big_resized_image - }; - - let cells = Self::cells_from_dynamic_image(resized_image); - ImagePreviewWidget::new(cells) - } - - fn cells_from_dynamic_image(image: DynamicImage) -> Vec> { - let image_rgba = image.into_rgba8(); - - //creation of the grid of cell - image_rgba - // iter over pair of rows - .rows() - .step_by(2) - .zip(image_rgba.rows().skip(1).step_by(2)) - .enumerate() - .map(|(double_row_y, (row_1, row_2))| { - // create rows of cells - row_1 - .into_iter() - .zip(row_2) - .enumerate() - .map(|(x, (color_up, color_down))| { - let position = (x, double_row_y); - DoublePixel::new(*color_up, *color_down) - .add_grid_background(position) - .into_cell() - }) - .collect::>() - }) - .collect::>>() - } -} - -// util to convert Rgba into ratatui's Cell -struct DoublePixel { - color_up: Rgba, - color_down: Rgba, -} -impl DoublePixel { - pub fn new(color_up: Rgba, color_down: Rgba) -> Self { - Self { - color_up, - color_down, - } - } - - pub fn add_grid_background(mut self, position: (usize, usize)) -> Self { - let color_up = self.color_up.0; - let color_down = self.color_down.0; - self.color_up = Self::blend_with_background(color_up, position, 0); - self.color_down = Self::blend_with_background(color_down, position, 1); - self - } - - fn blend_with_background( - color: impl Into>, - position: (usize, usize), - offset: usize, - ) -> Rgba { - let color = color.into(); - if color[3] == 255 { - color - } else { - let is_white = (position.0 + position.1 * 2 + offset) % 2 == 0; - let mut base = if is_white { WHITE } else { GRAY }; - base.blend(&color); - base - } - } - - pub fn into_cell(self) -> Cell { - let mut cell = Cell::new(PIXEL_STRING); - cell.set_bg(Self::convert_image_color_to_ratatui_color( - self.color_down, - )) - .set_fg(Self::convert_image_color_to_ratatui_color(self.color_up)); - cell - } - - fn convert_image_color_to_ratatui_color(color: Rgba) -> Color { - Color::Rgb(color[0], color[1], color[2]) - } -} diff --git a/television/utils/mod.rs b/television/utils/mod.rs index 9192f4a..d56ee4a 100644 --- a/television/utils/mod.rs +++ b/television/utils/mod.rs @@ -3,7 +3,6 @@ pub mod clipboard; pub mod command; pub mod files; pub mod hashmaps; -pub mod image; pub mod indices; pub mod input; pub mod metadata; @@ -11,5 +10,4 @@ pub mod rocell; pub mod shell; pub mod stdin; pub mod strings; -pub mod syntax; pub mod threads; diff --git a/television/utils/syntax.rs b/television/utils/syntax.rs deleted file mode 100644 index 03f8770..0000000 --- a/television/utils/syntax.rs +++ /dev/null @@ -1,284 +0,0 @@ -use anyhow::Result; -use bat::assets::HighlightingAssets; -use gag::Gag; -use std::path::{Path, PathBuf}; -use syntect::easy::HighlightLines; -use syntect::highlighting::{ - HighlightIterator, HighlightState, Highlighter, Style, Theme, -}; -use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet}; -use tracing::warn; - -#[allow(dead_code)] -#[derive(Debug, Clone)] -pub struct HighlightingState { - parse_state: ParseState, - highlight_state: HighlightState, -} - -impl HighlightingState { - pub fn new( - parse_state: ParseState, - highlight_state: HighlightState, - ) -> Self { - Self { - parse_state, - highlight_state, - } - } -} - -struct LineHighlighter<'a> { - highlighter: Highlighter<'a>, - pub parse_state: ParseState, - pub highlight_state: HighlightState, -} - -impl<'a> LineHighlighter<'a> { - pub fn new( - syntax: &SyntaxReference, - theme: &'a Theme, - ) -> LineHighlighter<'a> { - let highlighter = Highlighter::new(theme); - let highlight_state = - HighlightState::new(&highlighter, ScopeStack::new()); - Self { - highlighter, - parse_state: ParseState::new(syntax), - highlight_state, - } - } - - #[allow(dead_code)] - pub fn from_state( - state: HighlightingState, - theme: &'a Theme, - ) -> LineHighlighter<'a> { - Self { - highlighter: Highlighter::new(theme), - parse_state: state.parse_state, - highlight_state: state.highlight_state, - } - } - - /// Highlights a line of a file - pub fn highlight_line<'b>( - &mut self, - line: &'b str, - syntax_set: &SyntaxSet, - ) -> Result, syntect::Error> { - let ops = self.parse_state.parse_line(line, syntax_set)?; - let iter = HighlightIterator::new( - &mut self.highlight_state, - &ops[..], - line, - &self.highlighter, - ); - Ok(iter.collect()) - } -} - -#[deprecated( - note = "Use `compute_highlights_incremental` instead, which also returns the state" -)] -pub fn compute_highlights_for_path( - file_path: &Path, - lines: &[String], - syntax_set: &SyntaxSet, - syntax_theme: &Theme, -) -> Result>> { - let syntax = set_syntax_set(syntax_set, file_path); - let mut highlighter = HighlightLines::new(syntax, syntax_theme); - let mut highlighted_lines = Vec::new(); - for line in lines { - let hl_regions = highlighter.highlight_line(line, syntax_set)?; - highlighted_lines.push( - hl_regions - .iter() - .map(|(style, text)| (*style, (*text).to_string())) - .collect(), - ); - } - Ok(highlighted_lines) -} - -fn set_syntax_set<'a>( - syntax_set: &'a SyntaxSet, - file_path: &Path, -) -> &'a SyntaxReference { - syntax_set - .find_syntax_for_file(file_path) - .unwrap_or(None) - .unwrap_or_else(|| { - warn!( - "No syntax found for {:?}, defaulting to plain text", - file_path - ); - syntax_set.find_syntax_plain_text() - }) -} - -#[derive(Debug, Clone, PartialEq, Hash)] -pub struct HighlightedLines { - pub lines: Vec>, - //pub state: Option, -} - -impl HighlightedLines { - pub fn new( - lines: Vec>, - _state: &Option, - ) -> Self { - Self { lines, /*state*/ } - } -} - -pub fn compute_highlights_incremental( - file_path: &Path, - lines: &[String], - syntax_set: &SyntaxSet, - syntax_theme: &Theme, - _cached_lines: Option<&HighlightedLines>, -) -> Result { - let mut highlighted_lines: Vec<_>; - let mut highlighter: LineHighlighter; - - //if let Some(HighlightedLines { - // lines: c_lines, - // state: Some(s), - //}) = cached_lines - //{ - // highlighter = LineHighlighter::from_state(s, syntax_theme); - // highlighted_lines = c_lines; - //} else { - // let syntax = set_syntax_set(syntax_set, file_path); - // highlighter = LineHighlighter::new(syntax, syntax_theme); - // highlighted_lines = Vec::new(); - //}; - let syntax = set_syntax_set(syntax_set, file_path); - highlighter = LineHighlighter::new(syntax, syntax_theme); - highlighted_lines = Vec::with_capacity(lines.len()); - - for line in lines { - let hl_regions = highlighter.highlight_line(line, syntax_set)?; - highlighted_lines.push( - hl_regions - .iter() - .map(|(style, text)| (*style, (*text).to_string())) - .collect(), - ); - } - - Ok(HighlightedLines::new( - highlighted_lines, - &Some(HighlightingState::new( - highlighter.parse_state.clone(), - highlighter.highlight_state.clone(), - )), - )) -} - -#[allow(dead_code)] -pub fn compute_highlights_for_line<'a>( - line: &'a str, - syntax_set: &SyntaxSet, - syntax_theme: &Theme, - file_path: &str, -) -> Result> { - let syntax = syntax_set.find_syntax_for_file(file_path)?; - match syntax { - None => { - warn!( - "No syntax found for path {:?}, defaulting to plain text", - file_path - ); - Ok(vec![(Style::default(), line)]) - } - Some(syntax) => { - let mut highlighter = HighlightLines::new(syntax, syntax_theme); - Ok(highlighter.highlight_line(line, syntax_set)?) - } - } -} - -// Based on code from https://github.com/sharkdp/bat e981e974076a926a38f124b7d8746de2ca5f0a28 -// -// Copyright (c) 2018-2023 bat-developers (https://github.com/sharkdp/bat). -// -// 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. - -use directories::BaseDirs; - -#[cfg(target_os = "macos")] -use std::env; - -/// Wrapper for 'dirs' that treats `MacOS` more like `Linux`, by following the XDG specification. -/// -/// This means that the `XDG_CACHE_HOME` and `XDG_CONFIG_HOME` environment variables are -/// checked first. The fallback directories are `~/.cache/bat` and `~/.config/bat`, respectively. -pub struct BatProjectDirs { - cache_dir: PathBuf, -} - -impl BatProjectDirs { - fn new() -> Option { - #[cfg(target_os = "macos")] - let cache_dir_op = env::var_os("XDG_CACHE_HOME") - .map(PathBuf::from) - .filter(|p| p.is_absolute()) - .or_else(|| BaseDirs::new().map(|d| d.home_dir().join(".cache"))); - - #[cfg(not(target_os = "macos"))] - let cache_dir_op = BaseDirs::new().map(|d| d.cache_dir().to_owned()); - - let cache_dir = cache_dir_op.map(|d| d.join("bat"))?; - - Some(BatProjectDirs { cache_dir }) - } - - pub fn cache_dir(&self) -> &Path { - &self.cache_dir - } -} - -pub fn load_highlighting_assets() -> HighlightingAssets { - let project_dirs = BatProjectDirs::new() - .unwrap_or_else(|| panic!("Could not get home directory")); - - HighlightingAssets::from_cache(project_dirs.cache_dir()) - .unwrap_or_else(|_| HighlightingAssets::from_binary()) -} - -pub trait HighlightingAssetsExt { - fn get_theme_no_output(&self, theme_name: &str) -> &Theme; -} - -impl HighlightingAssetsExt for HighlightingAssets { - /// Get a theme by name. If the theme is not found, the default theme is returned. - /// - /// This is an ugly hack to work around the fact that bat actually prints a warning - /// to stderr when a theme is not found which might mess up the TUI. This function - /// suppresses that warning by temporarily redirecting stderr and stdout. - fn get_theme_no_output(&self, theme_name: &str) -> &Theme { - let _e = Gag::stderr(); - let _o = Gag::stdout(); - let theme = self.get_theme(theme_name); - theme - } -}