From 421b868e6973b634434e07484008fdf59e5b087e Mon Sep 17 00:00:00 2001 From: Arkadiusz Piekarz Date: Thu, 16 Jun 2022 20:55:03 +0200 Subject: [PATCH] Speed up regex filtering by compiling it only once Also send a new value of a property in Toggled event. --- Cargo.lock | 12 ++-- src/commit_amend_checkbox.rs | 20 ++---- src/commit_log_author_filter_entry.rs | 4 +- src/commit_log_filters_view.rs | 6 +- src/commit_log_model_filter.rs | 61 ++++++------------ src/event.rs | 6 +- src/gui.rs | 6 +- src/lib.rs | 1 + src/show_commit_log_filters_button.rs | 3 +- src/text_filter.rs | 93 +++++++++++++++++++++++++++ 10 files changed, 142 insertions(+), 70 deletions(-) create mode 100644 src/text_filter.rs diff --git a/Cargo.lock b/Cargo.lock index 8a15712..3cb055d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", @@ -1208,9 +1208,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" @@ -1259,9 +1259,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" diff --git a/src/commit_amend_checkbox.rs b/src/commit_amend_checkbox.rs index 62ff863..71d2d70 100644 --- a/src/commit_amend_checkbox.rs +++ b/src/commit_amend_checkbox.rs @@ -17,9 +17,9 @@ impl IEventHandler for CommitAmendCheckbox fn handle(&mut self, source: Source, event: &Event) { match event { - Event::AmendedCommit => self.onAmendedCommit(), - Event::Committed => self.onCommitted(), - Event::Toggled => self.onToggled(), + Event::AmendedCommit => self.onAmendedCommit(), + Event::Committed => self.onCommitted(), + Event::Toggled(isSelected) => self.onToggled(*isSelected), _ => handleUnknown(source, event) } } @@ -45,12 +45,6 @@ impl CommitAmendCheckbox // private - #[must_use] - pub fn isSelected(&self) -> bool - { - self.widget.is_active() - } - pub fn unselect(&self) { self.widget.set_active(false); @@ -77,8 +71,8 @@ impl CommitAmendCheckbox fn connectWidget(&self) { let eventSender = self.sender.clone(); - self.widget.connect_toggled(move |_checkbox| - eventSender.send((Source::CommitAmendCheckbox, Event::Toggled)).unwrap()); + self.widget.connect_toggled(move |checkbox| + eventSender.send((Source::CommitAmendCheckbox, Event::Toggled(checkbox.is_active()))).unwrap()); } fn onAmendedCommit(&self) @@ -93,9 +87,9 @@ impl CommitAmendCheckbox } } - fn onToggled(&self) + fn onToggled(&self, isSelected: bool) { - if self.isSelected() { + if isSelected { self.notifyOnSelected(); } else { self.notifyOnUnselected(); diff --git a/src/commit_log_author_filter_entry.rs b/src/commit_log_author_filter_entry.rs index 077e41f..7e6160c 100644 --- a/src/commit_log_author_filter_entry.rs +++ b/src/commit_log_author_filter_entry.rs @@ -17,6 +17,6 @@ pub fn setupCommitLogAuthorFilterEntry(guiElementProvider: &GuiElementProvider, pub(crate) fn setupCommitLogAuthorFilterRegexButton(guiElementProvider: &GuiElementProvider, sender: Sender) { let button = guiElementProvider.get::("Commit log author filter regex button"); - button.connect_toggled( - move |_button| sender.send((Source::CommitLogAuthorFilterRegexButton, Event::Toggled)).unwrap()); + button.connect_toggled(move |button| + sender.send((Source::CommitLogAuthorFilterRegexButton, Event::Toggled(button.is_active()))).unwrap()); } diff --git a/src/commit_log_filters_view.rs b/src/commit_log_filters_view.rs index 8ce4110..d34ef71 100644 --- a/src/commit_log_filters_view.rs +++ b/src/commit_log_filters_view.rs @@ -14,7 +14,7 @@ impl IEventHandler for CommitLogFiltersView fn handle(&mut self, source: Source, event: &Event) { match event { - Event::Toggled => self.onToggled(), + Event::Toggled(isEnabled) => self.onToggled(*isEnabled), _ => handleUnknown(source, event) } } @@ -28,8 +28,8 @@ impl CommitLogFiltersView Self{widget} } - fn onToggled(&self) + fn onToggled(&self, isEnabled: bool) { - self.widget.set_visible(!self.widget.is_visible()); + self.widget.set_visible(isEnabled); } } diff --git a/src/commit_log_model_filter.rs b/src/commit_log_model_filter.rs index 921ca38..c5aaa73 100644 --- a/src/commit_log_model_filter.rs +++ b/src/commit_log_model_filter.rs @@ -1,19 +1,18 @@ use crate::commit_log_column::{CommitLogColumn}; use crate::event::{Event, handleUnknown, IEventHandler, Sender, Source}; use crate::gui_element_provider::GuiElementProvider; +use crate::text_filter::TextFilter; use gtk::traits::TreeModelExt; use gtk::traits::TreeModelFilterExt; -use regex::RegexBuilder; use std::cell::RefCell; -use std::ops::Not; use std::rc::Rc; pub struct CommitLogModelFilter { modelFilter: gtk::TreeModelFilter, - authorFilter: Rc>, + authorFilter: Rc>, sender: Sender } @@ -24,7 +23,7 @@ impl IEventHandler for CommitLogModelFilter match (source, event) { (_, Event::RefilterRequested) => self.onRefilterRequested(), (_, Event::TextEntered(filter)) => self.onCommitAuthorFilterChanged(filter), - (Source::CommitLogAuthorFilterRegexButton, Event::Toggled) => self.onRegexToggled(), + (Source::CommitLogAuthorFilterRegexButton, Event::Toggled(isEnabled)) => self.onRegexToggled(*isEnabled), _ => handleUnknown(source, event) } } @@ -36,7 +35,7 @@ impl CommitLogModelFilter -> Self { let modelFilter = guiElementProvider.get::("Commit log store filter"); - let authorFilter = Rc::new(RefCell::new(AuthorFilter::new())); + let authorFilter = Rc::new(RefCell::new(TextFilter::new())); setupFilterFunction(&modelFilter, Rc::clone(&authorFilter)); Self{modelFilter, authorFilter, sender} } @@ -44,23 +43,27 @@ impl CommitLogModelFilter // private - fn onCommitAuthorFilterChanged(&self, filter: &str) + fn onCommitAuthorFilterChanged(&self, text: &str) { - self.authorFilter.borrow_mut().text = filter.to_lowercase(); - self.requestRefilter(); + match self.authorFilter.borrow_mut().setText(text) { + Ok(()) => self.requestRefilter(), + Err(e) => eprintln!("Invalid author filter regex: {}", e) + } + } - fn onRefilterRequested(&self) + fn onRegexToggled(&self, isEnabled: bool) { - self.modelFilter.refilter(); - self.sender.send((Source::CommitLogModelFilter, Event::RefilterEnded)).unwrap(); + match self.authorFilter.borrow_mut().enableRegex(isEnabled) { + Ok(()) => self.requestRefilter(), + Err(e) => eprintln!("Invalid author filter regex: {}", e) + } } - fn onRegexToggled(&self) + fn onRefilterRequested(&self) { - let mut authorFilter = self.authorFilter.borrow_mut(); - authorFilter.useRegex = authorFilter.useRegex.not(); - self.requestRefilter(); + self.modelFilter.refilter(); + self.sender.send((Source::CommitLogModelFilter, Event::RefilterEnded)).unwrap(); } fn requestRefilter(&self) @@ -69,38 +72,16 @@ impl CommitLogModelFilter } } -fn setupFilterFunction(modelFilter: >k::TreeModelFilter, authorFilter: Rc>) +fn setupFilterFunction(modelFilter: >k::TreeModelFilter, authorFilter: Rc>) { modelFilter.set_visible_func(move |model, iter| { let authorFilter = &*authorFilter.borrow(); - if authorFilter.text.is_empty() { + if authorFilter.isEmpty() { return true; } let author = model.value(iter, CommitLogColumn::Author.into()); let author = author.get::<&str>().unwrap(); - if authorFilter.useRegex { - match RegexBuilder::new(&authorFilter.text).case_insensitive(true).build() { - Ok(regex) => regex.is_match(author), - Err(_) => false - } - - } else { - author.to_lowercase().contains(&authorFilter.text) - } + authorFilter.isMatch(author) }); } - -struct AuthorFilter -{ - text: String, - useRegex: bool -} - -impl AuthorFilter -{ - fn new() -> Self - { - Self{text: String::new(), useRegex: false} - } -} diff --git a/src/event.rs b/src/event.rs index 570a87c..847a012 100644 --- a/src/event.rs +++ b/src/event.rs @@ -28,8 +28,8 @@ pub enum Event // button Clicked, - // checkbox - Toggled, + // checkbox, tool button + Toggled(IsEnabled), // commit amend mode CommitAmendEnabled, @@ -68,6 +68,8 @@ pub enum Event TextEntered(String) } +type IsEnabled = bool; + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Source { diff --git a/src/gui.rs b/src/gui.rs index f01fb73..b1dc65f 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -156,13 +156,13 @@ fn setupDispatching(gui: GuiObjects, mut repository: Rc>, re attach(receiver, move |(source, event)| { match (source, &event) { (S::CommitAmendCheckbox, E::CommitAmendDisabled) => (&repository, &mut commitMessageView, &mut commitButton, &mut diffView).handle(source, &event), (S::CommitAmendCheckbox, E::CommitAmendEnabled) => (&repository, &mut commitMessageView, &mut commitButton, &mut diffView).handle(source, &event), - (S::CommitAmendCheckbox, E::Toggled) => commitAmendCheckbox.handle(source, &event), + (S::CommitAmendCheckbox, E::Toggled(_)) => commitAmendCheckbox.handle(source, &event), (S::CommitButton, E::AmendCommitRequested(_)) => repository.handle(source, &event), (S::CommitButton, E::Clicked) => commitButton.handle(source, &event), (S::CommitButton, E::CommitRequested(_)) => repository.handle(source, &event), (S::CommitDiffViewWidget, E::ZoomRequested(_)) => commitDiffView.handle(source, &event), (S::CommitLogAuthorFilterEntry, E::TextEntered(_)) => commitLogModelFilter.handle(source, &event), - (S::CommitLogAuthorFilterRegexButton, E::Toggled) => commitLogModelFilter.handle(source, &event), + (S::CommitLogAuthorFilterRegexButton, E::Toggled(_)) => commitLogModelFilter.handle(source, &event), (S::CommitLogModelFilter, E::RefilterRequested) => (&mut commitLogView, &mut commitLogModelFilter).handle(source, &event), (S::CommitLogModelFilter, E::RefilterEnded) => commitLogView.handle(source, &event), (S::CommitLogView, E::CommitSelected(_)) => commitDiffView.handle(source, &event), @@ -187,7 +187,7 @@ fn setupDispatching(gui: GuiObjects, mut repository: Rc>, re (S::Repository, E::Refreshed) => (&unstagedChangesStore, &stagedChangesStore).handle(source, &event), (S::Repository, E::UpdatedInStaged(_)) => stagedChangesStore.handle(source, &event), (S::Repository, E::UpdatedInUnstaged(_)) => unstagedChangesStore.handle(source, &event), - (S::ShowCommitLogFiltersButton, E::Toggled) => commitLogFiltersView.handle(source, &event), + (S::ShowCommitLogFiltersButton, E::Toggled(_)) => commitLogFiltersView.handle(source, &event), (S::StagedChangesStore, E::Refreshed) => stagedChangesView.handle(source, &event), (S::StagedChangesView, E::FileChangeRefreshed(_)) => diffView.handle(source, &event), (S::StagedChangesView, E::FileChangeSelected(_)) => (&mut diffView, &mut unstagedChangesView).handle(source, &event), diff --git a/src/lib.rs b/src/lib.rs index 9e0a2d9..fa5cdef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,7 @@ mod staged_changes; mod staged_changes_store; mod staged_changes_view; mod line_diff; +mod text_filter; mod text_view; mod tool_bar_stack; mod tree_selection; diff --git a/src/show_commit_log_filters_button.rs b/src/show_commit_log_filters_button.rs index 89479c9..c480c19 100644 --- a/src/show_commit_log_filters_button.rs +++ b/src/show_commit_log_filters_button.rs @@ -7,5 +7,6 @@ use gtk::traits::ToggleToolButtonExt; pub(crate) fn setupShowCommitLogFiltersButton(guiElementProvider: &GuiElementProvider, sender: Sender) { let button = guiElementProvider.get::("Show commit log filters button"); - button.connect_toggled(move |_button| sender.send((Source::ShowCommitLogFiltersButton, Event::Toggled)).unwrap()); + button.connect_toggled( + move |button| sender.send((Source::ShowCommitLogFiltersButton, Event::Toggled(button.is_active()))).unwrap()); } diff --git a/src/text_filter.rs b/src/text_filter.rs new file mode 100644 index 0000000..2f5ef7f --- /dev/null +++ b/src/text_filter.rs @@ -0,0 +1,93 @@ +use anyhow::{bail, Result}; +use regex::{Regex, RegexBuilder}; + + +pub(crate) struct TextFilter +{ + regexState: RegexState +} + +enum RegexState +{ + Disabled{text: String}, + Invalid{text: String}, + Valid{regex: Regex} +} + +impl TextFilter +{ + pub(crate) fn new() -> Self + { + Self{regexState: RegexState::Disabled{text: String::new()}} + } + + pub(crate) fn setText(&mut self, newText: &str) -> Result<()> + { + match &mut self.regexState { + RegexState::Disabled{text} => *text = newText.into(), + RegexState::Invalid{text} => { + match RegexBuilder::new(newText).case_insensitive(true).build() { + Ok(newRegex) => self.regexState = RegexState::Valid{regex: newRegex}, + Err(e) => { + *text = newText.into(); + bail!(e) + } + } + }, + RegexState::Valid{regex} => { + match RegexBuilder::new(newText).case_insensitive(true).build() { + Ok(newRegex) => *regex = newRegex, + Err(e) => { + self.regexState = RegexState::Invalid{text: newText.into()}; + bail!(e) + } + } + } + } + Ok(()) + } + + pub(crate) fn enableRegex(&mut self, shouldEnable: bool) -> Result<()> + { + if shouldEnable { + match &mut self.regexState { + RegexState::Disabled{text} => { + match RegexBuilder::new(&text).case_insensitive(true).build() { + Ok(regex) => self.regexState = RegexState::Valid{regex}, + Err(e) => { + self.regexState = RegexState::Invalid{text: text.clone()}; + bail!(e) + } + } + }, + RegexState::Invalid{..} => (), + RegexState::Valid{..} => () + } + } else { + match &mut self.regexState { + RegexState::Disabled{..} => (), + RegexState::Invalid{text} => self.regexState = RegexState::Disabled{text: text.clone()}, + RegexState::Valid{regex} => self.regexState = RegexState::Disabled{text: regex.as_str().into()} + } + } + Ok(()) + } + + pub(crate) fn isEmpty(&self) -> bool + { + match &self.regexState { + RegexState::Disabled{text} => text.is_empty(), + RegexState::Invalid{..} => true, + RegexState::Valid{regex} => regex.as_str().is_empty() + } + } + + pub(crate) fn isMatch(&self, input: &str) -> bool + { + match &self.regexState { + RegexState::Disabled{text} => input.to_lowercase().contains(text), + RegexState::Invalid{..} => false, + RegexState::Valid{regex} => regex.is_match(input) + } + } +}