From edd9df4e2911e1fd8e96a83e9f4696f61b0f5647 Mon Sep 17 00:00:00 2001 From: Alexandre Pasmantier <47638216+alexpasmantier@users.noreply.github.com> Date: Sun, 24 Nov 2024 00:20:04 +0100 Subject: [PATCH] fix(entry): always preserve raw input + match ranges conversions (#62) * fix(entry): preserve raw input * chore(version): bump workspace crates and television * test: add tests for replace_non_printable and cleanup commented out code * chore(changelog): update changelog (auto) * chore(deps): update cargo dependencies --------- Co-authored-by: github-actions[bot] --- .github/workflows/changelog.yml | 2 +- CHANGELOG.md | 10 +- Cargo.lock | 146 +++++++----- Cargo.toml | 12 +- crates/television-channels/Cargo.toml | 8 +- .../television-channels/src/channels/alias.rs | 5 +- .../television-channels/src/channels/env.rs | 13 +- .../television-channels/src/channels/files.rs | 22 +- .../src/channels/git_repos.rs | 15 +- .../television-channels/src/channels/stdin.rs | 3 +- .../television-channels/src/channels/text.rs | 29 +-- crates/television-channels/src/entry.rs | 14 -- crates/television-derive/Cargo.toml | 2 +- crates/television-fuzzy/Cargo.toml | 2 +- crates/television-previewers/Cargo.toml | 6 +- .../television-previewers/src/previewers.rs | 15 +- .../src/previewers/basic.rs | 1 + .../src/previewers/directory.rs | 1 + .../src/previewers/env.rs | 1 + .../src/previewers/files.rs | 6 +- .../src/previewers/meta.rs | 8 +- crates/television-utils/Cargo.toml | 2 +- crates/television-utils/src/strings.rs | 218 +++++++++++++++--- crates/television/television.rs | 8 +- crates/television/ui/preview.rs | 24 +- crates/television/ui/results.rs | 114 ++++----- 26 files changed, 416 insertions(+), 271 deletions(-) diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 10c15e0..eed968c 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -36,5 +36,5 @@ jobs: git config user.email 'github-actions[bot]@users.noreply.github.com' set +e git add CHANGELOG.md - git commit -m "Update changelog" + git commit -m "chore(changelog): update changelog (auto)" git push https://${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git ${{ steps.extract_branch.outputs.branch }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c49d87..f233d0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ### 🐛 Bug Fixes - Quote file names that contain spaces when printing them to stdout (#51) +- *(entry)* Preserve raw input ### 🚜 Refactor @@ -15,15 +16,20 @@ All notable changes to this project will be documented in this file. ### 📚 Documentation - Terminal emulators compatibility and good first issues (#56) -- *(contributing)* Add setup step ### 🎨 Styling -- *(git)* Enforce conventional commits on git push with a hook +- *(git)* Enforce conventional commits on git push with a hook (#61) + +### 🧪 Testing + +- Add tests for replace_non_printable and cleanup commented out code ### ⚙️ Miscellaneous Tasks - Add readme version update to github actions (#55) +- *(version)* Bump workspace crates and television +- *(changelog)* Update changelog (auto) ### Build diff --git a/Cargo.lock b/Cargo.lock index 7fd9137..0fe09f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,20 +249,20 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "serde", ] [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "bytes" @@ -351,9 +351,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.37" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "shlex", ] @@ -366,9 +366,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -400,9 +400,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "clipboard-win" @@ -575,9 +575,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -749,6 +749,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -905,9 +911,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -1484,7 +1490,7 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -1716,7 +1722,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "same-file", "walkdir", "winapi-util", @@ -1746,10 +1752,14 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "instability" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" dependencies = [ + "darling", + "indoc", + "pretty_assertions", + "proc-macro2", "quote", "syn", ] @@ -1771,9 +1781,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "jiff" @@ -1825,9 +1835,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.162" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libloading" @@ -1864,9 +1874,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -2274,11 +2284,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2386,7 +2406,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -2401,9 +2421,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2470,9 +2490,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -2525,18 +2545,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -2545,9 +2565,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -2748,9 +2768,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -2792,7 +2812,7 @@ dependencies = [ [[package]] name = "television" -version = "0.5.1" +version = "0.5.2" dependencies = [ "better-panic", "clap", @@ -2823,7 +2843,7 @@ dependencies = [ [[package]] name = "television-channels" -version = "0.0.5" +version = "0.0.6" dependencies = [ "clap", "color-eyre", @@ -2842,7 +2862,7 @@ dependencies = [ [[package]] name = "television-derive" -version = "0.0.5" +version = "0.0.6" dependencies = [ "proc-macro2", "quote", @@ -2851,7 +2871,7 @@ dependencies = [ [[package]] name = "television-fuzzy" -version = "0.0.5" +version = "0.0.6" dependencies = [ "nucleo", "parking_lot", @@ -2859,7 +2879,7 @@ dependencies = [ [[package]] name = "television-previewers" -version = "0.0.5" +version = "0.0.6" dependencies = [ "color-eyre", "devicons", @@ -2874,7 +2894,7 @@ dependencies = [ [[package]] name = "television-utils" -version = "0.0.5" +version = "0.0.6" dependencies = [ "bat", "color-eyre", @@ -3158,9 +3178,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" @@ -3208,9 +3228,9 @@ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -3669,11 +3689,17 @@ dependencies = [ "hashlink", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -3683,9 +3709,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", @@ -3715,18 +3741,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0a4772e..1f1b1f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "television" -version = "0.5.1" +version = "0.5.2" edition = "2021" description = "The revolution will be televised." license = "MIT" @@ -54,11 +54,11 @@ name = "tv" [dependencies] # workspace dependencies -television-fuzzy = { path = "crates/television-fuzzy", version = "0.0.5" } -television-derive = { path = "crates/television-derive", version = "0.0.5" } -television-channels = { path = "crates/television-channels", version = "0.0.5" } -television-previewers = { path = "crates/television-previewers", version = "0.0.5" } -television-utils = { path = "crates/television-utils", version = "0.0.5" } +television-fuzzy = { path = "crates/television-fuzzy", version = "0.0.6" } +television-derive = { path = "crates/television-derive", version = "0.0.6" } +television-channels = { path = "crates/television-channels", version = "0.0.6" } +television-previewers = { path = "crates/television-previewers", version = "0.0.6" } +television-utils = { path = "crates/television-utils", version = "0.0.6" } # external dependencies better-panic = "0.3.0" diff --git a/crates/television-channels/Cargo.toml b/crates/television-channels/Cargo.toml index fb559e0..1287bc7 100644 --- a/crates/television-channels/Cargo.toml +++ b/crates/television-channels/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "television-channels" -version = "0.0.5" +version = "0.0.6" description.workspace = true authors.workspace = true repository.workspace = true @@ -13,9 +13,9 @@ edition.workspace = true rust-version.workspace = true [dependencies] -television-fuzzy = { path = "../television-fuzzy", version = "0.0.5" } -television-utils = { path = "../television-utils", version = "0.0.5" } -television-derive = { path = "../television-derive", version = "0.0.5" } +television-fuzzy = { path = "../television-fuzzy", version = "0.0.6" } +television-utils = { path = "../television-utils", version = "0.0.6" } +television-derive = { path = "../television-derive", version = "0.0.6" } devicons = "0.6.11" tracing = "0.1.40" eyre = "0.6.12" diff --git a/crates/television-channels/src/channels/alias.rs b/crates/television-channels/src/channels/alias.rs index 61dda60..41aa1f5 100644 --- a/crates/television-channels/src/channels/alias.rs +++ b/crates/television-channels/src/channels/alias.rs @@ -4,7 +4,6 @@ use crate::entry::PreviewType; use devicons::FileIcon; use television_fuzzy::matcher::{config::Config, injector::Injector, Matcher}; use television_utils::indices::sep_name_and_value_indices; -use television_utils::strings::preprocess_line; use tracing::debug; #[derive(Debug, Clone)] @@ -146,8 +145,8 @@ async fn load_aliases(injector: Injector) { if let Some(name) = parts.next() { if let Some(value) = parts.next() { return Some(Alias::new( - preprocess_line(name), - preprocess_line(value), + name.to_string(), + value.to_string(), )); } } diff --git a/crates/television-channels/src/channels/env.rs b/crates/television-channels/src/channels/env.rs index c0e1957..930652b 100644 --- a/crates/television-channels/src/channels/env.rs +++ b/crates/television-channels/src/channels/env.rs @@ -4,7 +4,6 @@ use super::OnAir; use crate::entry::{Entry, PreviewType}; use television_fuzzy::matcher::{config::Config, Matcher}; use television_utils::indices::sep_name_and_value_indices; -use television_utils::strings::preprocess_line; #[derive(Debug, Clone)] struct EnvVar { @@ -26,15 +25,9 @@ impl Channel { let matcher = Matcher::new(Config::default().n_threads(NUM_THREADS)); let injector = matcher.injector(); for (name, value) in std::env::vars() { - let () = injector.push( - EnvVar { - name: preprocess_line(&name), - value: preprocess_line(&value), - }, - |e, cols| { - cols[0] = (e.name.clone() + &e.value).into(); - }, - ); + let () = injector.push(EnvVar { name, value }, |e, cols| { + cols[0] = (e.name.clone() + &e.value).into(); + }); } Channel { matcher, diff --git a/crates/television-channels/src/channels/files.rs b/crates/television-channels/src/channels/files.rs index 80a1a6d..e2e520e 100644 --- a/crates/television-channels/src/channels/files.rs +++ b/crates/television-channels/src/channels/files.rs @@ -5,7 +5,6 @@ use std::collections::HashSet; use std::path::PathBuf; use television_fuzzy::matcher::{config::Config, injector::Injector, Matcher}; use television_utils::files::{walk_builder, DEFAULT_NUM_THREADS}; -use television_utils::strings::preprocess_line; pub struct Channel { matcher: Matcher, @@ -58,7 +57,7 @@ impl From<&mut TelevisionChannel> for Channel { Self::new( entries .iter() - .map(|entry| PathBuf::from(entry.display_name())) + .map(|entry| PathBuf::from(&entry.name)) .collect::>() .into_iter() .collect(), @@ -132,16 +131,15 @@ async fn load_files(paths: Vec, injector: Injector) { Box::new(move |result| { if let Ok(entry) = result { if entry.file_type().unwrap().is_file() { - let file_path = preprocess_line( - &entry - .path() - .strip_prefix(¤t_dir) - .unwrap_or(entry.path()) - .to_string_lossy(), - ); - let () = injector.push(file_path, |e, cols| { - cols[0] = e.clone().into(); - }); + let file_path = &entry + .path() + .strip_prefix(¤t_dir) + .unwrap_or(entry.path()) + .to_string_lossy(); + let () = + injector.push(file_path.to_string(), |e, cols| { + cols[0] = e.clone().into(); + }); } } ignore::WalkState::Continue diff --git a/crates/television-channels/src/channels/git_repos.rs b/crates/television-channels/src/channels/git_repos.rs index 9ee72cd..76cceb4 100644 --- a/crates/television-channels/src/channels/git_repos.rs +++ b/crates/television-channels/src/channels/git_repos.rs @@ -9,7 +9,6 @@ use crate::channels::OnAir; use crate::entry::{Entry, PreviewType}; use television_fuzzy::matcher::{config::Config, injector::Injector, Matcher}; use television_utils::files::{walk_builder, DEFAULT_NUM_THREADS}; -use television_utils::strings::preprocess_line; pub struct Channel { matcher: Matcher, @@ -150,13 +149,15 @@ async fn crawl_for_repos(starting_point: PathBuf, injector: Injector) { if entry.file_type().unwrap().is_dir() { // if the entry is a .git directory, add its parent to the list of git repos if entry.path().ends_with(".git") { - let parent_path = preprocess_line( - &entry.path().parent().unwrap().to_string_lossy(), - ); + let parent_path = + &entry.path().parent().unwrap().to_string_lossy(); debug!("Found git repo: {:?}", parent_path); - let () = injector.push(parent_path, |e, cols| { - cols[0] = e.clone().into(); - }); + let () = injector.push( + parent_path.to_string(), + |e, cols| { + cols[0] = e.clone().into(); + }, + ); return ignore::WalkState::Skip; } } diff --git a/crates/television-channels/src/channels/stdin.rs b/crates/television-channels/src/channels/stdin.rs index 8d448e2..b19b621 100644 --- a/crates/television-channels/src/channels/stdin.rs +++ b/crates/television-channels/src/channels/stdin.rs @@ -6,7 +6,6 @@ use devicons::FileIcon; use super::OnAir; use crate::entry::{Entry, PreviewType}; use television_fuzzy::matcher::{config::Config, Matcher}; -use television_utils::strings::preprocess_line; pub struct Channel { matcher: Matcher, @@ -20,7 +19,7 @@ impl Channel { let mut lines = Vec::new(); for line in std::io::stdin().lock().lines().map_while(Result::ok) { if !line.trim().is_empty() { - lines.push(preprocess_line(&line)); + lines.push(line); } } let matcher = Matcher::new(Config::default().n_threads(NUM_THREADS)); diff --git a/crates/television-channels/src/channels/text.rs b/crates/television-channels/src/channels/text.rs index 654b63f..2f666d5 100644 --- a/crates/television-channels/src/channels/text.rs +++ b/crates/television-channels/src/channels/text.rs @@ -11,8 +11,7 @@ use std::{ use television_fuzzy::matcher::{config::Config, injector::Injector, Matcher}; use television_utils::files::{walk_builder, DEFAULT_NUM_THREADS}; use television_utils::strings::{ - preprocess_line, proportion_of_printable_ascii_characters, - PRINTABLE_ASCII_THRESHOLD, + proportion_of_printable_ascii_characters, PRINTABLE_ASCII_THRESHOLD, }; use tracing::{debug, warn}; @@ -84,7 +83,7 @@ impl Channel { for entry in entries.into_iter().take(MAX_LINES_IN_MEM) { injector.push( CandidateLine::new( - entry.display_name().into(), + entry.name.into(), entry.value.unwrap(), entry.line_number.unwrap(), ), @@ -169,15 +168,11 @@ impl OnAir for Channel { let line = item.matched_string; let display_path = item.inner.path.to_string_lossy().to_string(); - Entry::new( - display_path.clone() + &item.inner.line_number.to_string(), - PreviewType::Files, - ) - .with_display_name(display_path) - .with_value(line) - .with_value_match_ranges(item.match_indices) - .with_icon(FileIcon::from(item.inner.path.as_path())) - .with_line_number(item.inner.line_number) + Entry::new(display_path.clone(), PreviewType::Files) + .with_value(line) + .with_value_match_ranges(item.match_indices) + .with_icon(FileIcon::from(item.inner.path.as_path())) + .with_line_number(item.inner.line_number) }) .collect() } @@ -186,11 +181,6 @@ impl OnAir for Channel { self.matcher.get_result(index).map(|item| { let display_path = item.inner.path.to_string_lossy().to_string(); Entry::new(display_path.clone(), PreviewType::Files) - .with_display_name( - display_path.clone() - + ":" - + &item.inner.line_number.to_string(), - ) .with_icon(FileIcon::from(item.inner.path.as_path())) .with_line_number(item.inner.line_number) }) @@ -314,8 +304,7 @@ fn try_inject_lines( match maybe_line { Ok(l) => { line_number += 1; - let line = preprocess_line(&l); - if line.is_empty() { + if l.is_empty() { debug!("Empty line"); continue; } @@ -323,7 +312,7 @@ fn try_inject_lines( path.strip_prefix(current_dir) .unwrap_or(path) .to_path_buf(), - line, + l, line_number, ); let () = injector.push(candidate, |c, cols| { diff --git a/crates/television-channels/src/entry.rs b/crates/television-channels/src/entry.rs index 128c953..1aa3489 100644 --- a/crates/television-channels/src/entry.rs +++ b/crates/television-channels/src/entry.rs @@ -10,8 +10,6 @@ use devicons::FileIcon; pub struct Entry { /// The name of the entry. pub name: String, - /// The display name of the entry. - display_name: Option, /// An optional value associated with the entry. pub value: Option, /// The optional ranges for matching characters in the name. @@ -35,7 +33,6 @@ impl Entry { /// use devicons::FileIcon; /// /// let entry = Entry::new("name".to_string(), PreviewType::EnvVar) - /// .with_display_name("display_name".to_string()) /// .with_value("value".to_string()) /// .with_name_match_ranges(vec![(0, 1)]) /// .with_value_match_ranges(vec![(0, 1)]) @@ -53,7 +50,6 @@ impl Entry { pub fn new(name: String, preview_type: PreviewType) -> Self { Self { name, - display_name: None, value: None, name_match_ranges: None, value_match_ranges: None, @@ -63,11 +59,6 @@ impl Entry { } } - pub fn with_display_name(mut self, display_name: String) -> Self { - self.display_name = Some(display_name); - self - } - pub fn with_value(mut self, value: String) -> Self { self.value = Some(value); self @@ -99,10 +90,6 @@ impl Entry { self } - pub fn display_name(&self) -> &str { - self.display_name.as_ref().unwrap_or(&self.name) - } - pub fn stdout_repr(&self) -> String { let mut repr = self.name.clone(); if repr.contains(|c| char::is_ascii_whitespace(&c)) { @@ -118,7 +105,6 @@ impl Entry { pub const ENTRY_PLACEHOLDER: Entry = Entry { name: String::new(), - display_name: None, value: None, name_match_ranges: None, value_match_ranges: None, diff --git a/crates/television-derive/Cargo.toml b/crates/television-derive/Cargo.toml index 83e679a..018f9ec 100644 --- a/crates/television-derive/Cargo.toml +++ b/crates/television-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "television-derive" -version = "0.0.5" +version = "0.0.6" description.workspace = true authors.workspace = true repository.workspace = true diff --git a/crates/television-fuzzy/Cargo.toml b/crates/television-fuzzy/Cargo.toml index 38b71e8..ef712ef 100644 --- a/crates/television-fuzzy/Cargo.toml +++ b/crates/television-fuzzy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "television-fuzzy" -version = "0.0.5" +version = "0.0.6" description.workspace = true authors.workspace = true repository.workspace = true diff --git a/crates/television-previewers/Cargo.toml b/crates/television-previewers/Cargo.toml index 0457454..11ec299 100644 --- a/crates/television-previewers/Cargo.toml +++ b/crates/television-previewers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "television-previewers" -version = "0.0.5" +version = "0.0.6" description.workspace = true authors.workspace = true repository.workspace = true @@ -14,8 +14,8 @@ rust-version.workspace = true [dependencies] syntect = "5.2.0" -television-channels = { path = "../television-channels", version = "0.0.5" } -television-utils = { path = "../television-utils", version = "0.0.5" } +television-channels = { path = "../television-channels", version = "0.0.6" } +television-utils = { path = "../television-utils", version = "0.0.6" } tracing = "0.1.40" parking_lot = "0.12.3" tokio = "1.41.1" diff --git a/crates/television-previewers/src/previewers.rs b/crates/television-previewers/src/previewers.rs index 8c68167..a7e1c4b 100644 --- a/crates/television-previewers/src/previewers.rs +++ b/crates/television-previewers/src/previewers.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use devicons::FileIcon; use television_channels::entry::{Entry, PreviewType}; pub mod basic; @@ -46,6 +47,7 @@ pub const FILE_TOO_LARGE_MSG: &str = "File too large"; pub struct Preview { pub title: String, pub content: PreviewContent, + pub icon: Option, } impl Default for Preview { @@ -53,13 +55,22 @@ impl Default for Preview { Preview { title: String::new(), content: PreviewContent::Empty, + icon: None, } } } impl Preview { - pub fn new(title: String, content: PreviewContent) -> Self { - Preview { title, content } + pub fn new( + title: String, + content: PreviewContent, + icon: Option, + ) -> Self { + Preview { + title, + content, + icon, + } } pub fn total_lines(&self) -> u16 { diff --git a/crates/television-previewers/src/previewers/basic.rs b/crates/television-previewers/src/previewers/basic.rs index 0c61403..323ac04 100644 --- a/crates/television-previewers/src/previewers/basic.rs +++ b/crates/television-previewers/src/previewers/basic.rs @@ -22,6 +22,7 @@ impl BasicPreviewer { Arc::new(Preview { title: entry.name.clone(), content: PreviewContent::PlainTextWrapped(entry.name.clone()), + icon: entry.icon, }) } } diff --git a/crates/television-previewers/src/previewers/directory.rs b/crates/television-previewers/src/previewers/directory.rs index 0660f96..e460045 100644 --- a/crates/television-previewers/src/previewers/directory.rs +++ b/crates/television-previewers/src/previewers/directory.rs @@ -58,6 +58,7 @@ fn build_tree_preview(entry: &Entry) -> Preview { .map(std::borrow::ToOwned::to_owned) .collect(), ), + icon: entry.icon, } } diff --git a/crates/television-previewers/src/previewers/env.rs b/crates/television-previewers/src/previewers/env.rs index 773276f..5753463 100644 --- a/crates/television-previewers/src/previewers/env.rs +++ b/crates/television-previewers/src/previewers/env.rs @@ -35,6 +35,7 @@ impl EnvVarPreviewer { } else { PreviewContent::Empty }, + icon: entry.icon, }); self.cache.insert(entry.clone(), preview.clone()); preview diff --git a/crates/television-previewers/src/previewers/files.rs b/crates/television-previewers/src/previewers/files.rs index 588a0f7..72b247a 100644 --- a/crates/television-previewers/src/previewers/files.rs +++ b/crates/television-previewers/src/previewers/files.rs @@ -164,7 +164,7 @@ impl FilePreviewer { .map_while(Result::ok) // we need to add a newline here because sublime syntaxes expect one // to be present at the end of each line - .map(|line| preprocess_line(&line) + "\n") + .map(|line| preprocess_line(&line).0 + "\n") .collect(); match syntax::compute_highlights_for_path( @@ -185,6 +185,7 @@ impl FilePreviewer { PreviewContent::SyntectHighlightedText( highlighted_lines, ), + entry_c.icon, )), ); debug!("Inserted highlighted preview into cache"); @@ -224,7 +225,7 @@ fn plain_text_preview(title: &str, reader: BufReader<&File>) -> Arc { // 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)), + Ok(line) => lines.push(preprocess_line(&line).0), Err(e) => { warn!("Error reading file: {:?}", e); return meta::not_supported(title); @@ -237,5 +238,6 @@ fn plain_text_preview(title: &str, reader: BufReader<&File>) -> Arc { Arc::new(Preview::new( title.to_string(), PreviewContent::PlainText(lines), + None, )) } diff --git a/crates/television-previewers/src/previewers/meta.rs b/crates/television-previewers/src/previewers/meta.rs index d12d377..a30547e 100644 --- a/crates/television-previewers/src/previewers/meta.rs +++ b/crates/television-previewers/src/previewers/meta.rs @@ -5,6 +5,7 @@ pub fn not_supported(title: &str) -> Arc { Arc::new(Preview::new( title.to_string(), PreviewContent::NotSupported, + None, )) } @@ -12,10 +13,15 @@ pub fn file_too_large(title: &str) -> Arc { Arc::new(Preview::new( title.to_string(), PreviewContent::FileTooLarge, + None, )) } #[allow(dead_code)] pub fn loading(title: &str) -> Arc { - Arc::new(Preview::new(title.to_string(), PreviewContent::Loading)) + Arc::new(Preview::new( + title.to_string(), + PreviewContent::Loading, + None, + )) } diff --git a/crates/television-utils/Cargo.toml b/crates/television-utils/Cargo.toml index 4394b4e..7cb4a9c 100644 --- a/crates/television-utils/Cargo.toml +++ b/crates/television-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "television-utils" -version = "0.0.5" +version = "0.0.6" description.workspace = true authors.workspace = true repository.workspace = true diff --git a/crates/television-utils/src/strings.rs b/crates/television-utils/src/strings.rs index 7023579..07d4dff 100644 --- a/crates/television-utils/src/strings.rs +++ b/crates/television-utils/src/strings.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use std::fmt::Write; /// Returns the index of the next character boundary in the given string. /// @@ -121,7 +120,24 @@ pub fn slice_up_to_char_boundary(s: &str, byte_index: usize) -> &str { } /// Attempts to parse a UTF-8 character from the given byte slice. -fn try_parse_utf8_char(input: &[u8]) -> Option<(char, usize)> { +/// +/// The function returns the parsed character and the number of bytes consumed. +/// +/// # Examples +/// ``` +/// use television_utils::strings::try_parse_utf8_char; +/// +/// let input = b"Hello, World!"; +/// let (chr, n) = try_parse_utf8_char(input).unwrap(); +/// assert_eq!(chr, 'H'); +/// assert_eq!(n, 1); +/// +/// let input = b"\xF0\x9F\x91\x8B\xF0\x9F\x8C\x8D!"; +/// let (chr, n) = try_parse_utf8_char(input).unwrap(); +/// assert_eq!(chr, '👋'); +/// assert_eq!(n, 4); +/// ``` +pub fn try_parse_utf8_char(input: &[u8]) -> Option<(char, usize)> { let str_from_utf8 = |seq| std::str::from_utf8(seq).ok(); let decoded = input @@ -143,7 +159,6 @@ lazy_static! { pub const EMPTY_STRING: &str = ""; pub const TAB_WIDTH: usize = 4; -const SPACE_CHARACTER: char = ' '; const TAB_CHARACTER: char = '\t'; const LINE_FEED_CHARACTER: char = '\x0A'; const DELETE_CHARACTER: char = '\x7F'; @@ -152,62 +167,68 @@ const NULL_CHARACTER: char = '\x00'; const UNIT_SEPARATOR_CHARACTER: char = '\u{001F}'; const APPLICATION_PROGRAM_COMMAND_CHARACTER: char = '\u{009F}'; +#[allow(clippy::missing_panics_doc)] /// Replaces non-printable characters in the given byte slice with default printable characters. /// /// The tab width is used to determine how many spaces to replace a tab character with. /// The default printable character for non-printable characters is the Unicode symbol for NULL. /// +/// The function returns a tuple containing the processed string and a vector of offsets introduced +/// by the transformation. +/// /// # Examples /// ``` /// use television_utils::strings::replace_non_printable; /// /// let input = b"Hello, World!"; -/// let output = replace_non_printable(input, 2); +/// let (output, offsets) = replace_non_printable(input, 2); /// assert_eq!(output, "Hello, World!"); +/// assert_eq!(offsets, vec![0,0,0,0,0,0,0,0,0,0,0,0,0]); /// -/// let input = b"Hello\tWorld!"; -/// let output = replace_non_printable(input, 2); -/// assert_eq!(output, "Hello World!"); +/// let input = b"Hello,\tWorld!"; +/// let (output, offsets) = replace_non_printable(input, 4); +/// assert_eq!(output, "Hello, World!"); +/// assert_eq!(offsets, vec![0,0,0,0,0,0,0,3,3,3,3,3,3]); /// -/// let input = b"Hello\nWorld!"; -/// let output = replace_non_printable(input, 2); -/// assert_eq!(output, "HelloWorld!"); -/// -/// let input = b"Hello\x00World!"; -/// let output = replace_non_printable(input, 2); -/// assert_eq!(output, "Hello␀World!"); -/// -/// let input = b"Hello\x7FWorld!"; -/// let output = replace_non_printable(input, 2); -/// assert_eq!(output, "Hello␀World!"); +/// let input = b"Hello,\nWorld!"; +/// let (output, offsets) = replace_non_printable(input, 2); +/// assert_eq!(output, "Hello,World!"); +/// assert_eq!(offsets, vec![0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1]); /// ``` -pub fn replace_non_printable(input: &[u8], tab_width: usize) -> String { +pub fn replace_non_printable( + input: &[u8], + tab_width: usize, +) -> (String, Vec) { let mut output = String::new(); + let mut offsets = Vec::new(); + let mut cumulative_offset: i16 = 0; let mut idx = 0; let len = input.len(); while idx < len { + offsets.push(cumulative_offset); if let Some((chr, skip_ahead)) = try_parse_utf8_char(&input[idx..]) { idx += skip_ahead; match chr { - // space - SPACE_CHARACTER => output.push(' '), // tab TAB_CHARACTER => { output.push_str(&" ".repeat(tab_width)); + cumulative_offset += i16::try_from(tab_width).unwrap() - 1; } // line feed - LINE_FEED_CHARACTER => {} + LINE_FEED_CHARACTER => { + cumulative_offset -= 1; + } // ASCII control characters from 0x00 to 0x1F // + control characters from \u{007F} to \u{009F} + // + BOM NULL_CHARACTER..=UNIT_SEPARATOR_CHARACTER - | DELETE_CHARACTER..=APPLICATION_PROGRAM_COMMAND_CHARACTER => { + | DELETE_CHARACTER..=APPLICATION_PROGRAM_COMMAND_CHARACTER + | BOM_CHARACTER => { output.push(*NULL_SYMBOL); } - // don't print BOMs - BOM_CHARACTER => {} // Unicode characters above 0x0700 seem unstable with ratatui c if c > '\u{0700}' => { output.push(*NULL_SYMBOL); @@ -216,12 +237,12 @@ pub fn replace_non_printable(input: &[u8], tab_width: usize) -> String { c => output.push(c), } } else { - write!(output, "\\x{:02X}", input[idx]).ok(); + output.push(*NULL_SYMBOL); idx += 1; } } - output + (output, offsets) } /// The threshold for considering a buffer to be printable ASCII. @@ -272,18 +293,21 @@ const MAX_LINE_LENGTH: usize = 300; /// use television_utils::strings::preprocess_line; /// /// let line = "Hello, World!"; -/// let processed = preprocess_line(line); +/// let (processed, offsets) = preprocess_line(line); /// assert_eq!(processed, "Hello, World!"); +/// assert_eq!(offsets, vec![0,0,0,0,0,0,0,0,0,0,0,0,0]); /// /// let line = "\x00World\x7F!"; -/// let processed = preprocess_line(line); +/// let (processed, offsets) = preprocess_line(line); /// assert_eq!(processed, "␀World␀!"); +/// assert_eq!(offsets, vec![0,0,0,0,0,0,0,0]); /// /// let line = "a".repeat(400); -/// let processed = preprocess_line(&line); +/// let (processed, offsets) = preprocess_line(&line); /// assert_eq!(processed.len(), 300); +/// assert_eq!(offsets, vec![0; 300]); /// ``` -pub fn preprocess_line(line: &str) -> String { +pub fn preprocess_line(line: &str) -> (String, Vec) { replace_non_printable( { if line.len() > MAX_LINE_LENGTH { @@ -292,12 +316,101 @@ pub fn preprocess_line(line: &str) -> String { line } } - .trim_end_matches(['\r', '\n', '\0']) .as_bytes(), TAB_WIDTH, ) } +/// Make a matched string printable while preserving match ranges in the process. +/// +/// This function preprocesses the matched string and returns a printable version of it along with +/// the match ranges adjusted to the new string. +/// +/// # Examples +/// ``` +/// use television_utils::strings::make_matched_string_printable; +/// +/// let matched_string = "Hello, World!"; +/// let match_ranges = vec![(0, 1), (7, 8)]; +/// let match_ranges = Some(match_ranges.as_slice()); +/// let (printable, match_indices) = make_matched_string_printable(matched_string, match_ranges); +/// assert_eq!(printable, "Hello, World!"); +/// assert_eq!(match_indices, vec![(0, 1), (7, 8)]); +/// +/// let matched_string = "Hello,\tWorld!"; +/// let match_ranges = vec![(0, 1), (7, 8)]; +/// let match_ranges = Some(match_ranges.as_slice()); +/// let (printable, match_indices) = make_matched_string_printable(matched_string, match_ranges); +/// assert_eq!(printable, "Hello, World!"); +/// assert_eq!(match_indices, vec![(0, 1), (10, 11)]); +/// +/// let matched_string = "Hello,\nWorld!"; +/// let match_ranges = vec![(0, 1), (7, 8)]; +/// let match_ranges = Some(match_ranges.as_slice()); +/// let (printable, match_indices) = make_matched_string_printable(matched_string, match_ranges); +/// assert_eq!(printable, "Hello,World!"); +/// assert_eq!(match_indices, vec![(0, 1), (6, 7)]); +/// +/// let matched_string = "Hello, World!"; +/// let (printable, match_indices) = make_matched_string_printable(matched_string, None); +/// assert_eq!(printable, "Hello, World!"); +/// assert_eq!(match_indices, vec![]); +/// +/// let matched_string = "build.rs"; +/// let match_ranges = vec![(0, 1), (7, 8)]; +/// let match_ranges = Some(match_ranges.as_slice()); +/// let (printable, match_indices) = make_matched_string_printable(matched_string, match_ranges); +/// assert_eq!(printable, "build.rs"); +/// assert_eq!(match_indices, vec![(0, 1), (7, 8)]); +/// +/// let matched_string = "a\tb"; +/// let match_ranges = vec![(0, 1), (2, 3)]; +/// let match_ranges = Some(match_ranges.as_slice()); +/// let (printable, match_indices) = make_matched_string_printable(matched_string, match_ranges); +/// assert_eq!(printable, "a b"); +/// assert_eq!(match_indices, vec![(0, 1), (5, 6)]); +/// +/// let matched_string = "a\tbcd".repeat(65); +/// let match_ranges = vec![(0, 1), (310, 311)]; +/// let match_ranges = Some(match_ranges.as_slice()); +/// let (printable, match_indices) = make_matched_string_printable(&matched_string, match_ranges); +/// assert_eq!(printable.len(), 480); +/// assert_eq!(match_indices, vec![(0, 1)]); +/// ``` +/// +/// # Panics +/// This will panic if the length of the printable string or the match indices don't fit into a +/// `u32`. +pub fn make_matched_string_printable( + matched_string: &str, + match_ranges: Option<&[(u32, u32)]>, +) -> (String, Vec<(u32, u32)>) { + let (printable, transformation_offsets) = preprocess_line(matched_string); + let mut match_indices = Vec::new(); + + if let Some(ranges) = match_ranges { + for (start, end) in ranges.iter().take_while(|(start, _)| { + *start < u32::try_from(transformation_offsets.len()).unwrap() + }) { + let new_start = i64::from(*start) + + i64::from(transformation_offsets[*start as usize]); + let new_end = i64::from(*end) + + i64::from( + // Use the last offset if the end index is out of bounds + // (this will be the case when the match range includes the last character) + transformation_offsets[(*end as usize) + .min(transformation_offsets.len() - 1)], + ); + match_indices.push(( + u32::try_from(new_start).unwrap(), + u32::try_from(new_end).unwrap(), + )); + } + } + + (printable, match_indices) +} + /// Shrink a string to a maximum length, adding an ellipsis in the middle. /// /// If the string is shorter than the maximum length, it is returned as is. @@ -402,7 +515,7 @@ mod tests { } fn test_replace_non_printable(input: &str, expected: &str) { - let actual = replace_non_printable(input.as_bytes(), 2); + let (actual, _offset) = replace_non_printable(input.as_bytes(), 2); assert_eq!(actual, expected); } @@ -438,7 +551,7 @@ mod tests { #[test] fn test_replace_non_printable_bom() { - test_replace_non_printable("Hello\u{FEFF}World!", "HelloWorld!"); + test_replace_non_printable("Hello\u{FEFF}World!", "Hello␀World!"); } #[test] @@ -446,6 +559,35 @@ mod tests { test_replace_non_printable("Àì", "Àì␀"); } + #[test] + fn test_replace_non_printable_range_tab() { + let input = b"Hello,\tWorld!"; + let (output, offsets) = replace_non_printable(input, 4); + assert_eq!(output, "Hello, World!"); + assert_eq!(offsets, vec![0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3]); + } + + #[test] + fn test_replace_non_printable_range_line_feed() { + let input = b"Hello,\nWorld!"; + let (output, offsets) = replace_non_printable(input, 2); + assert_eq!(output, "Hello,World!"); + assert_eq!(offsets, vec![0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1]); + } + + #[test] + fn test_replace_non_printable_no_range_changes() { + let input = b"Hello,\x00World!"; + let (output, offsets) = replace_non_printable(input, 2); + assert_eq!(output, "Hello,␀World!"); + assert_eq!(offsets, vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + let input = b"Hello,\x7FWorld!"; + let (output, offsets) = replace_non_printable(input, 2); + assert_eq!(output, "Hello,␀World!"); + assert_eq!(offsets, vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + } + fn test_proportion_of_printable_ascii_characters( input: &str, expected: f32, @@ -469,17 +611,17 @@ mod tests { } fn test_preprocess_line(input: &str, expected: &str) { - let actual = preprocess_line(input); - assert_eq!(actual, expected); + let (actual, _offset) = preprocess_line(input); + assert_eq!(actual, expected, "input: {:?}", input); } #[test] fn test_preprocess_line_cases() { test_preprocess_line("Hello, World!", "Hello, World!"); test_preprocess_line("Hello, World!\n", "Hello, World!"); - test_preprocess_line("Hello, World!\x00", "Hello, World!"); + test_preprocess_line("Hello, World!\x00", "Hello, World!␀"); test_preprocess_line("Hello, World!\x7F", "Hello, World!␀"); - test_preprocess_line("Hello, World!\u{FEFF}", "Hello, World!"); + test_preprocess_line("Hello, World!\u{FEFF}", "Hello, World!␀"); test_preprocess_line(&"a".repeat(400), &"a".repeat(300)); } } diff --git a/crates/television/television.rs b/crates/television/television.rs index f6f0a1f..8a2ee1e 100644 --- a/crates/television/television.rs +++ b/crates/television/television.rs @@ -399,15 +399,17 @@ impl Television { // top right block: preview title self.current_preview_total_lines = preview.total_lines(); - self.draw_preview_title_block(f, &layout, &selected_entry, &preview)?; + self.draw_preview_title_block(f, &layout, &preview)?; // bottom right block: preview content self.draw_preview_content_block( f, &layout, - &selected_entry, + selected_entry + .line_number + .map(|l| u16::try_from(l).unwrap_or(0)), &preview, - )?; + ); // remote control if matches!(self.mode, Mode::RemoteControl | Mode::SendToChannel) { diff --git a/crates/television/ui/preview.rs b/crates/television/ui/preview.rs index c0923af..2b0a549 100644 --- a/crates/television/ui/preview.rs +++ b/crates/television/ui/preview.rs @@ -10,7 +10,6 @@ use std::str::FromStr; use std::sync::Arc; use syntect::highlighting::Color as SyntectColor; use television_channels::channels::OnAir; -use television_channels::entry::Entry; use television_previewers::previewers::{ Preview, PreviewContent, FILE_TOO_LARGE_MSG, PREVIEW_NOT_SUPPORTED_MSG, }; @@ -28,13 +27,11 @@ impl Television { &self, f: &mut Frame, layout: &Layout, - selected_entry: &Entry, preview: &Arc, ) -> Result<()> { let mut preview_title_spans = Vec::new(); - if selected_entry.icon.is_some() && self.config.ui.use_nerd_font_icons - { - let icon = selected_entry.icon.as_ref().unwrap(); + if preview.icon.is_some() && self.config.ui.use_nerd_font_icons { + let icon = preview.icon.as_ref().unwrap(); preview_title_spans.push(Span::styled( { let mut icon_str = String::from(icon.icon); @@ -68,9 +65,9 @@ impl Television { &mut self, f: &mut Frame, layout: &Layout, - selected_entry: &Entry, + target_line: Option, preview: &Arc, - ) -> Result<()> { + ) { let preview_outer_block = Block::default() .title_top(Line::from(" Preview ").alignment(Alignment::Center)) .borders(Borders::ALL) @@ -101,13 +98,10 @@ impl Television { preview_inner_block, inner, preview, - selected_entry - .line_number - .map(|l| u16::try_from(l).unwrap_or(0)), + target_line, ); f.render_widget(preview_block, inner); //} - Ok(()) } #[allow(dead_code)] @@ -209,7 +203,7 @@ impl Television { .block(preview_block) .alignment(Alignment::Left) .style(Style::default().add_modifier(Modifier::ITALIC)), - _ => Paragraph::new(Text::raw(EMPTY_STRING)), + PreviewContent::Empty => Paragraph::new(Text::raw(EMPTY_STRING)), } } @@ -317,7 +311,7 @@ fn compute_paragraph_from_highlighted_lines( let line_number = build_line_number_span(i + 1).style(Style::default().fg( if line_specifier.is_some() - && i == line_specifier.unwrap() - 1 + && i == line_specifier.unwrap().saturating_sub(1) { DEFAULT_PREVIEW_GUTTER_SELECTED_FG } else { @@ -334,7 +328,9 @@ fn compute_paragraph_from_highlighted_lines( convert_syn_region_to_span( &(sr.0, sr.1), if line_specifier.is_some() - && i == line_specifier.unwrap() - 1 + && i == line_specifier + .unwrap() + .saturating_sub(1) { Some(SyntectColor { r: 50, diff --git a/crates/television/ui/results.rs b/crates/television/ui/results.rs index 8071e52..1643c60 100644 --- a/crates/television/ui/results.rs +++ b/crates/television/ui/results.rs @@ -12,7 +12,8 @@ use std::str::FromStr; use television_channels::channels::OnAir; use television_channels::entry::Entry; use television_utils::strings::{ - next_char_boundary, slice_at_char_boundaries, + make_matched_string_printable, next_char_boundary, + slice_at_char_boundaries, }; // Styles @@ -76,45 +77,41 @@ where List::new(entries.iter().map(|entry| { let mut spans = Vec::new(); // optional icon - if entry.icon.is_some() && use_icons { - let icon = entry.icon.as_ref().unwrap(); - spans.push(Span::styled( - icon.to_string(), - Style::default().fg(Color::from_str(icon.color).unwrap()), - )); - spans.push(Span::raw(" ")); - } - // entry name - if let Some(name_match_ranges) = &entry.name_match_ranges { - let mut last_match_end = 0; - for (start, end) in name_match_ranges - .iter() - .map(|(s, e)| (*s as usize, *e as usize)) - { - spans.push(Span::styled( - slice_at_char_boundaries( - &entry.name, - last_match_end, - start, - ), - Style::default().fg(results_list_colors.result_name_fg), - )); + if let Some(icon) = entry.icon.as_ref() { + if use_icons { spans.push(Span::styled( - slice_at_char_boundaries(&entry.name, start, end), - Style::default().fg(Color::Red), + icon.to_string(), + Style::default().fg(Color::from_str(icon.color).unwrap()), )); - last_match_end = end; + spans.push(Span::raw(" ")); } + } + // entry name + let (entry_name, name_match_ranges) = make_matched_string_printable( + &entry.name, + entry.name_match_ranges.as_deref(), + ); + let mut last_match_end = 0; + for (start, end) in name_match_ranges + .iter() + .map(|(s, e)| (*s as usize, *e as usize)) + { spans.push(Span::styled( - &entry.name[next_char_boundary(&entry.name, last_match_end)..], + slice_at_char_boundaries(&entry_name, last_match_end, start) + .to_string(), Style::default().fg(results_list_colors.result_name_fg), )); - } else { spans.push(Span::styled( - entry.display_name(), - Style::default().fg(results_list_colors.result_name_fg), + slice_at_char_boundaries(&entry_name, start, end).to_string(), + Style::default().fg(Color::Red), )); + last_match_end = end; } + spans.push(Span::styled( + entry_name[next_char_boundary(&entry_name, last_match_end)..] + .to_string(), + Style::default().fg(results_list_colors.result_name_fg), + )); // optional line number if let Some(line_number) = entry.line_number { spans.push(Span::styled( @@ -126,43 +123,32 @@ where if let Some(preview) = &entry.value { spans.push(Span::raw(": ")); - if let Some(preview_match_ranges) = &entry.value_match_ranges { - if !preview_match_ranges.is_empty() { - let mut last_match_end = 0; - for (start, end) in preview_match_ranges - .iter() - .map(|(s, e)| (*s as usize, *e as usize)) - { - spans.push(Span::styled( - slice_at_char_boundaries( - preview, - last_match_end, - start, - ), - Style::default() - .fg(results_list_colors.result_preview_fg), - )); - spans.push(Span::styled( - slice_at_char_boundaries(preview, start, end), - Style::default().fg(Color::Red), - )); - last_match_end = end; - } - spans.push(Span::styled( - &preview[next_char_boundary( - preview, - preview_match_ranges.last().unwrap().1 as usize, - )..], - Style::default() - .fg(results_list_colors.result_preview_fg), - )); - } - } else { - spans.push(Span::styled( + let (preview, preview_match_ranges) = + make_matched_string_printable( preview, + entry.value_match_ranges.as_deref(), + ); + let mut last_match_end = 0; + for (start, end) in preview_match_ranges + .iter() + .map(|(s, e)| (*s as usize, *e as usize)) + { + spans.push(Span::styled( + slice_at_char_boundaries(&preview, last_match_end, start) + .to_string(), Style::default().fg(results_list_colors.result_preview_fg), )); + spans.push(Span::styled( + slice_at_char_boundaries(&preview, start, end).to_string(), + Style::default().fg(Color::Red), + )); + last_match_end = end; } + spans.push(Span::styled( + preview[next_char_boundary(&preview, last_match_end)..] + .to_string(), + Style::default().fg(results_list_colors.result_preview_fg), + )); } Line::from(spans) }))