diff --git a/Cargo.lock b/Cargo.lock index 1c4ba77..4ee15f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1070,6 +1070,7 @@ dependencies = [ "laurier", "once_cell", "ratatui", + "rstest", "rust_decimal", "serde", "serde_json", @@ -1324,6 +1325,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -1336,6 +1348,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -1343,9 +1361,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -2768,6 +2788,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "rend" version = "0.4.2" @@ -2821,6 +2847,35 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.96", + "unicode-ident", +] + [[package]] name = "rust_decimal" version = "1.39.0" diff --git a/Cargo.toml b/Cargo.toml index f1bbe44..b23cdd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,9 @@ toml = "0.9.8" tui-input = "0.15.0" umbra = "0.4.0" +[dev-dependencies] +rstest = "0.26.1" + [profile.release] codegen-units = 1 lto = true diff --git a/src/view/table.rs b/src/view/table.rs index f9cbcf3..45a236c 100644 --- a/src/view/table.rs +++ b/src/view/table.rs @@ -587,17 +587,20 @@ impl TableView { fn filter_view_indices(&mut self) { let query = self.filter_input.value(); - self.view_indices = self - .row_cell_items - .iter() - .enumerate() - .filter(|(_, cell_items)| { - cell_items - .iter() - .any(|cell_item| cell_item.matched_index(query).is_some()) - }) - .map(|(i, _)| i) - .collect(); + self.view_indices = if query.is_empty() { + (0..self.items.len()).collect() + } else { + self.row_cell_items + .iter() + .enumerate() + .filter(|(_, cell_items)| { + cell_items + .iter() + .any(|cell_item| !cell_item.matched_indices(query).is_empty()) + }) + .map(|(i, _)| i) + .collect() + }; self.table_state = self .table_state .with_new_total_rows(self.view_indices.len()); diff --git a/src/widget/table.rs b/src/widget/table.rs index ec60a95..1a1e158 100644 --- a/src/widget/table.rs +++ b/src/widget/table.rs @@ -422,26 +422,63 @@ impl<'a> CellItem<'a> { if query.is_empty() { return Cell::from(Line::from(self.content.clone())); } - match self.matched_index(query) { - Some(i) => { - let mut hm = highlight_matched_text(self.content.clone()); - if self.plain_width > col_width { - hm = hm.ellipsis(ELLIPSIS); - } - let spans = hm - .matched_range(i, i + query.len()) - .matched_fg(matched_fg) - .matched_bg(matched_bg) - .into_spans(); - Cell::from(Line::from(spans)) + let indices = self.matched_indices(query); + if indices.is_empty() { + Cell::from(Line::from(self.content.clone())) + } else { + let mut hm = highlight_matched_text(self.content.clone()); + if self.plain_width > col_width { + hm = hm.ellipsis(ELLIPSIS); } - None => Cell::from(Line::from(self.content.clone())), + let spans = hm + .matched_indices(indices) + .matched_fg(matched_fg) + .matched_bg(matched_bg) + .into_spans(); + Cell::from(Line::from(spans)) } } - pub fn matched_index(&self, query: &str) -> Option { - let lower_query = query.to_lowercase(); - let lower_plain = self.plain.to_lowercase(); - lower_plain.find(&lower_query) + pub fn matched_indices(&self, query: &str) -> Vec { + matched_indices(query, &self.plain) + } +} + +fn matched_indices(query: &str, plain: &str) -> Vec { + let mut indices = Vec::new(); + let lower_plain = plain.to_lowercase(); + for q in query.split("|") { + if q.is_empty() { + continue; + } + let lower_query = q.to_lowercase(); + let l = lower_query.len(); + if let Some(i) = lower_plain.find(&lower_query) { + for idx in i..i + l { + indices.push(idx); + } + } + } + indices +} + +#[cfg(test)] +mod tests { + use rstest::*; + + use super::*; + + #[rstest] + #[case("", vec![])] + #[case("o", vec![4])] + #[case("lo", vec![3, 4])] + #[case("WOR", vec![6, 7, 8])] + #[case("el|or", vec![1, 2, 7, 8])] + #[case("ll|", vec![2, 3])] + #[case("|rl", vec![8, 9])] + fn test_matched_indices(#[case] query: &str, #[case] expected: Vec) { + let plain = "Hello World"; + let actual = matched_indices(query, plain); + assert_eq!(actual, expected); } }