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-actions[bot]@users.noreply.github.com>
This commit is contained in:
Alexandre Pasmantier 2024-11-24 00:20:04 +01:00 committed by GitHub
parent b703e1b26c
commit edd9df4e29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 419 additions and 274 deletions

View File

@ -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 }}

View File

@ -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

150
Cargo.lock generated
View File

@ -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"
@ -2275,10 +2285,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.89"
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",
@ -3670,10 +3690,16 @@ dependencies = [
]
[[package]]
name = "yoke"
version = "0.7.4"
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yoke"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",

View File

@ -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"

View File

@ -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"

View File

@ -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<Alias>) {
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(),
));
}
}

View File

@ -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| {
let () = injector.push(EnvVar { name, value }, |e, cols| {
cols[0] = (e.name.clone() + &e.value).into();
},
);
});
}
Channel {
matcher,

View File

@ -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<String>,
@ -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::<HashSet<_>>()
.into_iter()
.collect(),
@ -132,14 +131,13 @@ async fn load_files(paths: Vec<PathBuf>, injector: Injector<String>) {
Box::new(move |result| {
if let Ok(entry) = result {
if entry.file_type().unwrap().is_file() {
let file_path = preprocess_line(
&entry
let file_path = &entry
.path()
.strip_prefix(&current_dir)
.unwrap_or(entry.path())
.to_string_lossy(),
);
let () = injector.push(file_path, |e, cols| {
.to_string_lossy();
let () =
injector.push(file_path.to_string(), |e, cols| {
cols[0] = e.clone().into();
});
}

View File

@ -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<String>,
@ -150,13 +149,15 @@ async fn crawl_for_repos(starting_point: PathBuf, injector: Injector<String>) {
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| {
let () = injector.push(
parent_path.to_string(),
|e, cols| {
cols[0] = e.clone().into();
});
},
);
return ignore::WalkState::Skip;
}
}

View File

@ -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<String>,
@ -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));

View File

@ -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,11 +168,7 @@ 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)
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()))
@ -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| {

View File

@ -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<String>,
/// An optional value associated with the entry.
pub value: Option<String>,
/// 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,

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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<FileIcon>,
}
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<FileIcon>,
) -> Self {
Preview {
title,
content,
icon,
}
}
pub fn total_lines(&self) -> u16 {

View File

@ -22,6 +22,7 @@ impl BasicPreviewer {
Arc::new(Preview {
title: entry.name.clone(),
content: PreviewContent::PlainTextWrapped(entry.name.clone()),
icon: entry.icon,
})
}
}

View File

@ -58,6 +58,7 @@ fn build_tree_preview(entry: &Entry) -> Preview {
.map(std::borrow::ToOwned::to_owned)
.collect(),
),
icon: entry.icon,
}
}

View File

@ -35,6 +35,7 @@ impl EnvVarPreviewer {
} else {
PreviewContent::Empty
},
icon: entry.icon,
});
self.cache.insert(entry.clone(), preview.clone());
preview

View File

@ -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<Preview> {
// 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<Preview> {
Arc::new(Preview::new(
title.to_string(),
PreviewContent::PlainText(lines),
None,
))
}

View File

@ -5,6 +5,7 @@ pub fn not_supported(title: &str) -> Arc<Preview> {
Arc::new(Preview::new(
title.to_string(),
PreviewContent::NotSupported,
None,
))
}
@ -12,10 +13,15 @@ pub fn file_too_large(title: &str) -> Arc<Preview> {
Arc::new(Preview::new(
title.to_string(),
PreviewContent::FileTooLarge,
None,
))
}
#[allow(dead_code)]
pub fn loading(title: &str) -> Arc<Preview> {
Arc::new(Preview::new(title.to_string(), PreviewContent::Loading))
Arc::new(Preview::new(
title.to_string(),
PreviewContent::Loading,
None,
))
}

View File

@ -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

View File

@ -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<i16>) {
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<i16>) {
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!", "HelloWorld!");
}
#[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));
}
}

View File

@ -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) {

View File

@ -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<Preview>,
) -> 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<u16>,
preview: &Arc<Preview>,
) -> 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,

View File

@ -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();
if let Some(icon) = entry.icon.as_ref() {
if use_icons {
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 (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(
slice_at_char_boundaries(
&entry.name,
last_match_end,
start,
),
slice_at_char_boundaries(&entry_name, last_match_end, start)
.to_string(),
Style::default().fg(results_list_colors.result_name_fg),
));
spans.push(Span::styled(
slice_at_char_boundaries(&entry.name, start, end),
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)..],
entry_name[next_char_boundary(&entry_name, last_match_end)..]
.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),
));
}
// optional line number
if let Some(line_number) = entry.line_number {
spans.push(Span::styled(
@ -126,44 +123,33 @@ 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 (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,
),
Style::default()
.fg(results_list_colors.result_preview_fg),
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),
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,
preview_match_ranges.last().unwrap().1 as usize,
)..],
Style::default()
.fg(results_list_colors.result_preview_fg),
));
}
} else {
spans.push(Span::styled(
preview,
preview[next_char_boundary(&preview, last_match_end)..]
.to_string(),
Style::default().fg(results_list_colors.result_preview_fg),
));
}
}
Line::from(spans)
}))
.direction(list_direction)