From 9abf17cd78bba39da5385e58ee55612508e0a728 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 12 Oct 2023 12:14:40 -0700 Subject: [PATCH 01/11] Add option for whole-word search --- Cargo.lock | 25 +++++++++++++++++++------ Cargo.toml | 1 + src/app.rs | 24 ++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3d08f5..dbadf75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1806,6 +1806,7 @@ dependencies = [ "percentage", "rand", "rayon", + "regex", "reqwest", "serde", "url", @@ -1892,9 +1893,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" @@ -2539,9 +2540,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" dependencies = [ "aho-corasick", "memchr", @@ -2550,9 +2563,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33" [[package]] name = "reqwest" diff --git a/Cargo.toml b/Cargo.toml index ca10ba1..9a866a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ client = ["dep:reqwest", "dep:url"] server = ["dep:actix-cors", "dep:actix-web"] [dependencies] +regex = "1.10.0" percentage = "0.1.0" egui = "0.22.0" egui_extras = "0.22.0" diff --git a/src/app.rs b/src/app.rs index ff30ba7..c8a507f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -9,6 +9,7 @@ use egui::{ }; use egui_extras::{Column, TableBuilder}; use percentage::{Percentage, PercentageInteger}; +use regex::Regex; use serde::{Deserialize, Serialize}; use crate::data::{ @@ -111,6 +112,8 @@ struct SearchState { query: String, last_query: String, include_collapsed_entries: bool, + whole_word: bool, + last_whole_word: bool, last_include_collapsed_entries: bool, search_field: FieldID, last_search_field: FieldID, @@ -1112,6 +1115,8 @@ impl SearchState { query: "".to_owned(), last_query: "".to_owned(), include_collapsed_entries: false, + whole_word: false, + last_whole_word: false, last_include_collapsed_entries: false, search_field: title_id, last_search_field: title_id, @@ -1144,6 +1149,12 @@ impl SearchState { self.last_search_field = self.search_field; } + // Invalidate when the whole word setting changes. + if self.whole_word != self.last_whole_word { + invalidate = true; + self.last_whole_word = self.whole_word; + } + // Invalidate when EXCLUDING collapsed entries. (I.e., because the // searched set shrinks. Growing is ok because search is monotonic.) if self.include_collapsed_entries != self.last_include_collapsed_entries @@ -1165,7 +1176,12 @@ impl SearchState { } fn is_string_match(&self, s: &str) -> bool { - s.contains(&self.query) + match self.whole_word { + true => Regex::new(format!("\\b{}\\b", self.query).as_str()) + .unwrap() + .is_match(s), + false => s.contains(&self.query), + } } fn is_field_match(&self, field: &Field) -> bool { @@ -1180,7 +1196,7 @@ impl SearchState { fn is_match(&self, item: &ItemMeta) -> bool { let field = self.search_field; if field == self.title_field { - item.title.contains(&self.query) + self.is_string_match(&item.title) } else if let Some((_, value)) = item.fields.iter().find(|(x, _)| *x == field) { self.is_field_match(value) } else { @@ -1583,6 +1599,10 @@ impl Window { } }); }); + ui.checkbox( + &mut self.config.search_state.whole_word, + "whole-word matches only", + ); ui.checkbox( &mut self.config.search_state.include_collapsed_entries, "Include collapsed processors", From 230f9ba520f29253eb7437fee464ca969b74337a Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 13 Oct 2023 11:38:29 -0700 Subject: [PATCH 02/11] depedency order --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9a866a3..120cada 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ client = ["dep:reqwest", "dep:url"] server = ["dep:actix-cors", "dep:actix-web"] [dependencies] -regex = "1.10.0" percentage = "0.1.0" egui = "0.22.0" egui_extras = "0.22.0" @@ -33,6 +32,8 @@ rand = { version = "0.8" } # transitive depedency, required for rand to support wasm getrandom = { version = "0.2", features = ["js"] } +regex = "1.10.0" + # client url = { version = "2", optional = true } From 8db6655b410876567ad23fc5ab9c172f1ced41f5 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 13 Oct 2023 11:42:19 -0700 Subject: [PATCH 03/11] match -> if --- src/app.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index c8a507f..4e30118 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1176,11 +1176,12 @@ impl SearchState { } fn is_string_match(&self, s: &str) -> bool { - match self.whole_word { - true => Regex::new(format!("\\b{}\\b", self.query).as_str()) + if self.whole_word { + Regex::new(format!("\\b{}\\b", self.query).as_str()) .unwrap() - .is_match(s), - false => s.contains(&self.query), + .is_match(s) + } else { + s.contains(&self.query) } } From 276f1c360062c239571d0ae5c1c0c32257e84b9f Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 13 Oct 2023 11:43:08 -0700 Subject: [PATCH 04/11] option description --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 4e30118..c90bc2e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1602,7 +1602,7 @@ impl Window { }); ui.checkbox( &mut self.config.search_state.whole_word, - "whole-word matches only", + "Match whole words only", ); ui.checkbox( &mut self.config.search_state.include_collapsed_entries, From 2ae67336669857b6630952061bbdd5d680e62cf7 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 13 Oct 2023 11:46:25 -0700 Subject: [PATCH 05/11] escape regex --- src/app.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index c90bc2e..18f66c4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -9,7 +9,7 @@ use egui::{ }; use egui_extras::{Column, TableBuilder}; use percentage::{Percentage, PercentageInteger}; -use regex::Regex; +use regex::{Regex, escape}; use serde::{Deserialize, Serialize}; use crate::data::{ @@ -1177,7 +1177,7 @@ impl SearchState { fn is_string_match(&self, s: &str) -> bool { if self.whole_word { - Regex::new(format!("\\b{}\\b", self.query).as_str()) + Regex::new(format!("\\b{}\\b", escape(&self.query)).as_str()) .unwrap() .is_match(s) } else { From 847f315fdc7b42d40abcd0e51bf9b45a872bce78 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 13 Oct 2023 12:10:56 -0700 Subject: [PATCH 06/11] cache word regexes --- src/app.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index 18f66c4..566d1fc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -111,6 +111,7 @@ struct SearchState { // Search parameters query: String, last_query: String, + last_word_regex: Option, include_collapsed_entries: bool, whole_word: bool, last_whole_word: bool, @@ -1114,6 +1115,7 @@ impl SearchState { query: "".to_owned(), last_query: "".to_owned(), + last_word_regex: None, include_collapsed_entries: false, whole_word: false, last_whole_word: false, @@ -1141,6 +1143,10 @@ impl SearchState { if self.query != self.last_query { invalidate = true; self.last_query = self.query.clone(); + if self.whole_word { + let regex_string = format!("\\b{}\\b", escape(&self.query)); + self.last_word_regex = Some(Regex::new(regex_string.as_str()).unwrap()); + } } // Invalidate when the search field changes. @@ -1153,6 +1159,10 @@ impl SearchState { if self.whole_word != self.last_whole_word { invalidate = true; self.last_whole_word = self.whole_word; + if self.whole_word { + let regex_string = format!("\\b{}\\b", escape(&self.query)); + self.last_word_regex = Some(Regex::new(regex_string.as_str()).unwrap()); + } } // Invalidate when EXCLUDING collapsed entries. (I.e., because the @@ -1177,9 +1187,10 @@ impl SearchState { fn is_string_match(&self, s: &str) -> bool { if self.whole_word { - Regex::new(format!("\\b{}\\b", escape(&self.query)).as_str()) - .unwrap() - .is_match(s) + match &self.last_word_regex { + Some(regex) => regex.is_match(s), + _ => false, + } } else { s.contains(&self.query) } From 03fd91bbe3709fa8967b5104881c88687b749885 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 13 Oct 2023 12:14:56 -0700 Subject: [PATCH 07/11] fmt --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 566d1fc..fdabba8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -9,7 +9,7 @@ use egui::{ }; use egui_extras::{Column, TableBuilder}; use percentage::{Percentage, PercentageInteger}; -use regex::{Regex, escape}; +use regex::{escape, Regex}; use serde::{Deserialize, Serialize}; use crate::data::{ From ffda863c1685f9b6af8fc2b3d88f2b092bcce10c Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 19 Oct 2023 14:26:52 -0700 Subject: [PATCH 08/11] review comments --- src/app.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index fdabba8..435d7b8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1187,10 +1187,10 @@ impl SearchState { fn is_string_match(&self, s: &str) -> bool { if self.whole_word { - match &self.last_word_regex { - Some(regex) => regex.is_match(s), - _ => false, - } + let Some(regex) = &self.last_word_regex else { + unreachable!(); + }; + regex.is_match(s) } else { s.contains(&self.query) } From 1ad7275b173dc9b1f0051bde57ed55f0f48d1bb2 Mon Sep 17 00:00:00 2001 From: Elliott Slaughter Date: Fri, 20 Oct 2023 09:29:48 -0700 Subject: [PATCH 09/11] Build regex on invalidate to unify code paths. --- src/app.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index 435d7b8..02674ab 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1143,10 +1143,6 @@ impl SearchState { if self.query != self.last_query { invalidate = true; self.last_query = self.query.clone(); - if self.whole_word { - let regex_string = format!("\\b{}\\b", escape(&self.query)); - self.last_word_regex = Some(Regex::new(regex_string.as_str()).unwrap()); - } } // Invalidate when the search field changes. @@ -1159,10 +1155,6 @@ impl SearchState { if self.whole_word != self.last_whole_word { invalidate = true; self.last_whole_word = self.whole_word; - if self.whole_word { - let regex_string = format!("\\b{}\\b", escape(&self.query)); - self.last_word_regex = Some(Regex::new(regex_string.as_str()).unwrap()); - } } // Invalidate when EXCLUDING collapsed entries. (I.e., because the @@ -1181,6 +1173,11 @@ impl SearchState { } if invalidate { + if self.whole_word { + let regex_string = format!("\\b{}\\b", escape(&self.query)); + self.last_word_regex = Some(Regex::new(®ex_string).unwrap()); + } + self.clear(); } } From b3803581709f3b1edfd5f7a810b078d92b52f769 Mon Sep 17 00:00:00 2001 From: Elliott Slaughter Date: Fri, 20 Oct 2023 09:34:20 -0700 Subject: [PATCH 10/11] Change fields to deal with things in a uniform order. --- src/app.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index 02674ab..e69d433 100644 --- a/src/app.rs +++ b/src/app.rs @@ -111,13 +111,13 @@ struct SearchState { // Search parameters query: String, last_query: String, - last_word_regex: Option, - include_collapsed_entries: bool, whole_word: bool, - last_whole_word: bool, - last_include_collapsed_entries: bool, search_field: FieldID, last_search_field: FieldID, + last_whole_word: bool, + last_word_regex: Option, + include_collapsed_entries: bool, + last_include_collapsed_entries: bool, last_view_interval: Option, // Cache of matching items @@ -1115,13 +1115,13 @@ impl SearchState { query: "".to_owned(), last_query: "".to_owned(), - last_word_regex: None, - include_collapsed_entries: false, + search_field: title_id, + last_search_field: title_id, whole_word: false, last_whole_word: false, + last_word_regex: None, + include_collapsed_entries: false, last_include_collapsed_entries: false, - search_field: title_id, - last_search_field: title_id, last_view_interval: None, result_set: BTreeSet::new(), From 715a61977d3a27ee51bba38e9e45697c6e8556d4 Mon Sep 17 00:00:00 2001 From: Elliott Slaughter Date: Fri, 20 Oct 2023 09:35:06 -0700 Subject: [PATCH 11/11] Another fix for field order. --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index e69d433..8895805 100644 --- a/src/app.rs +++ b/src/app.rs @@ -111,9 +111,9 @@ struct SearchState { // Search parameters query: String, last_query: String, - whole_word: bool, search_field: FieldID, last_search_field: FieldID, + whole_word: bool, last_whole_word: bool, last_word_regex: Option, include_collapsed_entries: bool,