From f70b92aeee9c027e25b9704659d9a5984a8e49d0 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 00:11:02 +0000 Subject: [PATCH 001/118] Add search bar at bottom of revlog --- src/app.rs | 3 + src/components/commitlist.rs | 16 ++++ src/components/find_commit.rs | 144 ++++++++++++++++++++++++++++++++++ src/components/mod.rs | 2 + src/components/textinput.rs | 31 +++++--- src/keys.rs | 42 +++++----- src/queue.rs | 2 + src/tabs/revlog.rs | 90 +++++++++++++++++---- 8 files changed, 282 insertions(+), 48 deletions(-) create mode 100644 src/components/find_commit.rs diff --git a/src/app.rs b/src/app.rs index 2ce3b6be50..09cf56b4a2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -537,6 +537,9 @@ impl App { self.push_popup.push(branch)?; flags.insert(NeedsUpdate::ALL) } + InternalEvent::FilterLog(string_to_fliter_by) => { + self.revlog.filter(string_to_fliter_by) + } }; Ok(flags) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 16b071d977..5efae50e04 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -39,6 +39,7 @@ pub struct CommitList { scroll_top: Cell, theme: SharedTheme, key_config: SharedKeyConfig, + filter_string: Option, } impl CommitList { @@ -60,6 +61,7 @@ impl CommitList { theme, key_config, title: String::from(title), + filter_string: None, } } @@ -114,6 +116,11 @@ impl CommitList { self.tags = Some(tags); } + /// + pub fn set_filter(&mut self, filter_string: Option) { + self.filter_string = filter_string; + } + /// pub fn selected_entry(&self) -> Option<&LogEntry> { self.items.iter().nth( @@ -261,6 +268,15 @@ impl CommitList { for (idx, e) in self .items .iter() + .filter(|log_entry| { + if let Some(filter_string) = &self.filter_string { + return log_entry + .hash_short + .contains(filter_string) + || log_entry.msg.contains(filter_string); + } + true + }) .skip(self.scroll_top.get()) .take(height) .enumerate() diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs new file mode 100644 index 0000000000..a3bca72267 --- /dev/null +++ b/src/components/find_commit.rs @@ -0,0 +1,144 @@ +use super::{ + textinput::TextInputComponent, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, +}; +use crate::{ + keys::SharedKeyConfig, + queue::{InternalEvent, Queue}, + strings, + ui::style::SharedTheme, +}; +use anyhow::Result; +use crossterm::event::Event; +use tui::{backend::Backend, layout::Rect, Frame}; + +pub struct FindCommitComponent { + input: TextInputComponent, + branch_ref: Option, + queue: Queue, + is_focused: bool, + visible: bool, + key_config: SharedKeyConfig, +} + +impl DrawableComponent for FindCommitComponent { + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + self.input.draw(f, rect)?; + Ok(()) + } +} + +impl Component for FindCommitComponent { + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + self.input.commands(out, force_all); + + out.push(CommandInfo::new( + strings::commands::rename_branch_confirm_msg( + &self.key_config, + ), + true, + true, + )); + } + + visibility_blocking(self) + } + + fn event(&mut self, ev: Event) -> Result { + if self.is_visible() { + if let Event::Key(e) = ev { + if e == self.key_config.enter { + // Send internal event to filter revlog + self.queue.borrow_mut().push_back( + InternalEvent::FilterLog( + self.input.get_text().to_string(), + ), + ); + return Ok(true); + } else if e == self.key_config.exit_popup { + // Prevent text input closing + return Ok(true); + } + } + if self.input.event(ev)? { + return Ok(true); + } + } + Ok(false) + } + + fn is_visible(&self) -> bool { + return self.visible; + } + + fn hide(&mut self) { + self.visible = false; + } + fn show(&mut self) -> Result<()> { + self.visible = true; + Ok(()) + } + + fn focus(&mut self, focus: bool) { + self.is_focused = focus; + } + + fn focused(&self) -> bool { + return self.is_focused; + } + + fn toggle_visible(&mut self) -> Result<()> { + self.visible = !self.visible; + Ok(()) + } +} + +impl FindCommitComponent { + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + let mut input_component = TextInputComponent::new( + theme, + key_config.clone(), + &strings::rename_branch_popup_title(&key_config), + &strings::rename_branch_popup_msg(&key_config), + true, + ); + input_component.show().expect("Will not err"); + input_component.set_should_use_rect(true); + Self { + queue, + input: input_component, + branch_ref: None, + key_config, + visible: false, + is_focused: false, + } + } + + /// + pub fn open( + &mut self, + branch_ref: String, + cur_name: String, + ) -> Result<()> { + self.branch_ref = None; + self.branch_ref = Some(branch_ref); + self.input.set_text(cur_name); + self.show()?; + + Ok(()) + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 38efec75e5..21511d38f3 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -8,6 +8,7 @@ mod cred; mod diff; mod externaleditor; mod filetree; +mod find_commit; mod help; mod inspect_commit; mod msg; @@ -29,6 +30,7 @@ pub use create_branch::CreateBranchComponent; pub use diff::DiffComponent; pub use externaleditor::ExternalEditorComponent; pub use filetree::FileTreeComponent; +pub use find_commit::FindCommitComponent; pub use help::HelpComponent; pub use inspect_commit::InspectCommitComponent; pub use msg::MsgComponent; diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 58044f8b0f..b4d01a3349 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -39,6 +39,7 @@ pub struct TextInputComponent { key_config: SharedKeyConfig, cursor_position: usize, input_type: InputType, + should_use_rect: bool, } impl TextInputComponent { @@ -60,6 +61,7 @@ impl TextInputComponent { default_msg: default_msg.to_string(), cursor_position: 0, input_type: InputType::Multiline, + should_use_rect: false, } } @@ -234,6 +236,10 @@ impl TextInputComponent { f.render_widget(w, rect); } } + + pub fn set_should_use_rect(&mut self, b: bool) { + self.should_use_rect = b; + } } // merges last line of `txt` with first of `append` so we do not generate unneeded newlines @@ -260,7 +266,7 @@ impl DrawableComponent for TextInputComponent { fn draw( &self, f: &mut Frame, - _rect: Rect, + rect: Rect, ) -> Result<()> { if self.visible { let txt = if self.msg.is_empty() { @@ -272,16 +278,21 @@ impl DrawableComponent for TextInputComponent { self.get_draw_text() }; - let area = match self.input_type { - InputType::Multiline => { - let area = ui::centered_rect(60, 20, f.size()); - ui::rect_inside( - Size::new(10, 3), - f.size().into(), - area, - ) + let area = if self.should_use_rect { + rect + } else { + match self.input_type { + InputType::Multiline => { + let area = + ui::centered_rect(60, 20, f.size()); + ui::rect_inside( + Size::new(10, 3), + f.size().into(), + area, + ) + } + _ => ui::centered_rect_absolute(32, 3, f.size()), } - _ => ui::centered_rect_absolute(32, 3, f.size()), }; f.render_widget(Clear, area); diff --git a/src/keys.rs b/src/keys.rs index 2f3bcce118..55e5d074e6 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -33,6 +33,8 @@ pub struct KeyConfig { pub exit_popup: KeyEvent, pub open_commit: KeyEvent, pub open_commit_editor: KeyEvent, + pub show_find_commit_text_input: KeyEvent, + pub focus_find_commit: KeyEvent, pub open_help: KeyEvent, pub move_left: KeyEvent, pub move_right: KeyEvent, @@ -85,7 +87,9 @@ impl Default for KeyConfig { exit: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL}, exit_popup: KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::empty()}, open_commit: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()}, - open_commit_editor: KeyEvent { code: KeyCode::Char('e'), modifiers:KeyModifiers::CONTROL}, + open_commit_editor: KeyEvent { code: KeyCode::Char('e'), modifiers:KeyModifiers::CONTROL}, + show_find_commit_text_input: KeyEvent {code: KeyCode::Char('s'), modifiers: KeyModifiers::empty()}, + focus_find_commit: KeyEvent {code: KeyCode::Down, modifiers: KeyModifiers::ALT}, open_help: KeyEvent { code: KeyCode::Char('h'), modifiers: KeyModifiers::empty()}, move_left: KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::empty()}, move_right: KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::empty()}, @@ -206,27 +210,21 @@ impl KeyConfig { | KeyCode::BackTab | KeyCode::Delete | KeyCode::Insert - | KeyCode::Esc => { - format!( - "{}{}", - Self::get_modifier_hint(ev.modifiers), - self.get_key_symbol(ev.code) - ) - } - KeyCode::Char(c) => { - format!( - "{}{}", - Self::get_modifier_hint(ev.modifiers), - c - ) - } - KeyCode::F(u) => { - format!( - "{}F{}", - Self::get_modifier_hint(ev.modifiers), - u - ) - } + | KeyCode::Esc => format!( + "{}{}", + Self::get_modifier_hint(ev.modifiers), + self.get_key_symbol(ev.code) + ), + KeyCode::Char(c) => format!( + "{}{}", + Self::get_modifier_hint(ev.modifiers), + c + ), + KeyCode::F(u) => format!( + "{}F{}", + Self::get_modifier_hint(ev.modifiers), + u + ), KeyCode::Null => Self::get_modifier_hint(ev.modifiers), } } diff --git a/src/queue.rs b/src/queue.rs index 678baf6bb8..7e5cb12090 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -61,6 +61,8 @@ pub enum InternalEvent { OpenExternalEditor(Option), /// Push(String), + /// + FilterLog(String), } /// diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 9a2a673520..baf3a0176d 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -2,7 +2,7 @@ use crate::{ components::{ visibility_blocking, CommandBlocking, CommandInfo, CommitDetailsComponent, CommitList, Component, - DrawableComponent, + DrawableComponent, FindCommitComponent, }, keys::SharedKeyConfig, queue::{InternalEvent, Queue}, @@ -31,12 +31,15 @@ const SLICE_SIZE: usize = 1200; pub struct Revlog { commit_details: CommitDetailsComponent, list: CommitList, + find_commit: FindCommitComponent, git_log: AsyncLog, git_tags: AsyncTags, queue: Queue, visible: bool, branch_name: cached::BranchName, key_config: SharedKeyConfig, + show_find_commit_box: bool, + filter_string: Option, } impl Revlog { @@ -57,6 +60,11 @@ impl Revlog { ), list: CommitList::new( &strings::log_title(&key_config), + theme.clone(), + key_config.clone(), + ), + find_commit: FindCommitComponent::new( + queue.clone(), theme, key_config.clone(), ), @@ -65,6 +73,8 @@ impl Revlog { visible: false, branch_name: cached::BranchName::new(CWD), key_config, + show_find_commit_box: true, + filter_string: None, } } @@ -166,6 +176,14 @@ impl Revlog { tags.and_then(|tags| tags.get(&commit).cloned()) }) } + + pub fn filter(&mut self, filter_by: String) { + if filter_by == "" { + self.list.set_filter(None); + } else { + self.list.set_filter(Some(filter_by)); + } + } } impl DrawableComponent for Revlog { @@ -174,22 +192,50 @@ impl DrawableComponent for Revlog { f: &mut Frame, area: Rect, ) -> Result<()> { - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage(60), - Constraint::Percentage(40), - ] - .as_ref(), - ) - .split(area); - if self.commit_details.is_visible() { - self.list.draw(f, chunks[0])?; - self.commit_details.draw(f, chunks[1])?; + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage(60), + Constraint::Percentage(40), + ] + .as_ref(), + ) + .split(area); + + if self.find_commit.is_visible() { + let log_find_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(90), + Constraint::Percentage(20), + ] + .as_ref(), + ) + .split(chunks[0]); + self.list.draw(f, log_find_chunks[0])?; + self.find_commit.draw(f, log_find_chunks[1])?; + self.commit_details.draw(f, chunks[1])?; + } } else { - self.list.draw(f, area)?; + if self.find_commit.is_visible() { + let log_find_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(90), + Constraint::Percentage(20), + ] + .as_ref(), + ) + .split(area); + self.list.draw(f, log_find_chunks[0])?; + self.find_commit.draw(f, log_find_chunks[1])?; + } else { + self.list.draw(f, area)?; + } } Ok(()) @@ -199,7 +245,11 @@ impl DrawableComponent for Revlog { impl Component for Revlog { fn event(&mut self, ev: Event) -> Result { if self.visible { - let event_used = self.list.event(ev)?; + let event_used = if self.find_commit.focused() { + self.find_commit.event(ev)? + } else { + self.list.event(ev)? + }; if event_used { self.update()?; @@ -244,6 +294,14 @@ impl Component for Revlog { .borrow_mut() .push_back(InternalEvent::SelectBranch); return Ok(true); + } else if k + == self.key_config.show_find_commit_text_input + { + self.find_commit.toggle_visible()?; + return Ok(true); + } else if k == self.key_config.focus_find_commit { + self.find_commit.focus(true); + return Ok(true); } } } From 2a5d9a83dd294cad59c570b2bd931f04adf4f155 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 00:23:58 +0000 Subject: [PATCH 002/118] Add esc to escape and fix invisible commit details --- src/components/find_commit.rs | 1 + src/tabs/revlog.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs index a3bca72267..a3b34044ec 100644 --- a/src/components/find_commit.rs +++ b/src/components/find_commit.rs @@ -66,6 +66,7 @@ impl Component for FindCommitComponent { return Ok(true); } else if e == self.key_config.exit_popup { // Prevent text input closing + self.focus(false); return Ok(true); } } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index baf3a0176d..9ce5a44273 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -218,6 +218,9 @@ impl DrawableComponent for Revlog { self.list.draw(f, log_find_chunks[0])?; self.find_commit.draw(f, log_find_chunks[1])?; self.commit_details.draw(f, chunks[1])?; + } else { + self.list.draw(f, chunks[0])?; + self.commit_details.draw(f, chunks[1])?; } } else { if self.find_commit.is_visible() { From 5cc8e7d934545eb01cdf637934ff700a45ac0643 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 00:25:34 +0000 Subject: [PATCH 003/118] Remove unused lines --- src/components/find_commit.rs | 18 +----------------- src/tabs/revlog.rs | 2 -- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs index a3b34044ec..c34a46f407 100644 --- a/src/components/find_commit.rs +++ b/src/components/find_commit.rs @@ -14,7 +14,6 @@ use tui::{backend::Backend, layout::Rect, Frame}; pub struct FindCommitComponent { input: TextInputComponent, - branch_ref: Option, queue: Queue, is_focused: bool, visible: bool, @@ -117,29 +116,14 @@ impl FindCommitComponent { &strings::rename_branch_popup_msg(&key_config), true, ); - input_component.show().expect("Will not err"); + input_component.show().expect("Will not error"); input_component.set_should_use_rect(true); Self { queue, input: input_component, - branch_ref: None, key_config, visible: false, is_focused: false, } } - - /// - pub fn open( - &mut self, - branch_ref: String, - cur_name: String, - ) -> Result<()> { - self.branch_ref = None; - self.branch_ref = Some(branch_ref); - self.input.set_text(cur_name); - self.show()?; - - Ok(()) - } } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 9ce5a44273..c9e418bdfa 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -38,8 +38,6 @@ pub struct Revlog { visible: bool, branch_name: cached::BranchName, key_config: SharedKeyConfig, - show_find_commit_box: bool, - filter_string: Option, } impl Revlog { From be0c76f90ac6273d5c2c1fb738b27e20b02eb7e5 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 00:26:35 +0000 Subject: [PATCH 004/118] Fix revlog --- src/tabs/revlog.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index c9e418bdfa..b6d9a8351f 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -71,8 +71,6 @@ impl Revlog { visible: false, branch_name: cached::BranchName::new(CWD), key_config, - show_find_commit_box: true, - filter_string: None, } } From bd5123a9f604d82cdc0b8d08d7b28070f192bc49 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 00:34:12 +0000 Subject: [PATCH 005/118] Fix vim keys test --- assets/vim_style_key_config.ron | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/vim_style_key_config.ron b/assets/vim_style_key_config.ron index bd193b3e00..72c64cb865 100644 --- a/assets/vim_style_key_config.ron +++ b/assets/vim_style_key_config.ron @@ -70,6 +70,9 @@ push: ( code: Char('p'), modifiers: ( bits: 0,),), fetch: ( code: Char('f'), modifiers: ( bits: 0,),), + show_find_commit_text_input: ( code: Char('s'), modifiers: ( bits: 0,),), + focus_find_commit: ( code: Char('j'), modifiers: ( bits: 3,),), + //removed in 0.11 //tab_toggle_reverse_windows: ( code: BackTab, modifiers: ( bits: 1,),), ) From 3414a05f4fb37358b5d53d99968b170e08d26e13 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 00:41:08 +0000 Subject: [PATCH 006/118] Allow searching to happen live rather than press enter --- src/components/find_commit.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs index c34a46f407..608d50606f 100644 --- a/src/components/find_commit.rs +++ b/src/components/find_commit.rs @@ -55,21 +55,18 @@ impl Component for FindCommitComponent { fn event(&mut self, ev: Event) -> Result { if self.is_visible() { if let Event::Key(e) = ev { - if e == self.key_config.enter { - // Send internal event to filter revlog - self.queue.borrow_mut().push_back( - InternalEvent::FilterLog( - self.input.get_text().to_string(), - ), - ); - return Ok(true); - } else if e == self.key_config.exit_popup { + if e == self.key_config.exit_popup { // Prevent text input closing self.focus(false); return Ok(true); } } if self.input.event(ev)? { + self.queue.borrow_mut().push_back( + InternalEvent::FilterLog( + self.input.get_text().to_string(), + ), + ); return Ok(true); } } From 07f4ec1346a0d94761c76fd7024c3f13f85669a3 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 00:50:59 +0000 Subject: [PATCH 007/118] Filter commits in selected_entry --- src/components/commitlist.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 5efae50e04..c7ae84c49c 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -123,9 +123,21 @@ impl CommitList { /// pub fn selected_entry(&self) -> Option<&LogEntry> { - self.items.iter().nth( - self.selection.saturating_sub(self.items.index_offset()), - ) + self.items + .iter() + .filter(|log_entry| { + if let Some(filter_string) = &self.filter_string { + return log_entry + .hash_short + .contains(filter_string) + || log_entry.msg.contains(filter_string); + } + true + }) + .nth( + self.selection + .saturating_sub(self.items.index_offset()), + ) } pub fn copy_entry_hash(&self) -> Result<()> { From e8a6db5c42cdb464415655022bfce576398f53f9 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 12:15:59 +0000 Subject: [PATCH 008/118] Fix naming --- src/components/find_commit.rs | 26 +++++++------------------- src/strings.rs | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs index 608d50606f..80c242cc99 100644 --- a/src/components/find_commit.rs +++ b/src/components/find_commit.rs @@ -1,6 +1,6 @@ use super::{ - textinput::TextInputComponent, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, + textinput::TextInputComponent, CommandBlocking, CommandInfo, + Component, DrawableComponent, }; use crate::{ keys::SharedKeyConfig, @@ -34,22 +34,10 @@ impl DrawableComponent for FindCommitComponent { impl Component for FindCommitComponent { fn commands( &self, - out: &mut Vec, - force_all: bool, + _out: &mut Vec, + _force_all: bool, ) -> CommandBlocking { - if self.is_visible() || force_all { - self.input.commands(out, force_all); - - out.push(CommandInfo::new( - strings::commands::rename_branch_confirm_msg( - &self.key_config, - ), - true, - true, - )); - } - - visibility_blocking(self) + CommandBlocking::PassingOn } fn event(&mut self, ev: Event) -> Result { @@ -109,8 +97,8 @@ impl FindCommitComponent { let mut input_component = TextInputComponent::new( theme, key_config.clone(), - &strings::rename_branch_popup_title(&key_config), - &strings::rename_branch_popup_msg(&key_config), + &strings::find_commit_title(&key_config), + &strings::find_commit_msg(&key_config), true, ); input_component.show().expect("Will not error"); diff --git a/src/strings.rs b/src/strings.rs index 617a738c82..8f96d2cfe6 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -113,6 +113,12 @@ pub fn confirm_msg_delete_branch( pub fn log_title(_key_config: &SharedKeyConfig) -> String { "Commit".to_string() } +pub fn find_commit_title(_key_config: &SharedKeyConfig) -> String { + "Find Commit".to_string() +} +pub fn find_commit_msg(_key_config: &SharedKeyConfig) -> String { + "Search Sha, Author and Message".to_string() +} pub fn tag_commit_popup_title( _key_config: &SharedKeyConfig, ) -> String { @@ -714,6 +720,17 @@ pub mod commands { CMD_GROUP_LOG, ) } + pub fn find_commit(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Find Commit [{}]", + key_config + .get_hint(key_config.show_find_commit_text_input), + ), + "find commit", + CMD_GROUP_LOG, + ) + } pub fn tag_commit_confirm_msg( key_config: &SharedKeyConfig, ) -> CommandText { From 95e3a709e00c00a941250fc3a850d831dc56b397 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 12:16:32 +0000 Subject: [PATCH 009/118] Remove unused command --- src/strings.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/strings.rs b/src/strings.rs index 8f96d2cfe6..fe42cce9d0 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -720,17 +720,6 @@ pub mod commands { CMD_GROUP_LOG, ) } - pub fn find_commit(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Find Commit [{}]", - key_config - .get_hint(key_config.show_find_commit_text_input), - ), - "find commit", - CMD_GROUP_LOG, - ) - } pub fn tag_commit_confirm_msg( key_config: &SharedKeyConfig, ) -> CommandText { From 709b50133a0c52f2c8e8246c0f8274f48c674b1b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 12:30:15 +0000 Subject: [PATCH 010/118] Allow escaping with esc again --- src/components/find_commit.rs | 2 +- src/tabs/revlog.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs index 80c242cc99..728c81a76e 100644 --- a/src/components/find_commit.rs +++ b/src/components/find_commit.rs @@ -41,7 +41,7 @@ impl Component for FindCommitComponent { } fn event(&mut self, ev: Event) -> Result { - if self.is_visible() { + if self.is_visible() && self.focused() { if let Event::Key(e) = ev { if e == self.key_config.exit_popup { // Prevent text input closing diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index b6d9a8351f..93faa18928 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -244,11 +244,10 @@ impl DrawableComponent for Revlog { impl Component for Revlog { fn event(&mut self, ev: Event) -> Result { if self.visible { - let event_used = if self.find_commit.focused() { - self.find_commit.event(ev)? - } else { - self.list.event(ev)? - }; + let mut event_used = self.find_commit.event(ev)?; + if !event_used { + event_used = self.list.event(ev)?; + } if event_used { self.update()?; From b06a716598bbf16324f5cfb32b166da24b0a05fb Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 12:33:31 +0000 Subject: [PATCH 011/118] Auto set focus on filter commit to true --- src/tabs/revlog.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 93faa18928..b2d4cee566 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -296,6 +296,7 @@ impl Component for Revlog { == self.key_config.show_find_commit_text_input { self.find_commit.toggle_visible()?; + self.find_commit.focus(true); return Ok(true); } else if k == self.key_config.focus_find_commit { self.find_commit.focus(true); From bfbcc9a84b5a49f54a87688b9d0c4c1f36ff53a2 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 13:31:53 +0000 Subject: [PATCH 012/118] If filtering log get all commits --- asyncgit/src/revlog.rs | 13 ++++++++---- src/tabs/revlog.rs | 48 +++++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index ffb11218b9..c29ed6ad10 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -97,8 +97,11 @@ impl AsyncLog { Ok(false) } - /// - pub fn fetch(&mut self) -> Result { + /// None for amount means fetch the default + pub fn fetch( + &mut self, + amount: Option, + ) -> Result { self.background.store(false, Ordering::Relaxed); if self.is_pending() { @@ -125,6 +128,7 @@ impl AsyncLog { arc_current, arc_background, &sender, + amount.unwrap_or(LIMIT_COUNT), ) .expect("failed to fetch"); @@ -140,14 +144,15 @@ impl AsyncLog { arc_current: Arc>>, arc_background: Arc, sender: &Sender, + amount: usize, ) -> Result<()> { - let mut entries = Vec::with_capacity(LIMIT_COUNT); + let mut entries = Vec::with_capacity(amount); let r = repo(CWD)?; let mut walker = LogWalker::new(&r); loop { entries.clear(); let res_is_err = - walker.read(&mut entries, LIMIT_COUNT).is_err(); + walker.read(&mut entries, amount).is_err(); if !res_is_err { let mut current = arc_current.lock()?; diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index b2d4cee566..df99a1810b 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -38,6 +38,8 @@ pub struct Revlog { visible: bool, branch_name: cached::BranchName, key_config: SharedKeyConfig, + is_filtering: bool, + has_all_commits: bool, } impl Revlog { @@ -71,6 +73,8 @@ impl Revlog { visible: false, branch_name: cached::BranchName::new(CWD), key_config, + is_filtering: false, + has_all_commits: false, } } @@ -84,8 +88,13 @@ impl Revlog { /// pub fn update(&mut self) -> Result<()> { if self.visible { - let log_changed = - self.git_log.fetch()? == FetchStatus::Started; + let log_changed = if self.is_filtering { + // If filtering should get all commits + self.git_log.fetch(Some(usize::MAX))? + == FetchStatus::Started + } else { + self.git_log.fetch(None)? == FetchStatus::Started + }; self.list.set_count_total(self.git_log.count()?); @@ -140,15 +149,24 @@ impl Revlog { let want_min = self.list.selection().saturating_sub(SLICE_SIZE / 2); - let commits = sync::get_commits_info( - CWD, - &self.git_log.get_slice(want_min, SLICE_SIZE)?, - self.list.current_size().0.into(), - ); + // If filtering get all commits + let commits = if self.is_filtering { + sync::get_commits_info( + CWD, + &self.git_log.get_slice(0, usize::MAX)?, + self.list.current_size().0.into(), + ) + } else { + sync::get_commits_info( + CWD, + &self.git_log.get_slice(want_min, SLICE_SIZE)?, + self.list.current_size().0.into(), + ) + }; if let Ok(commits) = commits { self.list.items().set_items(want_min, commits); - } + }; Ok(()) } @@ -175,8 +193,22 @@ impl Revlog { pub fn filter(&mut self, filter_by: String) { if filter_by == "" { + self.is_filtering = false; + self.has_all_commits = false; self.list.set_filter(None); } else { + self.is_filtering = true; + // Don't get all the commits again if already have them, + // depening on repo could be expensive to constantly update + if !self.has_all_commits { + if let Err(e) = self.update() { + self.queue.borrow_mut().push_back( + InternalEvent::ShowErrorMsg(e.to_string()), + ); + } + self.has_all_commits = true; + } + self.list.set_filter(Some(filter_by)); } } From 0e671b91e420f01043ee0f05b4bec229317830c5 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 13:47:05 +0000 Subject: [PATCH 013/118] Change set total count to update total count in commitlist --- src/components/commitlist.rs | 16 ++++++++++++++-- src/tabs/revlog.rs | 2 +- src/tabs/stashlist.rs | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index c7ae84c49c..4bc5b10003 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -86,8 +86,20 @@ impl CommitList { } /// - pub fn set_count_total(&mut self, total: usize) { - self.count_total = total; + pub fn update_total_count(&mut self) { + self.count_total = self + .items + .iter() + .filter(|log_entry| { + if let Some(filter_string) = &self.filter_string { + return log_entry + .hash_short + .contains(filter_string) + || log_entry.msg.contains(filter_string); + } + true + }) + .count(); self.selection = cmp::min(self.selection, self.selection_max()); } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index df99a1810b..71b1001be3 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -96,7 +96,7 @@ impl Revlog { self.git_log.fetch(None)? == FetchStatus::Started }; - self.list.set_count_total(self.git_log.count()?); + self.list.update_total_count(); let selection = self.list.selection(); let selection_max = self.list.selection_max(); diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index 98f6c0139a..63d285e65a 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -48,7 +48,7 @@ impl StashList { let commits = sync::get_commits_info(CWD, stashes.as_slice(), 100)?; - self.list.set_count_total(commits.len()); + self.list.update_total_count(); self.list.items().set_items(0, commits); } From 1b1c6ee82e035465f58f5c2555e445de85b37435 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 13:48:25 +0000 Subject: [PATCH 014/118] Change key to bring up find commit box to : --- src/keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keys.rs b/src/keys.rs index 55e5d074e6..7e39dbabef 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -88,7 +88,7 @@ impl Default for KeyConfig { exit_popup: KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::empty()}, open_commit: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()}, open_commit_editor: KeyEvent { code: KeyCode::Char('e'), modifiers:KeyModifiers::CONTROL}, - show_find_commit_text_input: KeyEvent {code: KeyCode::Char('s'), modifiers: KeyModifiers::empty()}, + show_find_commit_text_input: KeyEvent {code: KeyCode::Char(':'), modifiers: KeyModifiers::empty()}, focus_find_commit: KeyEvent {code: KeyCode::Down, modifiers: KeyModifiers::ALT}, open_help: KeyEvent { code: KeyCode::Char('h'), modifiers: KeyModifiers::empty()}, move_left: KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::empty()}, From 21904ea6029402d68e1c670ee47f9220f0c3da74 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 13:49:44 +0000 Subject: [PATCH 015/118] Don't show char count in find commit component --- src/components/find_commit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs index 728c81a76e..2b54bc677b 100644 --- a/src/components/find_commit.rs +++ b/src/components/find_commit.rs @@ -99,7 +99,7 @@ impl FindCommitComponent { key_config.clone(), &strings::find_commit_title(&key_config), &strings::find_commit_msg(&key_config), - true, + false, ); input_component.show().expect("Will not error"); input_component.set_should_use_rect(true); From 84ca7d268cf26874eb25521118efc22a3c705151 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 13:52:20 +0000 Subject: [PATCH 016/118] Also filter by author --- src/components/commitlist.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 4bc5b10003..352992b340 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -95,7 +95,8 @@ impl CommitList { return log_entry .hash_short .contains(filter_string) - || log_entry.msg.contains(filter_string); + || log_entry.msg.contains(filter_string) + || log_entry.author.contains(filter_string); } true }) @@ -142,7 +143,8 @@ impl CommitList { return log_entry .hash_short .contains(filter_string) - || log_entry.msg.contains(filter_string); + || log_entry.msg.contains(filter_string) + || log_entry.author.contains(filter_string); } true }) @@ -297,7 +299,8 @@ impl CommitList { return log_entry .hash_short .contains(filter_string) - || log_entry.msg.contains(filter_string); + || log_entry.msg.contains(filter_string) + || log_entry.author.contains(filter_string); } true }) From ab8770c64ecd34fba672fc77c0cc8b124c0994dd Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 13:57:52 +0000 Subject: [PATCH 017/118] Add find commit to log commands --- src/strings.rs | 11 +++++++++++ src/tabs/revlog.rs | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/src/strings.rs b/src/strings.rs index fe42cce9d0..33c0ff3e2c 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -320,6 +320,17 @@ pub mod commands { CMD_GROUP_LOG, ) } + pub fn find_commit(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Find Commit [{}]", + key_config + .get_hint(key_config.show_find_commit_text_input), + ), + "show find commit box to search by sha, author or message", + CMD_GROUP_LOG, + ) + } pub fn diff_home_end( key_config: &SharedKeyConfig, ) -> CommandText { diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 71b1001be3..79bcfb5b82 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -382,6 +382,12 @@ impl Component for Revlog { self.visible || force_all, )); + out.push(CommandInfo::new( + strings::commands::find_commit(&self.key_config), + true, + self.visible || force_all, + )); + visibility_blocking(self) } From 41c121efd4ee74243a74e4420251d542cddd197b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 14:06:07 +0000 Subject: [PATCH 018/118] esc on log tab to cancel search --- src/components/find_commit.rs | 5 +++++ src/tabs/revlog.rs | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs index 2b54bc677b..cf766092e7 100644 --- a/src/components/find_commit.rs +++ b/src/components/find_commit.rs @@ -46,6 +46,7 @@ impl Component for FindCommitComponent { if e == self.key_config.exit_popup { // Prevent text input closing self.focus(false); + self.visible = false; return Ok(true); } } @@ -111,4 +112,8 @@ impl FindCommitComponent { is_focused: false, } } + + pub fn clear_input(&mut self) { + self.input.clear(); + } } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 79bcfb5b82..ab23c0167d 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -333,6 +333,16 @@ impl Component for Revlog { } else if k == self.key_config.focus_find_commit { self.find_commit.focus(true); return Ok(true); + } else if k == self.key_config.exit_popup { + self.filter("".to_string()); + self.find_commit.clear_input(); + if let Err(e) = self.update() { + self.queue.borrow_mut().push_back( + InternalEvent::ShowErrorMsg( + e.to_string(), + ), + ); + } } } } From 56ef6be7fbd21f0ec7ee454da5edd107f18150ca Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 14:06:59 +0000 Subject: [PATCH 019/118] Change search back to s --- src/keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keys.rs b/src/keys.rs index 7e39dbabef..55e5d074e6 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -88,7 +88,7 @@ impl Default for KeyConfig { exit_popup: KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::empty()}, open_commit: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()}, open_commit_editor: KeyEvent { code: KeyCode::Char('e'), modifiers:KeyModifiers::CONTROL}, - show_find_commit_text_input: KeyEvent {code: KeyCode::Char(':'), modifiers: KeyModifiers::empty()}, + show_find_commit_text_input: KeyEvent {code: KeyCode::Char('s'), modifiers: KeyModifiers::empty()}, focus_find_commit: KeyEvent {code: KeyCode::Down, modifiers: KeyModifiers::ALT}, open_help: KeyEvent { code: KeyCode::Char('h'), modifiers: KeyModifiers::empty()}, move_left: KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::empty()}, From e6dd94a3cdfee02f341d75fac6df555a155513c5 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 14:21:23 +0000 Subject: [PATCH 020/118] Also update count when not filtering --- src/tabs/revlog.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index ab23c0167d..85081449ac 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -196,8 +196,10 @@ impl Revlog { self.is_filtering = false; self.has_all_commits = false; self.list.set_filter(None); + self.list.update_total_count(); } else { self.is_filtering = true; + self.list.set_filter(Some(filter_by)); // Don't get all the commits again if already have them, // depening on repo could be expensive to constantly update if !self.has_all_commits { @@ -208,8 +210,6 @@ impl Revlog { } self.has_all_commits = true; } - - self.list.set_filter(Some(filter_by)); } } } From b5fdcebe63cd3b3418101cfa5809bd038a0b6f42 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 15:42:01 +0000 Subject: [PATCH 021/118] Fix h not working in text input --- src/app.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index 09cf56b4a2..6af3b36e34 100644 --- a/src/app.rs +++ b/src/app.rs @@ -349,11 +349,11 @@ impl App { create_branch_popup, rename_branch_popup, select_branch_popup, - help, revlog, status_tab, stashing_tab, - stashlist_tab + stashlist_tab, + help ] ); From 1ba3b2337df308d28f1604e94238fe77b05d21fb Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 15:49:26 +0000 Subject: [PATCH 022/118] Change tabs to spaces in keys.rs --- src/keys.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index 55e5d074e6..34e84984b1 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -87,9 +87,9 @@ impl Default for KeyConfig { exit: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL}, exit_popup: KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::empty()}, open_commit: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()}, - open_commit_editor: KeyEvent { code: KeyCode::Char('e'), modifiers:KeyModifiers::CONTROL}, - show_find_commit_text_input: KeyEvent {code: KeyCode::Char('s'), modifiers: KeyModifiers::empty()}, - focus_find_commit: KeyEvent {code: KeyCode::Down, modifiers: KeyModifiers::ALT}, + open_commit_editor: KeyEvent { code: KeyCode::Char('e'), modifiers:KeyModifiers::CONTROL}, + show_find_commit_text_input: KeyEvent {code: KeyCode::Char('s'), modifiers: KeyModifiers::empty()}, + focus_find_commit: KeyEvent {code: KeyCode::Down, modifiers: KeyModifiers::ALT}, open_help: KeyEvent { code: KeyCode::Char('h'), modifiers: KeyModifiers::empty()}, move_left: KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::empty()}, move_right: KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::empty()}, From 504e0a960d1a048c950e3cd8070e7b4e187fea54 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 19:47:42 +0000 Subject: [PATCH 023/118] Filter commits in commit log rather than commitlist --- asyncgit/src/revlog.rs | 13 ++--- src/components/commitlist.rs | 53 ++------------------ src/tabs/revlog.rs | 95 +++++++++++++++++++----------------- src/tabs/stashlist.rs | 2 +- 4 files changed, 60 insertions(+), 103 deletions(-) diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index c29ed6ad10..ffb11218b9 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -97,11 +97,8 @@ impl AsyncLog { Ok(false) } - /// None for amount means fetch the default - pub fn fetch( - &mut self, - amount: Option, - ) -> Result { + /// + pub fn fetch(&mut self) -> Result { self.background.store(false, Ordering::Relaxed); if self.is_pending() { @@ -128,7 +125,6 @@ impl AsyncLog { arc_current, arc_background, &sender, - amount.unwrap_or(LIMIT_COUNT), ) .expect("failed to fetch"); @@ -144,15 +140,14 @@ impl AsyncLog { arc_current: Arc>>, arc_background: Arc, sender: &Sender, - amount: usize, ) -> Result<()> { - let mut entries = Vec::with_capacity(amount); + let mut entries = Vec::with_capacity(LIMIT_COUNT); let r = repo(CWD)?; let mut walker = LogWalker::new(&r); loop { entries.clear(); let res_is_err = - walker.read(&mut entries, amount).is_err(); + walker.read(&mut entries, LIMIT_COUNT).is_err(); if !res_is_err { let mut current = arc_current.lock()?; diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 352992b340..387ad5c743 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -39,7 +39,6 @@ pub struct CommitList { scroll_top: Cell, theme: SharedTheme, key_config: SharedKeyConfig, - filter_string: Option, } impl CommitList { @@ -61,7 +60,6 @@ impl CommitList { theme, key_config, title: String::from(title), - filter_string: None, } } @@ -86,21 +84,8 @@ impl CommitList { } /// - pub fn update_total_count(&mut self) { - self.count_total = self - .items - .iter() - .filter(|log_entry| { - if let Some(filter_string) = &self.filter_string { - return log_entry - .hash_short - .contains(filter_string) - || log_entry.msg.contains(filter_string) - || log_entry.author.contains(filter_string); - } - true - }) - .count(); + pub fn set_total_count(&mut self, total: usize) { + self.count_total = total; self.selection = cmp::min(self.selection, self.selection_max()); } @@ -129,29 +114,11 @@ impl CommitList { self.tags = Some(tags); } - /// - pub fn set_filter(&mut self, filter_string: Option) { - self.filter_string = filter_string; - } - /// pub fn selected_entry(&self) -> Option<&LogEntry> { - self.items - .iter() - .filter(|log_entry| { - if let Some(filter_string) = &self.filter_string { - return log_entry - .hash_short - .contains(filter_string) - || log_entry.msg.contains(filter_string) - || log_entry.author.contains(filter_string); - } - true - }) - .nth( - self.selection - .saturating_sub(self.items.index_offset()), - ) + self.items.iter().nth( + self.selection.saturating_sub(self.items.index_offset()), + ) } pub fn copy_entry_hash(&self) -> Result<()> { @@ -294,16 +261,6 @@ impl CommitList { for (idx, e) in self .items .iter() - .filter(|log_entry| { - if let Some(filter_string) = &self.filter_string { - return log_entry - .hash_short - .contains(filter_string) - || log_entry.msg.contains(filter_string) - || log_entry.author.contains(filter_string); - } - true - }) .skip(self.scroll_top.get()) .take(height) .enumerate() diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 85081449ac..345a0f0a5e 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -38,8 +38,8 @@ pub struct Revlog { visible: bool, branch_name: cached::BranchName, key_config: SharedKeyConfig, - is_filtering: bool, - has_all_commits: bool, + filter_string: Option, + filter_count: usize, } impl Revlog { @@ -73,8 +73,8 @@ impl Revlog { visible: false, branch_name: cached::BranchName::new(CWD), key_config, - is_filtering: false, - has_all_commits: false, + filter_string: None, + filter_count: 0, } } @@ -88,15 +88,14 @@ impl Revlog { /// pub fn update(&mut self) -> Result<()> { if self.visible { - let log_changed = if self.is_filtering { - // If filtering should get all commits - self.git_log.fetch(Some(usize::MAX))? - == FetchStatus::Started - } else { - self.git_log.fetch(None)? == FetchStatus::Started - }; + let log_changed = + self.git_log.fetch()? == FetchStatus::Started; - self.list.update_total_count(); + if let Some(_) = &self.filter_string { + self.list.set_total_count(self.filter_count); + } else { + self.list.set_total_count(self.git_log.count()?); + } let selection = self.list.selection(); let selection_max = self.list.selection_max(); @@ -149,23 +148,36 @@ impl Revlog { let want_min = self.list.selection().saturating_sub(SLICE_SIZE / 2); - // If filtering get all commits - let commits = if self.is_filtering { - sync::get_commits_info( - CWD, - &self.git_log.get_slice(0, usize::MAX)?, - self.list.current_size().0.into(), - ) - } else { - sync::get_commits_info( - CWD, - &self.git_log.get_slice(want_min, SLICE_SIZE)?, - self.list.current_size().0.into(), - ) - }; - - if let Ok(commits) = commits { - self.list.items().set_items(want_min, commits); + let commits = sync::get_commits_info( + CWD, + &self.git_log.get_slice(want_min, SLICE_SIZE)?, + self.list.current_size().0.into(), + ); + + if let Ok(mut commits) = commits { + if let Some(filter_string) = &self.filter_string { + let filtered_commits = commits + .drain(..) + .filter(|commit_info| { + return commit_info + .id + .get_short_string() + .contains(filter_string) + || commit_info + .message + .contains(filter_string) + || commit_info + .author + .contains(filter_string); + }) + .collect::>(); + self.filter_count = filtered_commits.len(); + self.list + .items() + .set_items(want_min, filtered_commits); + } else { + self.list.items().set_items(want_min, commits); + } }; Ok(()) @@ -193,23 +205,16 @@ impl Revlog { pub fn filter(&mut self, filter_by: String) { if filter_by == "" { - self.is_filtering = false; - self.has_all_commits = false; - self.list.set_filter(None); - self.list.update_total_count(); + self.filter_string = None; + self.list.clear(); } else { - self.is_filtering = true; - self.list.set_filter(Some(filter_by)); - // Don't get all the commits again if already have them, - // depening on repo could be expensive to constantly update - if !self.has_all_commits { - if let Err(e) = self.update() { - self.queue.borrow_mut().push_back( - InternalEvent::ShowErrorMsg(e.to_string()), - ); - } - self.has_all_commits = true; - } + self.filter_string = Some(filter_by); + self.list.clear(); + } + if let Err(e) = self.update() { + self.queue.borrow_mut().push_back( + InternalEvent::ShowErrorMsg(e.to_string()), + ); } } } diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index 63d285e65a..d6782c61bb 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -48,7 +48,7 @@ impl StashList { let commits = sync::get_commits_info(CWD, stashes.as_slice(), 100)?; - self.list.update_total_count(); + self.list.set_total_count(commits.len()); self.list.items().set_items(0, commits); } From 6930d73b31d6a3a94ee9b0a542173161356959e7 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 21:31:32 +0000 Subject: [PATCH 024/118] Add extend item list to fetch commit --- asyncgit/src/revlog.rs | 6 +-- src/components/utils/logitems.rs | 10 ++++ src/tabs/revlog.rs | 88 +++++++++++++++++++------------- 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index ffb11218b9..a137b6c51f 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -34,8 +34,8 @@ pub struct AsyncLog { background: Arc, } -static LIMIT_COUNT: usize = 3000; -static SLEEP_FOREGROUND: Duration = Duration::from_millis(2); +static LIMIT_COUNT: usize = 5; +static SLEEP_FOREGROUND: Duration = Duration::from_millis(500); static SLEEP_BACKGROUND: Duration = Duration::from_millis(1000); impl AsyncLog { @@ -63,7 +63,7 @@ impl AsyncLog { let list = self.current.lock()?; let list_len = list.len(); let min = start_index.min(list_len); - let max = min + amount; + let max = min.saturating_add(amount); let max = max.min(list_len); Ok(list[min..max].to_vec()) } diff --git a/src/components/utils/logitems.rs b/src/components/utils/logitems.rs index 9fa26be0f9..5af9b2f3c2 100644 --- a/src/components/utils/logitems.rs +++ b/src/components/utils/logitems.rs @@ -62,6 +62,16 @@ impl ItemBatch { self.index_offset = start_index; } + /// + pub fn extend(&mut self, commits: Vec) { + self.items.extend(commits.into_iter().map(LogEntry::from)); + } + + /// + pub fn len(&self) -> usize { + self.items.len() + } + /// returns `true` if we should fetch updated list of items pub fn needs_data(&self, idx: usize, idx_max: usize) -> bool { let want_min = diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 345a0f0a5e..d313bd7bdf 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -40,6 +40,7 @@ pub struct Revlog { key_config: SharedKeyConfig, filter_string: Option, filter_count: usize, + prev_log_count: usize, } impl Revlog { @@ -75,6 +76,7 @@ impl Revlog { key_config, filter_string: None, filter_count: 0, + prev_log_count: 0, } } @@ -91,12 +93,6 @@ impl Revlog { let log_changed = self.git_log.fetch()? == FetchStatus::Started; - if let Some(_) = &self.filter_string { - self.list.set_total_count(self.filter_count); - } else { - self.list.set_total_count(self.git_log.count()?); - } - let selection = self.list.selection(); let selection_max = self.list.selection_max(); if self.list.items().needs_data(selection, selection_max) @@ -105,6 +101,12 @@ impl Revlog { self.fetch_commits()?; } + if let Some(_) = &self.filter_string { + //self.list.set_total_count(self.git_log.count()?); + } else { + self.list.set_total_count(self.git_log.count()?); + } + self.git_tags.request(Duration::from_secs(3), false)?; self.list.set_branch( @@ -148,37 +150,50 @@ impl Revlog { let want_min = self.list.selection().saturating_sub(SLICE_SIZE / 2); - let commits = sync::get_commits_info( - CWD, - &self.git_log.get_slice(want_min, SLICE_SIZE)?, - self.list.current_size().0.into(), - ); - - if let Ok(mut commits) = commits { - if let Some(filter_string) = &self.filter_string { - let filtered_commits = commits - .drain(..) - .filter(|commit_info| { - return commit_info - .id - .get_short_string() - .contains(filter_string) - || commit_info - .message - .contains(filter_string) - || commit_info - .author - .contains(filter_string); - }) - .collect::>(); - self.filter_count = filtered_commits.len(); - self.list - .items() - .set_items(want_min, filtered_commits); - } else { + if self.filter_string.is_none() { + let commits = sync::get_commits_info( + CWD, + &self.git_log.get_slice(want_min, SLICE_SIZE)?, + self.list.current_size().0.into(), + ); + + if let Ok(commits) = commits { self.list.items().set_items(want_min, commits); - } - }; + }; + } else { + let commits = sync::get_commits_info( + CWD, + &self + .git_log + .get_slice(self.prev_log_count, usize::MAX)?, + self.list.current_size().0.into(), + ); + + if let Ok(mut commits) = commits { + if let Some(filter_string) = &self.filter_string { + let filtered_commits = commits + .drain(..) + .filter(|commit_info| { + return commit_info + .id + .get_short_string() + .contains(filter_string) + || commit_info + .message + .contains(filter_string) + || commit_info + .author + .contains(filter_string); + }) + .collect::>(); + self.filter_count += filtered_commits.len(); + self.list.items().extend(filtered_commits); + let total_count = self.filter_count; + self.list.set_total_count(total_count); + } + }; + self.prev_log_count = self.git_log.count()?; + } Ok(()) } @@ -204,6 +219,7 @@ impl Revlog { } pub fn filter(&mut self, filter_by: String) { + self.filter_count = 0; if filter_by == "" { self.filter_string = None; self.list.clear(); From a505c05932998f69c84f5f3be98324d55cec3f24 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 4 Feb 2021 22:02:50 +0000 Subject: [PATCH 025/118] Revert to 1ba3b23: Change tabs to spaces in keys.rs --- asyncgit/src/revlog.rs | 19 +++--- src/components/commitlist.rs | 53 ++++++++++++++-- src/components/utils/logitems.rs | 10 --- src/tabs/revlog.rs | 103 ++++++++++++------------------- src/tabs/stashlist.rs | 2 +- 5 files changed, 102 insertions(+), 85 deletions(-) diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index a137b6c51f..c29ed6ad10 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -34,8 +34,8 @@ pub struct AsyncLog { background: Arc, } -static LIMIT_COUNT: usize = 5; -static SLEEP_FOREGROUND: Duration = Duration::from_millis(500); +static LIMIT_COUNT: usize = 3000; +static SLEEP_FOREGROUND: Duration = Duration::from_millis(2); static SLEEP_BACKGROUND: Duration = Duration::from_millis(1000); impl AsyncLog { @@ -63,7 +63,7 @@ impl AsyncLog { let list = self.current.lock()?; let list_len = list.len(); let min = start_index.min(list_len); - let max = min.saturating_add(amount); + let max = min + amount; let max = max.min(list_len); Ok(list[min..max].to_vec()) } @@ -97,8 +97,11 @@ impl AsyncLog { Ok(false) } - /// - pub fn fetch(&mut self) -> Result { + /// None for amount means fetch the default + pub fn fetch( + &mut self, + amount: Option, + ) -> Result { self.background.store(false, Ordering::Relaxed); if self.is_pending() { @@ -125,6 +128,7 @@ impl AsyncLog { arc_current, arc_background, &sender, + amount.unwrap_or(LIMIT_COUNT), ) .expect("failed to fetch"); @@ -140,14 +144,15 @@ impl AsyncLog { arc_current: Arc>>, arc_background: Arc, sender: &Sender, + amount: usize, ) -> Result<()> { - let mut entries = Vec::with_capacity(LIMIT_COUNT); + let mut entries = Vec::with_capacity(amount); let r = repo(CWD)?; let mut walker = LogWalker::new(&r); loop { entries.clear(); let res_is_err = - walker.read(&mut entries, LIMIT_COUNT).is_err(); + walker.read(&mut entries, amount).is_err(); if !res_is_err { let mut current = arc_current.lock()?; diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 387ad5c743..352992b340 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -39,6 +39,7 @@ pub struct CommitList { scroll_top: Cell, theme: SharedTheme, key_config: SharedKeyConfig, + filter_string: Option, } impl CommitList { @@ -60,6 +61,7 @@ impl CommitList { theme, key_config, title: String::from(title), + filter_string: None, } } @@ -84,8 +86,21 @@ impl CommitList { } /// - pub fn set_total_count(&mut self, total: usize) { - self.count_total = total; + pub fn update_total_count(&mut self) { + self.count_total = self + .items + .iter() + .filter(|log_entry| { + if let Some(filter_string) = &self.filter_string { + return log_entry + .hash_short + .contains(filter_string) + || log_entry.msg.contains(filter_string) + || log_entry.author.contains(filter_string); + } + true + }) + .count(); self.selection = cmp::min(self.selection, self.selection_max()); } @@ -114,11 +129,29 @@ impl CommitList { self.tags = Some(tags); } + /// + pub fn set_filter(&mut self, filter_string: Option) { + self.filter_string = filter_string; + } + /// pub fn selected_entry(&self) -> Option<&LogEntry> { - self.items.iter().nth( - self.selection.saturating_sub(self.items.index_offset()), - ) + self.items + .iter() + .filter(|log_entry| { + if let Some(filter_string) = &self.filter_string { + return log_entry + .hash_short + .contains(filter_string) + || log_entry.msg.contains(filter_string) + || log_entry.author.contains(filter_string); + } + true + }) + .nth( + self.selection + .saturating_sub(self.items.index_offset()), + ) } pub fn copy_entry_hash(&self) -> Result<()> { @@ -261,6 +294,16 @@ impl CommitList { for (idx, e) in self .items .iter() + .filter(|log_entry| { + if let Some(filter_string) = &self.filter_string { + return log_entry + .hash_short + .contains(filter_string) + || log_entry.msg.contains(filter_string) + || log_entry.author.contains(filter_string); + } + true + }) .skip(self.scroll_top.get()) .take(height) .enumerate() diff --git a/src/components/utils/logitems.rs b/src/components/utils/logitems.rs index 5af9b2f3c2..9fa26be0f9 100644 --- a/src/components/utils/logitems.rs +++ b/src/components/utils/logitems.rs @@ -62,16 +62,6 @@ impl ItemBatch { self.index_offset = start_index; } - /// - pub fn extend(&mut self, commits: Vec) { - self.items.extend(commits.into_iter().map(LogEntry::from)); - } - - /// - pub fn len(&self) -> usize { - self.items.len() - } - /// returns `true` if we should fetch updated list of items pub fn needs_data(&self, idx: usize, idx_max: usize) -> bool { let want_min = diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index d313bd7bdf..85081449ac 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -38,9 +38,8 @@ pub struct Revlog { visible: bool, branch_name: cached::BranchName, key_config: SharedKeyConfig, - filter_string: Option, - filter_count: usize, - prev_log_count: usize, + is_filtering: bool, + has_all_commits: bool, } impl Revlog { @@ -74,9 +73,8 @@ impl Revlog { visible: false, branch_name: cached::BranchName::new(CWD), key_config, - filter_string: None, - filter_count: 0, - prev_log_count: 0, + is_filtering: false, + has_all_commits: false, } } @@ -90,8 +88,15 @@ impl Revlog { /// pub fn update(&mut self) -> Result<()> { if self.visible { - let log_changed = - self.git_log.fetch()? == FetchStatus::Started; + let log_changed = if self.is_filtering { + // If filtering should get all commits + self.git_log.fetch(Some(usize::MAX))? + == FetchStatus::Started + } else { + self.git_log.fetch(None)? == FetchStatus::Started + }; + + self.list.update_total_count(); let selection = self.list.selection(); let selection_max = self.list.selection_max(); @@ -101,12 +106,6 @@ impl Revlog { self.fetch_commits()?; } - if let Some(_) = &self.filter_string { - //self.list.set_total_count(self.git_log.count()?); - } else { - self.list.set_total_count(self.git_log.count()?); - } - self.git_tags.request(Duration::from_secs(3), false)?; self.list.set_branch( @@ -150,50 +149,24 @@ impl Revlog { let want_min = self.list.selection().saturating_sub(SLICE_SIZE / 2); - if self.filter_string.is_none() { - let commits = sync::get_commits_info( + // If filtering get all commits + let commits = if self.is_filtering { + sync::get_commits_info( CWD, - &self.git_log.get_slice(want_min, SLICE_SIZE)?, + &self.git_log.get_slice(0, usize::MAX)?, self.list.current_size().0.into(), - ); - - if let Ok(commits) = commits { - self.list.items().set_items(want_min, commits); - }; + ) } else { - let commits = sync::get_commits_info( + sync::get_commits_info( CWD, - &self - .git_log - .get_slice(self.prev_log_count, usize::MAX)?, + &self.git_log.get_slice(want_min, SLICE_SIZE)?, self.list.current_size().0.into(), - ); + ) + }; - if let Ok(mut commits) = commits { - if let Some(filter_string) = &self.filter_string { - let filtered_commits = commits - .drain(..) - .filter(|commit_info| { - return commit_info - .id - .get_short_string() - .contains(filter_string) - || commit_info - .message - .contains(filter_string) - || commit_info - .author - .contains(filter_string); - }) - .collect::>(); - self.filter_count += filtered_commits.len(); - self.list.items().extend(filtered_commits); - let total_count = self.filter_count; - self.list.set_total_count(total_count); - } - }; - self.prev_log_count = self.git_log.count()?; - } + if let Ok(commits) = commits { + self.list.items().set_items(want_min, commits); + }; Ok(()) } @@ -219,18 +192,24 @@ impl Revlog { } pub fn filter(&mut self, filter_by: String) { - self.filter_count = 0; if filter_by == "" { - self.filter_string = None; - self.list.clear(); + self.is_filtering = false; + self.has_all_commits = false; + self.list.set_filter(None); + self.list.update_total_count(); } else { - self.filter_string = Some(filter_by); - self.list.clear(); - } - if let Err(e) = self.update() { - self.queue.borrow_mut().push_back( - InternalEvent::ShowErrorMsg(e.to_string()), - ); + self.is_filtering = true; + self.list.set_filter(Some(filter_by)); + // Don't get all the commits again if already have them, + // depening on repo could be expensive to constantly update + if !self.has_all_commits { + if let Err(e) = self.update() { + self.queue.borrow_mut().push_back( + InternalEvent::ShowErrorMsg(e.to_string()), + ); + } + self.has_all_commits = true; + } } } } diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index d6782c61bb..63d285e65a 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -48,7 +48,7 @@ impl StashList { let commits = sync::get_commits_info(CWD, stashes.as_slice(), 100)?; - self.list.set_total_count(commits.len()); + self.list.update_total_count(); self.list.items().set_items(0, commits); } From db48ec5d5c1561070514da5e45829e9c1b2ea72b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 13:42:14 +0000 Subject: [PATCH 026/118] Implement async filterer for commits --- asyncgit/src/revlog.rs | 12 +- asyncgit/src/sync/commits_info.rs | 2 +- src/components/commitlist.rs | 17 +- src/components/mod.rs | 1 + src/components/utils/async_commit_filter.rs | 228 ++++++++++++++++++++ src/components/utils/mod.rs | 1 + src/tabs/revlog.rs | 53 +++-- src/tabs/stashlist.rs | 2 +- 8 files changed, 273 insertions(+), 43 deletions(-) create mode 100644 src/components/utils/async_commit_filter.rs diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index c29ed6ad10..921000b372 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -27,6 +27,7 @@ pub enum FetchStatus { } /// +#[derive(Clone)] pub struct AsyncLog { current: Arc>>, sender: Sender, @@ -34,7 +35,7 @@ pub struct AsyncLog { background: Arc, } -static LIMIT_COUNT: usize = 3000; +static LIMIT_COUNT: usize = 5; static SLEEP_FOREGROUND: Duration = Duration::from_millis(2); static SLEEP_BACKGROUND: Duration = Duration::from_millis(1000); @@ -97,11 +98,8 @@ impl AsyncLog { Ok(false) } - /// None for amount means fetch the default - pub fn fetch( - &mut self, - amount: Option, - ) -> Result { + /// + pub fn fetch(&mut self) -> Result { self.background.store(false, Ordering::Relaxed); if self.is_pending() { @@ -128,7 +126,7 @@ impl AsyncLog { arc_current, arc_background, &sender, - amount.unwrap_or(LIMIT_COUNT), + LIMIT_COUNT, ) .expect("failed to fetch"); diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs index 5d3ad8209c..c2d2661935 100644 --- a/asyncgit/src/sync/commits_info.rs +++ b/asyncgit/src/sync/commits_info.rs @@ -43,7 +43,7 @@ impl From for CommitId { } /// -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CommitInfo { /// pub message: String, diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 352992b340..c3b3d5d850 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -86,21 +86,8 @@ impl CommitList { } /// - pub fn update_total_count(&mut self) { - self.count_total = self - .items - .iter() - .filter(|log_entry| { - if let Some(filter_string) = &self.filter_string { - return log_entry - .hash_short - .contains(filter_string) - || log_entry.msg.contains(filter_string) - || log_entry.author.contains(filter_string); - } - true - }) - .count(); + pub fn update_total_count(&mut self, count: usize) { + self.count_total = count; self.selection = cmp::min(self.selection, self.selection_max()); } diff --git a/src/components/mod.rs b/src/components/mod.rs index 21511d38f3..fb8520e84f 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -41,6 +41,7 @@ pub use select_branch::SelectBranchComponent; pub use stashmsg::StashMsgComponent; pub use tag_commit::TagCommitComponent; pub use textinput::{InputType, TextInputComponent}; +pub use utils::async_commit_filter; pub use utils::filetree::FileTreeItemKind; use crate::ui::style::Theme; diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs new file mode 100644 index 0000000000..98009a1c69 --- /dev/null +++ b/src/components/utils/async_commit_filter.rs @@ -0,0 +1,228 @@ +use anyhow::Result; +use asyncgit::{ + sync::{self, CommitInfo}, + AsyncLog, CWD, +}; +use bitflags::bitflags; +use crossbeam_channel::{Sender, TryRecvError}; +use std::{ + sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, Mutex, + }, + thread, + time::Duration, +}; + +const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(2); +const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = + Duration::from_millis(5); +const SLICE_SIZE: usize = 1200; + +bitflags! { + pub struct FilterBy: u32 { + const SHA = 0b0000_0001; + const AUTHOR = 0b0000_0010; + const MESSAGE = 0b0000_0100; + } +} + +pub struct AsyncCommitFilterer { + git_log: AsyncLog, + filter_string: String, + filter_by: FilterBy, + filtered_commits: Arc>>, + filter_count: Arc, + filter_finished: Arc, + filter_thread_sender: Option>, + message_length_limit: usize, +} + +impl AsyncCommitFilterer { + pub fn new( + git_log: AsyncLog, + message_length_limit: usize, + ) -> Self { + Self { + filter_string: "".to_string(), + filter_by: FilterBy::empty(), + git_log: git_log, + filtered_commits: Arc::new(Mutex::new(Vec::new())), + filter_count: Arc::new(AtomicUsize::new(0)), + filter_finished: Arc::new(AtomicBool::new(false)), + filter_thread_sender: None, + message_length_limit, + } + } + + pub fn clear( + &mut self, + ) -> Result< + (), + std::sync::PoisonError< + std::sync::MutexGuard>, + >, + > { + self.filtered_commits.lock()?.clear(); + Ok(()) + } + + pub fn filter( + vec_commit_info: &mut Vec, + filter_string: &String, + filter_by: FilterBy, + ) -> Vec { + vec_commit_info + .drain(..) + .filter(|ci| { + if filter_by.contains(FilterBy::SHA) { + if filter_string.contains(&ci.id.to_string()) { + return true; + } + } + if filter_by.contains(FilterBy::AUTHOR) { + if filter_string.contains(&ci.author) { + return true; + } + } + if filter_by.contains(FilterBy::MESSAGE) { + if filter_string.contains(&ci.message) { + return true; + } + } + false + }) + .collect::>() + } + + pub fn start_filter( + &mut self, + filter_string: String, + filter_by: FilterBy, + ) -> Result<()> { + self.clear().expect("Can't fail unless app crashes"); + self.filter_string = filter_string.clone(); + self.filter_by = filter_by.clone(); + + let filtered_commits = Arc::clone(&self.filtered_commits); + let filter_count = Arc::clone(&self.filter_count); + let async_log = self.git_log.clone(); + let filter_finished = Arc::clone(&self.filter_finished); + let message_length_limit = self.message_length_limit; + + let (tx, rx) = crossbeam_channel::unbounded(); + + self.filter_thread_sender = Some(tx); + + thread::spawn(move || { + let mut cur_index: usize = 0; + loop { + match rx.try_recv() { + Ok(_) | Err(TryRecvError::Disconnected) => { + break; + } + Err(TryRecvError::Empty) => { + // Get the git_log and start filtering through it + match async_log + .get_slice(cur_index, SLICE_SIZE) + { + Ok(ids) => match sync::get_commits_info( + CWD, + &ids, + message_length_limit, + ) { + Ok(mut v) => { + if v.len() == 1 + && async_log.is_pending() + { + // Assume finished if log not pending and only 1 commit + filter_finished.store( + true, + Ordering::Relaxed, + ); + break; + } + + let mut filtered = Self::filter( + &mut v, + &filter_string, + filter_by, + ); + filter_count.fetch_add( + filtered.len(), + Ordering::Relaxed, + ); + match filtered_commits.lock() { + Ok(mut fc) => { + fc.append(&mut filtered); + drop(fc); + cur_index += SLICE_SIZE; + thread::sleep( + FILTER_SLEEP_DURATION, + ); + } + Err(_) => { + // Failed to lock `filtered_commits` + thread::sleep( + FILTER_SLEEP_DURATION_FAILED_LOCK, + ); + } + } + } + Err(_) => { + // Failed to get commit info + thread::sleep( + FILTER_SLEEP_DURATION_FAILED_LOCK, + ); + } + }, + Err(_) => { + // Failed to get slice + thread::sleep( + FILTER_SLEEP_DURATION_FAILED_LOCK, + ); + } + } + } + } + } + }); + Ok(()) + } + + /// Stop the filter, is is possible to restart from this stage by calling restart + pub fn stop_filter(&self) -> Result<(), ()> { + if let Some(sender) = &self.filter_thread_sender { + return sender.send(true).map_err(|_| ()); + } + Err(()) + } + + /// Use if the next item to be filtered is a substring of the previous item. + /// This then only searches through the previous list + //pub fn continue_filter(&mut self, _s: String) -> Result<()> { + // Ok(()) + //} + + pub fn get_filter_items( + &mut self, + start: usize, + amount: usize, + ) -> Result< + Vec, + std::sync::PoisonError< + std::sync::MutexGuard>, + >, + > { + let fc = self.filtered_commits.lock()?; + let len = fc.len(); + let min = start.min(len); + let max = min + amount; + let max = max.min(len); + Ok(fc[min..max].to_vec()) + } + + pub fn count(&self) -> usize { + self.filter_count.load(Ordering::Relaxed) + } +} diff --git a/src/components/utils/mod.rs b/src/components/utils/mod.rs index a3fe5652c9..81fc380716 100644 --- a/src/components/utils/mod.rs +++ b/src/components/utils/mod.rs @@ -1,5 +1,6 @@ use chrono::{DateTime, Local, NaiveDateTime, Utc}; +pub mod async_commit_filter; pub mod filetree; pub mod logitems; pub mod statustree; diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 85081449ac..bbfcffac73 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -1,5 +1,6 @@ use crate::{ components::{ + async_commit_filter::{AsyncCommitFilterer, FilterBy}, visibility_blocking, CommandBlocking, CommandInfo, CommitDetailsComponent, CommitList, Component, DrawableComponent, FindCommitComponent, @@ -32,6 +33,7 @@ pub struct Revlog { commit_details: CommitDetailsComponent, list: CommitList, find_commit: FindCommitComponent, + async_filter: AsyncCommitFilterer, git_log: AsyncLog, git_tags: AsyncTags, queue: Queue, @@ -39,7 +41,6 @@ pub struct Revlog { branch_name: cached::BranchName, key_config: SharedKeyConfig, is_filtering: bool, - has_all_commits: bool, } impl Revlog { @@ -50,6 +51,7 @@ impl Revlog { theme: SharedTheme, key_config: SharedKeyConfig, ) -> Self { + let log = AsyncLog::new(sender); Self { queue: queue.clone(), commit_details: CommitDetailsComponent::new( @@ -68,13 +70,13 @@ impl Revlog { theme, key_config.clone(), ), - git_log: AsyncLog::new(sender), + async_filter: AsyncCommitFilterer::new(log.clone(), 10), + git_log: log, git_tags: AsyncTags::new(sender), visible: false, branch_name: cached::BranchName::new(CWD), key_config, is_filtering: false, - has_all_commits: false, } } @@ -88,15 +90,15 @@ impl Revlog { /// pub fn update(&mut self) -> Result<()> { if self.visible { - let log_changed = if self.is_filtering { - // If filtering should get all commits - self.git_log.fetch(Some(usize::MAX))? - == FetchStatus::Started + let mut log_changed = false; + if self.is_filtering { + self.list + .update_total_count(self.async_filter.count()); } else { - self.git_log.fetch(None)? == FetchStatus::Started - }; - - self.list.update_total_count(); + log_changed = + self.git_log.fetch()? == FetchStatus::Started; + self.list.update_total_count(self.git_log.count()?); + } let selection = self.list.selection(); let selection_max = self.list.selection_max(); @@ -149,19 +151,19 @@ impl Revlog { let want_min = self.list.selection().saturating_sub(SLICE_SIZE / 2); - // If filtering get all commits let commits = if self.is_filtering { - sync::get_commits_info( - CWD, - &self.git_log.get_slice(0, usize::MAX)?, - self.list.current_size().0.into(), - ) + self.async_filter + .get_filter_items(want_min, SLICE_SIZE) + .map_err(|_| { + anyhow::anyhow!("Failed to get filtered items") + }) } else { sync::get_commits_info( CWD, &self.git_log.get_slice(want_min, SLICE_SIZE)?, self.list.current_size().0.into(), ) + .map_err(|e| anyhow::anyhow!(e.to_string())) }; if let Ok(commits) = commits { @@ -192,11 +194,24 @@ impl Revlog { } pub fn filter(&mut self, filter_by: String) { + if filter_by == "" { + self.async_filter + .start_filter(filter_by, FilterBy::all()) + .expect("TODO: REMOVE EXPECT"); + self.is_filtering = false; + } else { + self.async_filter.stop_filter(); + self.is_filtering = true; + } + + /* if filter_by == "" { self.is_filtering = false; self.has_all_commits = false; self.list.set_filter(None); - self.list.update_total_count(); + self.list.update_total_count( + self.git_log.count().expect("Some"), + ); } else { self.is_filtering = true; self.list.set_filter(Some(filter_by)); @@ -210,7 +225,7 @@ impl Revlog { } self.has_all_commits = true; } - } + }*/ } } diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index 63d285e65a..d1016c0cf5 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -48,7 +48,7 @@ impl StashList { let commits = sync::get_commits_info(CWD, stashes.as_slice(), 100)?; - self.list.update_total_count(); + self.list.update_total_count(commits.len()); self.list.items().set_items(0, commits); } From 7c9fc66696a61f3e9e111de2403d3696b1d985f3 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 13:43:06 +0000 Subject: [PATCH 027/118] Remove filter code from commitlist --- src/components/commitlist.rs | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index c3b3d5d850..d8b44cb17a 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -39,7 +39,6 @@ pub struct CommitList { scroll_top: Cell, theme: SharedTheme, key_config: SharedKeyConfig, - filter_string: Option, } impl CommitList { @@ -61,7 +60,6 @@ impl CommitList { theme, key_config, title: String::from(title), - filter_string: None, } } @@ -116,29 +114,11 @@ impl CommitList { self.tags = Some(tags); } - /// - pub fn set_filter(&mut self, filter_string: Option) { - self.filter_string = filter_string; - } - /// pub fn selected_entry(&self) -> Option<&LogEntry> { - self.items - .iter() - .filter(|log_entry| { - if let Some(filter_string) = &self.filter_string { - return log_entry - .hash_short - .contains(filter_string) - || log_entry.msg.contains(filter_string) - || log_entry.author.contains(filter_string); - } - true - }) - .nth( - self.selection - .saturating_sub(self.items.index_offset()), - ) + self.items.iter().nth( + self.selection.saturating_sub(self.items.index_offset()), + ) } pub fn copy_entry_hash(&self) -> Result<()> { @@ -281,16 +261,6 @@ impl CommitList { for (idx, e) in self .items .iter() - .filter(|log_entry| { - if let Some(filter_string) = &self.filter_string { - return log_entry - .hash_short - .contains(filter_string) - || log_entry.msg.contains(filter_string) - || log_entry.author.contains(filter_string); - } - true - }) .skip(self.scroll_top.get()) .take(height) .enumerate() From 9d3985dce469a0b06a6f404cd3eff83b578eebc3 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 13:51:42 +0000 Subject: [PATCH 028/118] Shutdown filter thread before starting another filter --- src/components/utils/async_commit_filter.rs | 7 +++++++ src/tabs/revlog.rs | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 98009a1c69..5f438fc3ad 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -103,6 +103,13 @@ impl AsyncCommitFilterer { self.clear().expect("Can't fail unless app crashes"); self.filter_string = filter_string.clone(); self.filter_by = filter_by.clone(); + if let Some(sender) = &self.filter_thread_sender { + return sender.send(true).map_err(|_| { + anyhow::anyhow!( + "Could not send shutdown to filter thread" + ) + }); + } let filtered_commits = Arc::clone(&self.filtered_commits); let filter_count = Arc::clone(&self.filter_count); diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index bbfcffac73..4137cd3755 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -200,7 +200,9 @@ impl Revlog { .expect("TODO: REMOVE EXPECT"); self.is_filtering = false; } else { - self.async_filter.stop_filter(); + self.async_filter.stop_filter().expect( + "TODO: Could not stop filter, it's out of control!!!", + ); self.is_filtering = true; } From 47df534dc2253964e843dcba200dde995e033509 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 15:03:26 +0000 Subject: [PATCH 029/118] Working async commit filter --- src/components/utils/async_commit_filter.rs | 41 ++++++++++++++++----- src/tabs/revlog.rs | 30 +++++++++------ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 5f438fc3ad..d5578a33e1 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -1,7 +1,7 @@ use anyhow::Result; use asyncgit::{ sync::{self, CommitInfo}, - AsyncLog, CWD, + AsyncLog, AsyncNotification, CWD, }; use bitflags::bitflags; use crossbeam_channel::{Sender, TryRecvError}; @@ -27,6 +27,12 @@ bitflags! { } } +#[derive(PartialEq)] +pub enum FilterStatus { + Filtering, + Finished, +} + pub struct AsyncCommitFilterer { git_log: AsyncLog, filter_string: String, @@ -35,12 +41,14 @@ pub struct AsyncCommitFilterer { filter_count: Arc, filter_finished: Arc, filter_thread_sender: Option>, + sender: Sender, message_length_limit: usize, } impl AsyncCommitFilterer { pub fn new( git_log: AsyncLog, + sender: &Sender, message_length_limit: usize, ) -> Self { Self { @@ -51,6 +59,7 @@ impl AsyncCommitFilterer { filter_count: Arc::new(AtomicUsize::new(0)), filter_finished: Arc::new(AtomicBool::new(false)), filter_thread_sender: None, + sender: sender.clone(), message_length_limit, } } @@ -76,17 +85,17 @@ impl AsyncCommitFilterer { .drain(..) .filter(|ci| { if filter_by.contains(FilterBy::SHA) { - if filter_string.contains(&ci.id.to_string()) { + if ci.id.to_string().contains(filter_string) { return true; } } if filter_by.contains(FilterBy::AUTHOR) { - if filter_string.contains(&ci.author) { + if ci.author.contains(filter_string) { return true; } } if filter_by.contains(FilterBy::MESSAGE) { - if filter_string.contains(&ci.message) { + if ci.message.contains(filter_string) { return true; } } @@ -103,13 +112,15 @@ impl AsyncCommitFilterer { self.clear().expect("Can't fail unless app crashes"); self.filter_string = filter_string.clone(); self.filter_by = filter_by.clone(); - if let Some(sender) = &self.filter_thread_sender { + self.filter_count.store(0, Ordering::Relaxed); + self.stop_filter().expect("Can't fail"); + /*if let Some(sender) = &self.filter_thread_sender { return sender.send(true).map_err(|_| { anyhow::anyhow!( "Could not send shutdown to filter thread" ) }); - } + }*/ let filtered_commits = Arc::clone(&self.filtered_commits); let filter_count = Arc::clone(&self.filter_count); @@ -120,6 +131,7 @@ impl AsyncCommitFilterer { let (tx, rx) = crossbeam_channel::unbounded(); self.filter_thread_sender = Some(tx); + let async_app_sender = self.sender.clone(); thread::spawn(move || { let mut cur_index: usize = 0; @@ -140,7 +152,7 @@ impl AsyncCommitFilterer { ) { Ok(mut v) => { if v.len() == 1 - && async_log.is_pending() + && !async_log.is_pending() { // Assume finished if log not pending and only 1 commit filter_finished.store( @@ -164,6 +176,7 @@ impl AsyncCommitFilterer { fc.append(&mut filtered); drop(fc); cur_index += SLICE_SIZE; + async_app_sender.send(AsyncNotification::Log).expect("error sending"); thread::sleep( FILTER_SLEEP_DURATION, ); @@ -200,9 +213,11 @@ impl AsyncCommitFilterer { /// Stop the filter, is is possible to restart from this stage by calling restart pub fn stop_filter(&self) -> Result<(), ()> { if let Some(sender) = &self.filter_thread_sender { - return sender.send(true).map_err(|_| ()); + match sender.try_send(true) { + Ok(_) | Err(_) => {} + }; } - Err(()) + Ok(()) } /// Use if the next item to be filtered is a substring of the previous item. @@ -232,4 +247,12 @@ impl AsyncCommitFilterer { pub fn count(&self) -> usize { self.filter_count.load(Ordering::Relaxed) } + + pub fn fetch(&self) -> FilterStatus { + if self.filter_finished.load(Ordering::Relaxed) { + FilterStatus::Finished + } else { + FilterStatus::Filtering + } + } } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 4137cd3755..76ac8c3815 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -1,6 +1,8 @@ use crate::{ components::{ - async_commit_filter::{AsyncCommitFilterer, FilterBy}, + async_commit_filter::{ + AsyncCommitFilterer, FilterBy, FilterStatus, + }, visibility_blocking, CommandBlocking, CommandInfo, CommitDetailsComponent, CommitList, Component, DrawableComponent, FindCommitComponent, @@ -70,7 +72,11 @@ impl Revlog { theme, key_config.clone(), ), - async_filter: AsyncCommitFilterer::new(log.clone(), 10), + async_filter: AsyncCommitFilterer::new( + log.clone(), + sender, + 10, + ), git_log: log, git_tags: AsyncTags::new(sender), visible: false, @@ -90,15 +96,14 @@ impl Revlog { /// pub fn update(&mut self) -> Result<()> { if self.visible { - let mut log_changed = false; - if self.is_filtering { + let log_changed = if self.is_filtering { self.list .update_total_count(self.async_filter.count()); + self.async_filter.fetch() == FilterStatus::Filtering } else { - log_changed = - self.git_log.fetch()? == FetchStatus::Started; self.list.update_total_count(self.git_log.count()?); - } + self.git_log.fetch()? == FetchStatus::Started + }; let selection = self.list.selection(); let selection_max = self.list.selection_max(); @@ -195,16 +200,17 @@ impl Revlog { pub fn filter(&mut self, filter_by: String) { if filter_by == "" { - self.async_filter - .start_filter(filter_by, FilterBy::all()) - .expect("TODO: REMOVE EXPECT"); - self.is_filtering = false; - } else { self.async_filter.stop_filter().expect( "TODO: Could not stop filter, it's out of control!!!", ); + self.is_filtering = false; + } else { + self.async_filter + .start_filter(filter_by, FilterBy::all()) + .expect("TODO: REMOVE EXPECT"); self.is_filtering = true; } + self.update(); /* if filter_by == "" { From b2d2f8253a2b69020d78a64a09c804c71a28934d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 16:37:23 +0000 Subject: [PATCH 030/118] Fix async filter thread terminating upon finishing search --- src/components/utils/async_commit_filter.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index d5578a33e1..7239c0c8fb 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -14,9 +14,9 @@ use std::{ time::Duration, }; -const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(2); +const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(5); const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = - Duration::from_millis(5); + Duration::from_millis(10); const SLICE_SIZE: usize = 1200; bitflags! { @@ -151,10 +151,10 @@ impl AsyncCommitFilterer { message_length_limit, ) { Ok(mut v) => { - if v.len() == 1 + if v.len() <= 1 && !async_log.is_pending() { - // Assume finished if log not pending and only 1 commit + // Assume finished if log not pending and either 0 or 1 commit filter_finished.store( true, Ordering::Relaxed, From 70ca71a49402e597a0718ab0b9ced02e24312ef3 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 17:39:04 +0000 Subject: [PATCH 031/118] Set commit length when retrieving filtered commits --- asyncgit/src/sync/commits_info.rs | 3 +- asyncgit/src/sync/mod.rs | 4 ++- src/components/utils/async_commit_filter.rs | 37 ++++++++++----------- src/tabs/revlog.rs | 15 ++++----- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs index c2d2661935..d94378e9d5 100644 --- a/asyncgit/src/sync/commits_info.rs +++ b/asyncgit/src/sync/commits_info.rs @@ -107,7 +107,8 @@ pub fn get_message( } #[inline] -fn limit_str(s: &str, limit: usize) -> &str { +/// +pub fn limit_str(s: &str, limit: usize) -> &str { if let Some(first) = s.lines().next() { let mut limit = limit.min(first.len()); while !first.is_char_boundary(limit) { diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 618039b86a..f6553cb09a 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -32,7 +32,9 @@ pub use commit_details::{ get_commit_details, CommitDetails, CommitMessage, }; pub use commit_files::get_commit_files; -pub use commits_info::{get_commits_info, CommitId, CommitInfo}; +pub use commits_info::{ + get_commits_info, limit_str, CommitId, CommitInfo, +}; pub use diff::get_diff_commit; pub use hooks::{ hooks_commit_msg, hooks_post_commit, hooks_pre_commit, HookResult, diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 7239c0c8fb..ba6e8febe7 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -1,6 +1,6 @@ use anyhow::Result; use asyncgit::{ - sync::{self, CommitInfo}, + sync::{self, limit_str, CommitInfo}, AsyncLog, AsyncNotification, CWD, }; use bitflags::bitflags; @@ -14,7 +14,7 @@ use std::{ time::Duration, }; -const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(5); +const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(500); const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = Duration::from_millis(10); const SLICE_SIZE: usize = 1200; @@ -42,14 +42,12 @@ pub struct AsyncCommitFilterer { filter_finished: Arc, filter_thread_sender: Option>, sender: Sender, - message_length_limit: usize, } impl AsyncCommitFilterer { pub fn new( git_log: AsyncLog, sender: &Sender, - message_length_limit: usize, ) -> Self { Self { filter_string: "".to_string(), @@ -60,7 +58,6 @@ impl AsyncCommitFilterer { filter_finished: Arc::new(AtomicBool::new(false)), filter_thread_sender: None, sender: sender.clone(), - message_length_limit, } } @@ -109,24 +106,18 @@ impl AsyncCommitFilterer { filter_string: String, filter_by: FilterBy, ) -> Result<()> { + self.stop_filter(); + self.clear().expect("Can't fail unless app crashes"); self.filter_string = filter_string.clone(); self.filter_by = filter_by.clone(); self.filter_count.store(0, Ordering::Relaxed); - self.stop_filter().expect("Can't fail"); - /*if let Some(sender) = &self.filter_thread_sender { - return sender.send(true).map_err(|_| { - anyhow::anyhow!( - "Could not send shutdown to filter thread" - ) - }); - }*/ let filtered_commits = Arc::clone(&self.filtered_commits); let filter_count = Arc::clone(&self.filter_count); let async_log = self.git_log.clone(); let filter_finished = Arc::clone(&self.filter_finished); - let message_length_limit = self.message_length_limit; + filter_finished.store(false, Ordering::Relaxed); let (tx, rx) = crossbeam_channel::unbounded(); @@ -148,7 +139,7 @@ impl AsyncCommitFilterer { Ok(ids) => match sync::get_commits_info( CWD, &ids, - message_length_limit, + usize::MAX, ) { Ok(mut v) => { if v.len() <= 1 @@ -210,14 +201,16 @@ impl AsyncCommitFilterer { Ok(()) } - /// Stop the filter, is is possible to restart from this stage by calling restart - pub fn stop_filter(&self) -> Result<(), ()> { + /// Stop the filter if one was running, otherwise does nothing. + /// Is it possible to restart from this stage by calling restart + pub fn stop_filter(&self) { + // Any error this gives can be safely ignored, + // it will send if reciever exists, otherwise does nothing if let Some(sender) = &self.filter_thread_sender { match sender.try_send(true) { Ok(_) | Err(_) => {} }; } - Ok(()) } /// Use if the next item to be filtered is a substring of the previous item. @@ -230,6 +223,7 @@ impl AsyncCommitFilterer { &mut self, start: usize, amount: usize, + message_length_limit: usize, ) -> Result< Vec, std::sync::PoisonError< @@ -241,7 +235,12 @@ impl AsyncCommitFilterer { let min = start.min(len); let max = min + amount; let max = max.min(len); - Ok(fc[min..max].to_vec()) + let mut commits_requested = fc[min..max].to_vec(); + for c in &mut commits_requested { + c.message = limit_str(&c.message, message_length_limit) + .to_string(); + } + Ok(commits_requested) } pub fn count(&self) -> usize { diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 76ac8c3815..a8f89fd25f 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -75,7 +75,6 @@ impl Revlog { async_filter: AsyncCommitFilterer::new( log.clone(), sender, - 10, ), git_log: log, git_tags: AsyncTags::new(sender), @@ -158,10 +157,12 @@ impl Revlog { let commits = if self.is_filtering { self.async_filter - .get_filter_items(want_min, SLICE_SIZE) - .map_err(|_| { - anyhow::anyhow!("Failed to get filtered items") - }) + .get_filter_items( + want_min, + SLICE_SIZE, + self.list.current_size().0.into(), + ) + .map_err(|e| anyhow::anyhow!(e.to_string())) } else { sync::get_commits_info( CWD, @@ -200,9 +201,7 @@ impl Revlog { pub fn filter(&mut self, filter_by: String) { if filter_by == "" { - self.async_filter.stop_filter().expect( - "TODO: Could not stop filter, it's out of control!!!", - ); + self.async_filter.stop_filter(); self.is_filtering = false; } else { self.async_filter From cd938f6060052bc985602e1dffce1c1e81eee8b8 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 17:47:06 +0000 Subject: [PATCH 032/118] Finish filter if 0 recieved rather than <=1 --- src/components/utils/async_commit_filter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index ba6e8febe7..0e69cfac19 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -142,10 +142,10 @@ impl AsyncCommitFilterer { usize::MAX, ) { Ok(mut v) => { - if v.len() <= 1 + if v.len() == 0 && !async_log.is_pending() { - // Assume finished if log not pending and either 0 or 1 commit + // Assume finished if log not pending and 0 recieved filter_finished.store( true, Ordering::Relaxed, From fdf2fad14def295c194a38a40e1f8c15e5eedcbd Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 18:03:59 +0000 Subject: [PATCH 033/118] Make search case insensitive --- src/components/utils/async_commit_filter.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 0e69cfac19..5e89dbcf10 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -82,17 +82,30 @@ impl AsyncCommitFilterer { .drain(..) .filter(|ci| { if filter_by.contains(FilterBy::SHA) { - if ci.id.to_string().contains(filter_string) { + if ci + .id + .to_string() + .to_lowercase() + .contains(&filter_string.to_lowercase()) + { return true; } } if filter_by.contains(FilterBy::AUTHOR) { - if ci.author.contains(filter_string) { + if ci + .author + .to_lowercase() + .contains(&filter_string.to_lowercase()) + { return true; } } if filter_by.contains(FilterBy::MESSAGE) { - if ci.message.contains(filter_string) { + if ci + .message + .to_lowercase() + .contains(&filter_string.to_lowercase()) + { return true; } } From d3abde62f7986538d7c48a28e0df574f92225953 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 18:07:46 +0000 Subject: [PATCH 034/118] Fix clippy warning --- src/app.rs | 2 +- src/tabs/revlog.rs | 37 ++++--------------------------------- 2 files changed, 5 insertions(+), 34 deletions(-) diff --git a/src/app.rs b/src/app.rs index 6af3b36e34..2b20802fbb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -538,7 +538,7 @@ impl App { flags.insert(NeedsUpdate::ALL) } InternalEvent::FilterLog(string_to_fliter_by) => { - self.revlog.filter(string_to_fliter_by) + self.revlog.filter(string_to_fliter_by)? } }; diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index a8f89fd25f..d292e93c62 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -199,7 +199,7 @@ impl Revlog { }) } - pub fn filter(&mut self, filter_by: String) { + pub fn filter(&mut self, filter_by: String) -> Result<()> { if filter_by == "" { self.async_filter.stop_filter(); self.is_filtering = false; @@ -209,30 +209,7 @@ impl Revlog { .expect("TODO: REMOVE EXPECT"); self.is_filtering = true; } - self.update(); - - /* - if filter_by == "" { - self.is_filtering = false; - self.has_all_commits = false; - self.list.set_filter(None); - self.list.update_total_count( - self.git_log.count().expect("Some"), - ); - } else { - self.is_filtering = true; - self.list.set_filter(Some(filter_by)); - // Don't get all the commits again if already have them, - // depening on repo could be expensive to constantly update - if !self.has_all_commits { - if let Err(e) = self.update() { - self.queue.borrow_mut().push_back( - InternalEvent::ShowErrorMsg(e.to_string()), - ); - } - self.has_all_commits = true; - } - }*/ + self.update() } } @@ -356,15 +333,9 @@ impl Component for Revlog { self.find_commit.focus(true); return Ok(true); } else if k == self.key_config.exit_popup { - self.filter("".to_string()); + self.filter("".to_string())?; self.find_commit.clear_input(); - if let Err(e) = self.update() { - self.queue.borrow_mut().push_back( - InternalEvent::ShowErrorMsg( - e.to_string(), - ), - ); - } + self.update()?; } } } From ebbe26e8d6c308b22d38b27a56e1e0523cd75f80 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 23:11:20 +0000 Subject: [PATCH 035/118] Add a filter function using : in search to filter by sha, author or message --- src/tabs/revlog.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index d292e93c62..68f11a36f1 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -199,13 +199,44 @@ impl Revlog { }) } + fn get_what_to_filter_by( + filter_by_str: String, + ) -> (String, FilterBy) { + if let Some(':') = filter_by_str.chars().nth(0) { + let mut to_filter_by = FilterBy::empty(); + let mut split_str = + filter_by_str.split(' ').collect::>(); + if split_str.len() == 1 { + split_str.push(""); + } + let first = split_str[0]; + if first.contains('s') { + to_filter_by = to_filter_by | FilterBy::SHA; + } + if first.contains('a') { + to_filter_by = to_filter_by | FilterBy::AUTHOR; + } + if first.contains('m') { + to_filter_by = to_filter_by | FilterBy::MESSAGE; + } + if to_filter_by.is_empty() { + to_filter_by = FilterBy::all(); + } + + return (split_str[1..].join(" "), to_filter_by); + } + (filter_by_str, FilterBy::all()) + } + pub fn filter(&mut self, filter_by: String) -> Result<()> { if filter_by == "" { self.async_filter.stop_filter(); self.is_filtering = false; } else { + let (search_string_processed, to_filter_by) = + Self::get_what_to_filter_by(filter_by); self.async_filter - .start_filter(filter_by, FilterBy::all()) + .start_filter(search_string_processed, to_filter_by) .expect("TODO: REMOVE EXPECT"); self.is_filtering = true; } From 16da8a0f78ffc4405529bd5c9fa1f623704e4130 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 23:20:22 +0000 Subject: [PATCH 036/118] Remove expect --- src/tabs/revlog.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 68f11a36f1..eb56b323ed 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -237,7 +237,7 @@ impl Revlog { Self::get_what_to_filter_by(filter_by); self.async_filter .start_filter(search_string_processed, to_filter_by) - .expect("TODO: REMOVE EXPECT"); + .map_err(|e| anyhow::anyhow!(e.to_string()))?; self.is_filtering = true; } self.update() From 0114cbf215c1b05d5cb287ff4883f3717771cf1b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 23:21:58 +0000 Subject: [PATCH 037/118] Change async filter sleep durations --- src/components/utils/async_commit_filter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 5e89dbcf10..0c1aaf04f0 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -14,9 +14,9 @@ use std::{ time::Duration, }; -const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(500); +const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(20); const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = - Duration::from_millis(10); + Duration::from_millis(500); const SLICE_SIZE: usize = 1200; bitflags! { From b4cf79889e7ee87c5d34e8b32224b43395452a65 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Tue, 9 Feb 2021 23:52:21 +0000 Subject: [PATCH 038/118] Trim whitespace --- src/app.rs | 2 +- src/tabs/revlog.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/app.rs b/src/app.rs index 2b20802fbb..54abba9d4d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -538,7 +538,7 @@ impl App { flags.insert(NeedsUpdate::ALL) } InternalEvent::FilterLog(string_to_fliter_by) => { - self.revlog.filter(string_to_fliter_by)? + self.revlog.filter(&string_to_fliter_by)? } }; diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index eb56b323ed..6d8ee76412 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -223,18 +223,22 @@ impl Revlog { to_filter_by = FilterBy::all(); } - return (split_str[1..].join(" "), to_filter_by); + return ( + split_str[1..].join(" ").trim().to_string(), + to_filter_by, + ); } (filter_by_str, FilterBy::all()) } - pub fn filter(&mut self, filter_by: String) -> Result<()> { + pub fn filter(&mut self, filter_by: &str) -> Result<()> { + let trimmed_string = filter_by.trim().to_string(); if filter_by == "" { self.async_filter.stop_filter(); self.is_filtering = false; } else { let (search_string_processed, to_filter_by) = - Self::get_what_to_filter_by(filter_by); + Self::get_what_to_filter_by(trimmed_string); self.async_filter .start_filter(search_string_processed, to_filter_by) .map_err(|e| anyhow::anyhow!(e.to_string()))?; @@ -364,7 +368,7 @@ impl Component for Revlog { self.find_commit.focus(true); return Ok(true); } else if k == self.key_config.exit_popup { - self.filter("".to_string())?; + self.filter("")?; self.find_commit.clear_input(); self.update()?; } From 0be484058e95c5acea046b4d342105b4ddca340f Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 10 Feb 2021 00:33:06 +0000 Subject: [PATCH 039/118] Allow union filtering with || --- src/components/utils/async_commit_filter.rs | 91 ++++++++++----------- src/tabs/revlog.rs | 66 ++++++++------- 2 files changed, 81 insertions(+), 76 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 0c1aaf04f0..3207d9d306 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -35,8 +35,7 @@ pub enum FilterStatus { pub struct AsyncCommitFilterer { git_log: AsyncLog, - filter_string: String, - filter_by: FilterBy, + filter_strings: Vec<(String, FilterBy)>, filtered_commits: Arc>>, filter_count: Arc, filter_finished: Arc, @@ -50,8 +49,7 @@ impl AsyncCommitFilterer { sender: &Sender, ) -> Self { Self { - filter_string: "".to_string(), - filter_by: FilterBy::empty(), + filter_strings: Vec::new(), git_log: git_log, filtered_commits: Arc::new(Mutex::new(Vec::new())), filter_count: Arc::new(AtomicUsize::new(0)), @@ -74,56 +72,58 @@ impl AsyncCommitFilterer { } pub fn filter( - vec_commit_info: &mut Vec, - filter_string: &String, - filter_by: FilterBy, + vec_commit_info: Vec, + filter_strings: &Vec<(String, FilterBy)>, ) -> Vec { - vec_commit_info - .drain(..) - .filter(|ci| { - if filter_by.contains(FilterBy::SHA) { - if ci - .id - .to_string() - .to_lowercase() - .contains(&filter_string.to_lowercase()) - { - return true; + let mut commit_infos = vec_commit_info; + for (s, filter) in filter_strings { + commit_infos = commit_infos + .drain(..) + .filter(|ci| { + if filter.contains(FilterBy::SHA) { + if ci + .id + .to_string() + .to_lowercase() + .contains(&s.to_lowercase()) + { + return true; + } } - } - if filter_by.contains(FilterBy::AUTHOR) { - if ci - .author - .to_lowercase() - .contains(&filter_string.to_lowercase()) - { - return true; + + if filter.contains(FilterBy::AUTHOR) { + if ci + .author + .to_lowercase() + .contains(&s.to_lowercase()) + { + return true; + } } - } - if filter_by.contains(FilterBy::MESSAGE) { - if ci - .message - .to_lowercase() - .contains(&filter_string.to_lowercase()) - { - return true; + if filter.contains(FilterBy::MESSAGE) { + if ci + .message + .to_lowercase() + .contains(&s.to_lowercase()) + { + return true; + } } - } - false - }) - .collect::>() + false + }) + .collect::>() + } + return commit_infos; } pub fn start_filter( &mut self, - filter_string: String, - filter_by: FilterBy, + filter_strings: Vec<(String, FilterBy)>, ) -> Result<()> { self.stop_filter(); self.clear().expect("Can't fail unless app crashes"); - self.filter_string = filter_string.clone(); - self.filter_by = filter_by.clone(); + self.filter_strings = filter_strings.clone(); self.filter_count.store(0, Ordering::Relaxed); let filtered_commits = Arc::clone(&self.filtered_commits); @@ -154,7 +154,7 @@ impl AsyncCommitFilterer { &ids, usize::MAX, ) { - Ok(mut v) => { + Ok(v) => { if v.len() == 0 && !async_log.is_pending() { @@ -167,9 +167,8 @@ impl AsyncCommitFilterer { } let mut filtered = Self::filter( - &mut v, - &filter_string, - filter_by, + v, + &filter_strings, ); filter_count.fetch_add( filtered.len(), diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 6d8ee76412..cd111b4a82 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -200,35 +200,41 @@ impl Revlog { } fn get_what_to_filter_by( - filter_by_str: String, - ) -> (String, FilterBy) { - if let Some(':') = filter_by_str.chars().nth(0) { - let mut to_filter_by = FilterBy::empty(); - let mut split_str = - filter_by_str.split(' ').collect::>(); - if split_str.len() == 1 { - split_str.push(""); - } - let first = split_str[0]; - if first.contains('s') { - to_filter_by = to_filter_by | FilterBy::SHA; - } - if first.contains('a') { - to_filter_by = to_filter_by | FilterBy::AUTHOR; - } - if first.contains('m') { - to_filter_by = to_filter_by | FilterBy::MESSAGE; - } - if to_filter_by.is_empty() { - to_filter_by = FilterBy::all(); - } + filter_by_str: &str, + ) -> Vec<(String, FilterBy)> { + let mut search_vec = vec![]; + for split_sub in filter_by_str.split("||") { + if let Some(':') = split_sub.chars().nth(0) { + let mut to_filter_by = FilterBy::empty(); + let mut split_str = + split_sub.split(' ').collect::>(); + if split_str.len() == 1 { + split_str.push(""); + } + let first = split_str[0]; + if first.contains('s') { + to_filter_by = to_filter_by | FilterBy::SHA; + } + if first.contains('a') { + to_filter_by = to_filter_by | FilterBy::AUTHOR; + } + if first.contains('m') { + to_filter_by = to_filter_by | FilterBy::MESSAGE; + } + if to_filter_by.is_empty() { + to_filter_by = FilterBy::all(); + } - return ( - split_str[1..].join(" ").trim().to_string(), - to_filter_by, - ); + search_vec.push(( + split_str[1..].join(" ").trim().to_string(), + to_filter_by, + )); + } else { + search_vec + .push((split_sub.to_string(), FilterBy::all())) + } } - (filter_by_str, FilterBy::all()) + return search_vec; } pub fn filter(&mut self, filter_by: &str) -> Result<()> { @@ -237,10 +243,10 @@ impl Revlog { self.async_filter.stop_filter(); self.is_filtering = false; } else { - let (search_string_processed, to_filter_by) = - Self::get_what_to_filter_by(trimmed_string); + let filter_strings = + Self::get_what_to_filter_by(&trimmed_string); self.async_filter - .start_filter(search_string_processed, to_filter_by) + .start_filter(filter_strings) .map_err(|e| anyhow::anyhow!(e.to_string()))?; self.is_filtering = true; } From 05c734c45ceee088f8183d18e4c8ad8d21965a68 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 10 Feb 2021 18:33:09 +0000 Subject: [PATCH 040/118] Use rayon core for thread and add work pending --- src/components/utils/async_commit_filter.rs | 13 +++++++++---- src/tabs/revlog.rs | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 3207d9d306..fc46f24ee8 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -14,10 +14,10 @@ use std::{ time::Duration, }; -const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(20); +const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(2000); const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = Duration::from_millis(500); -const SLICE_SIZE: usize = 1200; +const SLICE_SIZE: usize = 100; bitflags! { pub struct FilterBy: u32 { @@ -53,12 +53,16 @@ impl AsyncCommitFilterer { git_log: git_log, filtered_commits: Arc::new(Mutex::new(Vec::new())), filter_count: Arc::new(AtomicUsize::new(0)), - filter_finished: Arc::new(AtomicBool::new(false)), + filter_finished: Arc::new(AtomicBool::new(true)), filter_thread_sender: None, sender: sender.clone(), } } + pub fn is_pending(&self) -> bool { + self.fetch() == FilterStatus::Filtering + } + pub fn clear( &mut self, ) -> Result< @@ -137,7 +141,7 @@ impl AsyncCommitFilterer { self.filter_thread_sender = Some(tx); let async_app_sender = self.sender.clone(); - thread::spawn(move || { + rayon_core::spawn(move || { let mut cur_index: usize = 0; loop { match rx.try_recv() { @@ -223,6 +227,7 @@ impl AsyncCommitFilterer { Ok(_) | Err(_) => {} }; } + self.filter_finished.store(true, Ordering::Relaxed); } /// Use if the next item to be filtered is a substring of the previous item. diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index cd111b4a82..f5d19dcc84 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -89,6 +89,7 @@ impl Revlog { pub fn any_work_pending(&self) -> bool { self.git_log.is_pending() || self.git_tags.is_pending() + || self.async_filter.is_pending() || self.commit_details.any_work_pending() } From bf0d6ea86e2658571b7c04ed07e864d0812c4b46 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 10 Feb 2021 19:18:40 +0000 Subject: [PATCH 041/118] Fix sleep timings so filter runs quicker --- src/components/utils/async_commit_filter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index fc46f24ee8..41520171e4 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -14,10 +14,10 @@ use std::{ time::Duration, }; -const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(2000); +const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(10); const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = Duration::from_millis(500); -const SLICE_SIZE: usize = 100; +const SLICE_SIZE: usize = 1200; bitflags! { pub struct FilterBy: u32 { From 933d4c53e0a6fb7df704d92633246428c8d3e055 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 10 Feb 2021 19:35:01 +0000 Subject: [PATCH 042/118] Store state locally for quicker return if no work pending --- src/components/utils/async_commit_filter.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 41520171e4..9f515c10b1 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -39,6 +39,7 @@ pub struct AsyncCommitFilterer { filtered_commits: Arc>>, filter_count: Arc, filter_finished: Arc, + filter_finished_local: bool, filter_thread_sender: Option>, sender: Sender, } @@ -54,13 +55,20 @@ impl AsyncCommitFilterer { filtered_commits: Arc::new(Mutex::new(Vec::new())), filter_count: Arc::new(AtomicUsize::new(0)), filter_finished: Arc::new(AtomicBool::new(true)), + filter_finished_local: true, filter_thread_sender: None, sender: sender.clone(), } } - pub fn is_pending(&self) -> bool { - self.fetch() == FilterStatus::Filtering + pub fn is_pending(&mut self) -> bool { + self.filter_finished_local + || if self.fetch() == FilterStatus::Finished { + self.filter_finished_local = true; + true + } else { + false + } } pub fn clear( @@ -219,7 +227,7 @@ impl AsyncCommitFilterer { /// Stop the filter if one was running, otherwise does nothing. /// Is it possible to restart from this stage by calling restart - pub fn stop_filter(&self) { + pub fn stop_filter(&mut self) { // Any error this gives can be safely ignored, // it will send if reciever exists, otherwise does nothing if let Some(sender) = &self.filter_thread_sender { @@ -227,6 +235,7 @@ impl AsyncCommitFilterer { Ok(_) | Err(_) => {} }; } + self.filter_finished_local = false; self.filter_finished.store(true, Ordering::Relaxed); } From 9578e8d23843a1e4eb8845878fb67eaa28fefb07 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Wed, 10 Feb 2021 20:04:20 +0000 Subject: [PATCH 043/118] Use refcell to fix is_pending --- src/components/utils/async_commit_filter.rs | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 9f515c10b1..45081d155d 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -6,6 +6,7 @@ use asyncgit::{ use bitflags::bitflags; use crossbeam_channel::{Sender, TryRecvError}; use std::{ + cell::RefCell, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, Mutex, @@ -39,7 +40,7 @@ pub struct AsyncCommitFilterer { filtered_commits: Arc>>, filter_count: Arc, filter_finished: Arc, - filter_finished_local: bool, + is_pending_local: RefCell, filter_thread_sender: Option>, sender: Sender, } @@ -55,20 +56,20 @@ impl AsyncCommitFilterer { filtered_commits: Arc::new(Mutex::new(Vec::new())), filter_count: Arc::new(AtomicUsize::new(0)), filter_finished: Arc::new(AtomicBool::new(true)), - filter_finished_local: true, + is_pending_local: RefCell::new(true), filter_thread_sender: None, sender: sender.clone(), } } - pub fn is_pending(&mut self) -> bool { - self.filter_finished_local - || if self.fetch() == FilterStatus::Finished { - self.filter_finished_local = true; - true - } else { - false - } + pub fn is_pending(&self) -> bool { + let mut b = self.is_pending_local.borrow_mut(); + if *b { + *b = self.fetch() == FilterStatus::Filtering; + *b + } else { + false + } } pub fn clear( @@ -227,7 +228,7 @@ impl AsyncCommitFilterer { /// Stop the filter if one was running, otherwise does nothing. /// Is it possible to restart from this stage by calling restart - pub fn stop_filter(&mut self) { + pub fn stop_filter(&self) { // Any error this gives can be safely ignored, // it will send if reciever exists, otherwise does nothing if let Some(sender) = &self.filter_thread_sender { @@ -235,7 +236,7 @@ impl AsyncCommitFilterer { Ok(_) | Err(_) => {} }; } - self.filter_finished_local = false; + self.is_pending_local.replace(true); self.filter_finished.store(true, Ordering::Relaxed); } From e9eeb53be2723b715457a3842fd18df3f8a06520 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 10:55:15 +0000 Subject: [PATCH 044/118] Allow &&(intersection) and ||(union) filtering --- src/components/utils/async_commit_filter.rs | 102 ++++++++++++-------- src/tabs/revlog.rs | 67 +++++++------ 2 files changed, 99 insertions(+), 70 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 45081d155d..96feb516a3 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -36,7 +36,7 @@ pub enum FilterStatus { pub struct AsyncCommitFilterer { git_log: AsyncLog, - filter_strings: Vec<(String, FilterBy)>, + filter_strings: Vec>, filtered_commits: Arc>>, filter_count: Arc, filter_finished: Arc, @@ -85,53 +85,73 @@ impl AsyncCommitFilterer { } pub fn filter( - vec_commit_info: Vec, - filter_strings: &Vec<(String, FilterBy)>, + mut vec_commit_info: Vec, + filter_strings: &Vec>, ) -> Vec { - let mut commit_infos = vec_commit_info; - for (s, filter) in filter_strings { - commit_infos = commit_infos - .drain(..) - .filter(|ci| { - if filter.contains(FilterBy::SHA) { - if ci - .id - .to_string() - .to_lowercase() - .contains(&s.to_lowercase()) - { - return true; - } - } - - if filter.contains(FilterBy::AUTHOR) { - if ci - .author - .to_lowercase() - .contains(&s.to_lowercase()) - { - return true; - } + vec_commit_info + .drain(..) + .filter(|commit| { + for to_and in filter_strings { + let mut is_and = true; + for (s, filter) in to_and { + let b = false + || if filter.contains(FilterBy::SHA) { + if commit + .id + .to_string() + .to_lowercase() + .contains(&s.to_lowercase()) + { + true + } else { + false + } + } else { + false + } + || if filter.contains(FilterBy::AUTHOR) { + if commit + .author + .to_lowercase() + .contains(&s.to_lowercase()) + { + true + } else { + false + } + } else { + false + } + || if filter.contains(FilterBy::MESSAGE) { + if commit + .message + .to_lowercase() + .contains(&s.to_lowercase()) + { + true + //filtered_commits.push(commit.clone()); + //break; + } else { + false + } + } else { + false + }; + is_and = is_and && b; } - if filter.contains(FilterBy::MESSAGE) { - if ci - .message - .to_lowercase() - .contains(&s.to_lowercase()) - { - return true; - } + if is_and { + return true; } - false - }) - .collect::>() - } - return commit_infos; + } + false + }) + .collect() + //return filtered_commits; } pub fn start_filter( &mut self, - filter_strings: Vec<(String, FilterBy)>, + filter_strings: Vec>, ) -> Result<()> { self.stop_filter(); diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index f5d19dcc84..f87884f433 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -202,38 +202,47 @@ impl Revlog { fn get_what_to_filter_by( filter_by_str: &str, - ) -> Vec<(String, FilterBy)> { + ) -> Vec> { let mut search_vec = vec![]; - for split_sub in filter_by_str.split("||") { - if let Some(':') = split_sub.chars().nth(0) { - let mut to_filter_by = FilterBy::empty(); - let mut split_str = - split_sub.split(' ').collect::>(); - if split_str.len() == 1 { - split_str.push(""); - } - let first = split_str[0]; - if first.contains('s') { - to_filter_by = to_filter_by | FilterBy::SHA; - } - if first.contains('a') { - to_filter_by = to_filter_by | FilterBy::AUTHOR; - } - if first.contains('m') { - to_filter_by = to_filter_by | FilterBy::MESSAGE; - } - if to_filter_by.is_empty() { - to_filter_by = FilterBy::all(); - } + let mut and_vec = Vec::new(); + for or in filter_by_str.split("||") { + for split_sub in or.split("&&") { + if let Some(':') = split_sub.chars().nth(0) { + let mut to_filter_by = FilterBy::empty(); + let mut split_str = + split_sub.split(' ').collect::>(); + if split_str.len() == 1 { + split_str.push(""); + } + let first = split_str[0]; + if first.contains('s') { + to_filter_by = to_filter_by | FilterBy::SHA; + } + if first.contains('a') { + to_filter_by = + to_filter_by | FilterBy::AUTHOR; + } + if first.contains('m') { + to_filter_by = + to_filter_by | FilterBy::MESSAGE; + } + if to_filter_by.is_empty() { + to_filter_by = FilterBy::all(); + } - search_vec.push(( - split_str[1..].join(" ").trim().to_string(), - to_filter_by, - )); - } else { - search_vec - .push((split_sub.to_string(), FilterBy::all())) + and_vec.push(( + split_str[1..].join(" ").trim().to_string(), + to_filter_by, + )); + } else { + and_vec.push(( + split_sub.to_string(), + FilterBy::all(), + )) + } } + search_vec.push(and_vec.clone()); + and_vec.clear(); } return search_vec; } From f442c33e4c8ad0ed426bbec2f733f27ac530887d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 17:59:59 +0000 Subject: [PATCH 045/118] Use mutexes to prevent several threads filtering at once --- src/components/utils/async_commit_filter.rs | 118 +++++++++++++------- 1 file changed, 77 insertions(+), 41 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 96feb516a3..bf3eb0ab5b 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -42,6 +42,9 @@ pub struct AsyncCommitFilterer { filter_finished: Arc, is_pending_local: RefCell, filter_thread_sender: Option>, + //filter_thread_receiver: Receiver, + filter_thread_running: Arc, + filter_thread_mutex: Arc>, sender: Sender, } @@ -50,14 +53,18 @@ impl AsyncCommitFilterer { git_log: AsyncLog, sender: &Sender, ) -> Self { + //let (tx, rx) = crossbeam_channel::unbounded(); Self { filter_strings: Vec::new(), git_log: git_log, filtered_commits: Arc::new(Mutex::new(Vec::new())), filter_count: Arc::new(AtomicUsize::new(0)), - filter_finished: Arc::new(AtomicBool::new(true)), + filter_finished: Arc::new(AtomicBool::new(false)), + filter_thread_mutex: Arc::new(Mutex::new(())), is_pending_local: RefCell::new(true), filter_thread_sender: None, + //filter_thread_receiver: rx.clone(), + filter_thread_running: Arc::new(AtomicBool::new(true)), sender: sender.clone(), } } @@ -149,6 +156,7 @@ impl AsyncCommitFilterer { //return filtered_commits; } + #[allow(clippy::too_many_lines)] pub fn start_filter( &mut self, filter_strings: Vec>, @@ -163,18 +171,34 @@ impl AsyncCommitFilterer { let filter_count = Arc::clone(&self.filter_count); let async_log = self.git_log.clone(); let filter_finished = Arc::clone(&self.filter_finished); - filter_finished.store(false, Ordering::Relaxed); + + let filter_thread_running = + Arc::clone(&self.filter_thread_running); let (tx, rx) = crossbeam_channel::unbounded(); + //let rx = self.filter_thread_receiver.clone(); self.filter_thread_sender = Some(tx); let async_app_sender = self.sender.clone(); + let prev_thread_mutex = Arc::clone(&self.filter_thread_mutex); + // let filter_thread_mutex = Arc::new(Mutex::new(true)); + self.filter_thread_mutex = Arc::new(Mutex::new(())); + + let cur_thread_mutex = Arc::clone(&self.filter_thread_mutex); + // Arc::clone(&self.filter_thread_mutex); + rayon_core::spawn(move || { + // Only 1 thread can filter at a time + let _c = cur_thread_mutex.lock().expect("cant fail"); + let _p = prev_thread_mutex.lock().expect("cant fail"); + filter_finished.store(false, Ordering::Relaxed); let mut cur_index: usize = 0; loop { match rx.try_recv() { Ok(_) | Err(TryRecvError::Disconnected) => { + filter_thread_running + .store(false, Ordering::Relaxed); break; } Err(TryRecvError::Empty) => { @@ -182,56 +206,68 @@ impl AsyncCommitFilterer { match async_log .get_slice(cur_index, SLICE_SIZE) { - Ok(ids) => match sync::get_commits_info( - CWD, - &ids, - usize::MAX, - ) { - Ok(v) => { - if v.len() == 0 - && !async_log.is_pending() - { - // Assume finished if log not pending and 0 recieved - filter_finished.store( - true, + Ok(ids) => { + match sync::get_commits_info( + CWD, + &ids, + usize::MAX, + ) { + Ok(v) => { + if v.len() == 0 + && !async_log.is_pending() + { + // Assume finished if log not pending and 0 recieved + filter_finished.store( + true, + Ordering::Relaxed, + ); + filter_thread_running + .store( + false, + Ordering::Relaxed, + ); + break; + } + + let mut filtered = + Self::filter( + v, + &filter_strings, + ); + filter_count.fetch_add( + filtered.len(), Ordering::Relaxed, ); - break; - } - - let mut filtered = Self::filter( - v, - &filter_strings, - ); - filter_count.fetch_add( - filtered.len(), - Ordering::Relaxed, - ); - match filtered_commits.lock() { - Ok(mut fc) => { - fc.append(&mut filtered); - drop(fc); - cur_index += SLICE_SIZE; - async_app_sender.send(AsyncNotification::Log).expect("error sending"); - thread::sleep( + match filtered_commits.lock() + { + Ok(mut fc) => { + fc.append( + &mut filtered, + ); + drop(fc); + cur_index += + SLICE_SIZE; + async_app_sender.send(AsyncNotification::Log).expect("error sending"); + thread::sleep( FILTER_SLEEP_DURATION, ); - } - Err(_) => { - // Failed to lock `filtered_commits` - thread::sleep( + } + Err(_) => { + // Failed to lock `filtered_commits` + thread::sleep( FILTER_SLEEP_DURATION_FAILED_LOCK, ); + } } } - } - Err(_) => { - // Failed to get commit info - thread::sleep( + Err(_) => { + // Failed to get commit info + thread::sleep( FILTER_SLEEP_DURATION_FAILED_LOCK, ); + } } - }, + } Err(_) => { // Failed to get slice thread::sleep( From ae01732680616326a60886d85fd6ee8dfaf6f01e Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 18:09:44 +0000 Subject: [PATCH 046/118] Clear filtered commits when starting a new search in thread --- src/components/utils/async_commit_filter.rs | 126 ++++++++++---------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index bf3eb0ab5b..8c56892d29 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -193,89 +193,85 @@ impl AsyncCommitFilterer { let _c = cur_thread_mutex.lock().expect("cant fail"); let _p = prev_thread_mutex.lock().expect("cant fail"); filter_finished.store(false, Ordering::Relaxed); + filter_count.store(0, Ordering::Relaxed); + filtered_commits.lock().expect("Cant fail").clear(); let mut cur_index: usize = 0; loop { - match rx.try_recv() { - Ok(_) | Err(TryRecvError::Disconnected) => { - filter_thread_running - .store(false, Ordering::Relaxed); - break; - } - Err(TryRecvError::Empty) => { - // Get the git_log and start filtering through it - match async_log - .get_slice(cur_index, SLICE_SIZE) - { - Ok(ids) => { - match sync::get_commits_info( - CWD, - &ids, - usize::MAX, - ) { - Ok(v) => { - if v.len() == 0 - && !async_log.is_pending() - { - // Assume finished if log not pending and 0 recieved - filter_finished.store( - true, - Ordering::Relaxed, - ); - filter_thread_running - .store( - false, - Ordering::Relaxed, - ); - break; - } - - let mut filtered = - Self::filter( - v, - &filter_strings, - ); - filter_count.fetch_add( - filtered.len(), + // Get the git_log and start filtering through it + match async_log.get_slice(cur_index, SLICE_SIZE) { + Ok(ids) => { + match sync::get_commits_info( + CWD, + &ids, + usize::MAX, + ) { + Ok(v) => { + match rx.try_recv() { + Ok(_) + | Err( + TryRecvError::Disconnected, + ) => { + filter_thread_running.store( + false, Ordering::Relaxed, ); - match filtered_commits.lock() - { - Ok(mut fc) => { - fc.append( - &mut filtered, - ); - drop(fc); - cur_index += - SLICE_SIZE; - async_app_sender.send(AsyncNotification::Log).expect("error sending"); - thread::sleep( - FILTER_SLEEP_DURATION, - ); - } - Err(_) => { - // Failed to lock `filtered_commits` - thread::sleep( - FILTER_SLEEP_DURATION_FAILED_LOCK, + break; + } + _ => {} + } + if v.len() == 0 + && !async_log.is_pending() + { + // Assume finished if log not pending and 0 recieved + filter_finished.store( + true, + Ordering::Relaxed, + ); + filter_thread_running.store( + false, + Ordering::Relaxed, + ); + break; + } + + let mut filtered = + Self::filter(v, &filter_strings); + filter_count.fetch_add( + filtered.len(), + Ordering::Relaxed, ); - } - } + match filtered_commits.lock() { + Ok(mut fc) => { + fc.append(&mut filtered); + drop(fc); + cur_index += SLICE_SIZE; + async_app_sender.send(AsyncNotification::Log).expect("error sending"); + thread::sleep( + FILTER_SLEEP_DURATION, + ); } Err(_) => { - // Failed to get commit info + // Failed to lock `filtered_commits` thread::sleep( - FILTER_SLEEP_DURATION_FAILED_LOCK, - ); + FILTER_SLEEP_DURATION_FAILED_LOCK, + ); } } } Err(_) => { - // Failed to get slice + // Failed to get commit info thread::sleep( FILTER_SLEEP_DURATION_FAILED_LOCK, ); } } } + Err(_) => { + // Failed to get slice + thread::sleep( + FILTER_SLEEP_DURATION_FAILED_LOCK, + ); + } } } }); From c5b469b89fb16dd56265ab49506e64bc5366c237 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 18:51:04 +0000 Subject: [PATCH 047/118] Use parking log mutex --- Cargo.lock | 1 + Cargo.toml | 1 + src/components/utils/async_commit_filter.rs | 55 +++++++-------------- 3 files changed, 20 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85afe425f2..572ae4e7d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,6 +370,7 @@ dependencies = [ "dirs-next", "itertools", "log", + "parking_lot", "pprof", "rayon-core", "ron", diff --git a/Cargo.toml b/Cargo.toml index a0cf5b1a0b..ba73d09900 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ serde = "1.0" anyhow = "1.0.38" unicode-width = "0.1" textwrap = "0.13" +parking_lot = "0.11" [target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies] which = "4.0" diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 8c56892d29..f0137e4070 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -9,12 +9,14 @@ use std::{ cell::RefCell, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, - Arc, Mutex, + Arc, }, thread, time::Duration, }; +use parking_lot::Mutex; + const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(10); const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = Duration::from_millis(500); @@ -79,15 +81,8 @@ impl AsyncCommitFilterer { } } - pub fn clear( - &mut self, - ) -> Result< - (), - std::sync::PoisonError< - std::sync::MutexGuard>, - >, - > { - self.filtered_commits.lock()?.clear(); + pub fn clear(&mut self) -> Result<()> { + self.filtered_commits.lock().clear(); Ok(()) } @@ -190,11 +185,11 @@ impl AsyncCommitFilterer { rayon_core::spawn(move || { // Only 1 thread can filter at a time - let _c = cur_thread_mutex.lock().expect("cant fail"); - let _p = prev_thread_mutex.lock().expect("cant fail"); + let _c = cur_thread_mutex.lock(); + let _p = prev_thread_mutex.lock(); filter_finished.store(false, Ordering::Relaxed); filter_count.store(0, Ordering::Relaxed); - filtered_commits.lock().expect("Cant fail").clear(); + filtered_commits.lock().clear(); let mut cur_index: usize = 0; loop { // Get the git_log and start filtering through it @@ -240,23 +235,14 @@ impl AsyncCommitFilterer { filtered.len(), Ordering::Relaxed, ); - match filtered_commits.lock() { - Ok(mut fc) => { - fc.append(&mut filtered); - drop(fc); - cur_index += SLICE_SIZE; - async_app_sender.send(AsyncNotification::Log).expect("error sending"); - thread::sleep( - FILTER_SLEEP_DURATION, - ); - } - Err(_) => { - // Failed to lock `filtered_commits` - thread::sleep( - FILTER_SLEEP_DURATION_FAILED_LOCK, - ); - } - } + let mut fc = filtered_commits.lock(); + fc.append(&mut filtered); + drop(fc); + cur_index += SLICE_SIZE; + async_app_sender + .send(AsyncNotification::Log) + .expect("error sending"); + thread::sleep(FILTER_SLEEP_DURATION); } Err(_) => { // Failed to get commit info @@ -303,13 +289,8 @@ impl AsyncCommitFilterer { start: usize, amount: usize, message_length_limit: usize, - ) -> Result< - Vec, - std::sync::PoisonError< - std::sync::MutexGuard>, - >, - > { - let fc = self.filtered_commits.lock()?; + ) -> Result> { + let fc = self.filtered_commits.lock(); let len = fc.len(); let min = start.min(len); let max = min + amount; From 8fc599d4544a5eddba4a08803a428860429afc53 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 18:51:34 +0000 Subject: [PATCH 048/118] Fix import order --- src/components/utils/async_commit_filter.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index f0137e4070..546946ea56 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -5,6 +5,7 @@ use asyncgit::{ }; use bitflags::bitflags; use crossbeam_channel::{Sender, TryRecvError}; +use parking_lot::Mutex; use std::{ cell::RefCell, sync::{ @@ -15,8 +16,6 @@ use std::{ time::Duration, }; -use parking_lot::Mutex; - const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(10); const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = Duration::from_millis(500); From 524ddcc616b37a131ee79871182a4d3076a730da Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 18:57:13 +0000 Subject: [PATCH 049/118] Fix spinner up before filter --- src/components/utils/async_commit_filter.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 546946ea56..8b4c05175f 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -62,7 +62,7 @@ impl AsyncCommitFilterer { filter_count: Arc::new(AtomicUsize::new(0)), filter_finished: Arc::new(AtomicBool::new(false)), filter_thread_mutex: Arc::new(Mutex::new(())), - is_pending_local: RefCell::new(true), + is_pending_local: RefCell::new(false), filter_thread_sender: None, //filter_thread_receiver: rx.clone(), filter_thread_running: Arc::new(AtomicBool::new(true)), @@ -181,6 +181,7 @@ impl AsyncCommitFilterer { let cur_thread_mutex = Arc::clone(&self.filter_thread_mutex); // Arc::clone(&self.filter_thread_mutex); + self.is_pending_local.replace(true); rayon_core::spawn(move || { // Only 1 thread can filter at a time @@ -273,7 +274,7 @@ impl AsyncCommitFilterer { Ok(_) | Err(_) => {} }; } - self.is_pending_local.replace(true); + self.is_pending_local.replace(false); self.filter_finished.store(true, Ordering::Relaxed); } From 06d6ade63b0e44040c84b953b1bf25ee0fdebdd6 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 18:58:57 +0000 Subject: [PATCH 050/118] Remove filter_thread_running --- src/components/utils/async_commit_filter.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 8b4c05175f..a71f9c1c77 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -44,7 +44,6 @@ pub struct AsyncCommitFilterer { is_pending_local: RefCell, filter_thread_sender: Option>, //filter_thread_receiver: Receiver, - filter_thread_running: Arc, filter_thread_mutex: Arc>, sender: Sender, } @@ -65,7 +64,6 @@ impl AsyncCommitFilterer { is_pending_local: RefCell::new(false), filter_thread_sender: None, //filter_thread_receiver: rx.clone(), - filter_thread_running: Arc::new(AtomicBool::new(true)), sender: sender.clone(), } } @@ -166,9 +164,6 @@ impl AsyncCommitFilterer { let async_log = self.git_log.clone(); let filter_finished = Arc::clone(&self.filter_finished); - let filter_thread_running = - Arc::clone(&self.filter_thread_running); - let (tx, rx) = crossbeam_channel::unbounded(); //let rx = self.filter_thread_receiver.clone(); @@ -206,10 +201,6 @@ impl AsyncCommitFilterer { | Err( TryRecvError::Disconnected, ) => { - filter_thread_running.store( - false, - Ordering::Relaxed, - ); break; } _ => {} @@ -222,10 +213,6 @@ impl AsyncCommitFilterer { true, Ordering::Relaxed, ); - filter_thread_running.store( - false, - Ordering::Relaxed, - ); break; } From 7ed2cda405a4777b081af47875ff2992031f36a2 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 18:59:56 +0000 Subject: [PATCH 051/118] Remove unneeded comments --- src/components/utils/async_commit_filter.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index a71f9c1c77..116e041faa 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -43,7 +43,6 @@ pub struct AsyncCommitFilterer { filter_finished: Arc, is_pending_local: RefCell, filter_thread_sender: Option>, - //filter_thread_receiver: Receiver, filter_thread_mutex: Arc>, sender: Sender, } @@ -53,7 +52,6 @@ impl AsyncCommitFilterer { git_log: AsyncLog, sender: &Sender, ) -> Self { - //let (tx, rx) = crossbeam_channel::unbounded(); Self { filter_strings: Vec::new(), git_log: git_log, @@ -63,7 +61,6 @@ impl AsyncCommitFilterer { filter_thread_mutex: Arc::new(Mutex::new(())), is_pending_local: RefCell::new(false), filter_thread_sender: None, - //filter_thread_receiver: rx.clone(), sender: sender.clone(), } } @@ -128,8 +125,6 @@ impl AsyncCommitFilterer { .contains(&s.to_lowercase()) { true - //filtered_commits.push(commit.clone()); - //break; } else { false } @@ -145,7 +140,6 @@ impl AsyncCommitFilterer { false }) .collect() - //return filtered_commits; } #[allow(clippy::too_many_lines)] @@ -165,17 +159,14 @@ impl AsyncCommitFilterer { let filter_finished = Arc::clone(&self.filter_finished); let (tx, rx) = crossbeam_channel::unbounded(); - //let rx = self.filter_thread_receiver.clone(); self.filter_thread_sender = Some(tx); let async_app_sender = self.sender.clone(); let prev_thread_mutex = Arc::clone(&self.filter_thread_mutex); - // let filter_thread_mutex = Arc::new(Mutex::new(true)); self.filter_thread_mutex = Arc::new(Mutex::new(())); let cur_thread_mutex = Arc::clone(&self.filter_thread_mutex); - // Arc::clone(&self.filter_thread_mutex); self.is_pending_local.replace(true); rayon_core::spawn(move || { From c444aaae77e0f9059c2f096bf13aedb7927a4ab7 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 19:08:12 +0000 Subject: [PATCH 052/118] Make filter function more succinct --- src/components/utils/async_commit_filter.rs | 57 +++++++-------------- 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 116e041faa..39683824f4 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -90,48 +90,27 @@ impl AsyncCommitFilterer { for to_and in filter_strings { let mut is_and = true; for (s, filter) in to_and { - let b = false - || if filter.contains(FilterBy::SHA) { - if commit + is_and = is_and + && ((filter.contains(FilterBy::SHA) + && commit .id .to_string() .to_lowercase() - .contains(&s.to_lowercase()) - { - true - } else { - false - } - } else { - false - } - || if filter.contains(FilterBy::AUTHOR) { - if commit - .author - .to_lowercase() - .contains(&s.to_lowercase()) - { - true - } else { - false - } - } else { - false - } - || if filter.contains(FilterBy::MESSAGE) { - if commit - .message - .to_lowercase() - .contains(&s.to_lowercase()) - { - true - } else { - false - } - } else { - false - }; - is_and = is_and && b; + .contains(&s.to_lowercase())) + || (filter + .contains(FilterBy::AUTHOR) + && commit + .author + .to_lowercase() + .contains( + &s.to_lowercase(), + )) + || filter + .contains(FilterBy::MESSAGE) + && commit + .message + .to_lowercase() + .contains(&s.to_lowercase())); } if is_and { return true; From 5bd747b08ebfdd9e1d467ea9c29f0921def530c9 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 19:10:22 +0000 Subject: [PATCH 053/118] Remove clippy allow too many lines --- src/components/utils/async_commit_filter.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 39683824f4..d8b80f9a00 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -121,7 +121,6 @@ impl AsyncCommitFilterer { .collect() } - #[allow(clippy::too_many_lines)] pub fn start_filter( &mut self, filter_strings: Vec>, From 8db4523219766bf09a586f5f0b197fe5338d6a8f Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 19:11:38 +0000 Subject: [PATCH 054/118] Fix brackets in filter function --- src/components/utils/async_commit_filter.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index d8b80f9a00..36e119281c 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -105,12 +105,14 @@ impl AsyncCommitFilterer { .contains( &s.to_lowercase(), )) - || filter + || (filter .contains(FilterBy::MESSAGE) && commit .message .to_lowercase() - .contains(&s.to_lowercase())); + .contains( + &s.to_lowercase(), + ))); } if is_and { return true; From a1cd02051c1af4deb02f20bc10c3d185d3d376c1 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 19:16:51 +0000 Subject: [PATCH 055/118] Remove reset values from start_filter in main thread --- src/components/utils/async_commit_filter.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 36e119281c..19533790c7 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -129,9 +129,7 @@ impl AsyncCommitFilterer { ) -> Result<()> { self.stop_filter(); - self.clear().expect("Can't fail unless app crashes"); self.filter_strings = filter_strings.clone(); - self.filter_count.store(0, Ordering::Relaxed); let filtered_commits = Arc::clone(&self.filtered_commits); let filter_count = Arc::clone(&self.filter_count); From 126431a516c365b09116fddcf196fa091e052edc Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 19:18:26 +0000 Subject: [PATCH 056/118] Remove clear function from async filter --- src/components/utils/async_commit_filter.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 19533790c7..16a1386125 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -75,11 +75,6 @@ impl AsyncCommitFilterer { } } - pub fn clear(&mut self) -> Result<()> { - self.filtered_commits.lock().clear(); - Ok(()) - } - pub fn filter( mut vec_commit_info: Vec, filter_strings: &Vec>, From 7a416de9ea04c6be8f1ea4457e18078f6749ab15 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 19:59:38 +0000 Subject: [PATCH 057/118] Move try_recv to before getting slice of commits --- src/components/utils/async_commit_filter.rs | 100 +++++++++++--------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 16a1386125..c235edd4f2 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -151,64 +151,70 @@ impl AsyncCommitFilterer { filtered_commits.lock().clear(); let mut cur_index: usize = 0; loop { - // Get the git_log and start filtering through it - match async_log.get_slice(cur_index, SLICE_SIZE) { - Ok(ids) => { - match sync::get_commits_info( - CWD, - &ids, - usize::MAX, - ) { - Ok(v) => { - match rx.try_recv() { - Ok(_) - | Err( - TryRecvError::Disconnected, - ) => { - break; - } - _ => {} - } - if v.len() == 0 - && !async_log.is_pending() - { - // Assume finished if log not pending and 0 recieved - filter_finished.store( - true, - Ordering::Relaxed, - ); - break; - } + match rx.try_recv() { + Ok(_) | Err(TryRecvError::Disconnected) => { + break; + } + _ => { + // Get the git_log and start filtering through it + match async_log + .get_slice(cur_index, SLICE_SIZE) + { + Ok(ids) => { + match sync::get_commits_info( + CWD, + &ids, + usize::MAX, + ) { + Ok(v) => { + if v.len() == 0 + && !async_log.is_pending() + { + // Assume finished if log not pending and 0 recieved + filter_finished.store( + true, + Ordering::Relaxed, + ); + break; + } - let mut filtered = - Self::filter(v, &filter_strings); - filter_count.fetch_add( - filtered.len(), - Ordering::Relaxed, - ); - let mut fc = filtered_commits.lock(); - fc.append(&mut filtered); - drop(fc); - cur_index += SLICE_SIZE; - async_app_sender + let mut filtered = + Self::filter( + v, + &filter_strings, + ); + filter_count.fetch_add( + filtered.len(), + Ordering::Relaxed, + ); + let mut fc = + filtered_commits.lock(); + fc.append(&mut filtered); + drop(fc); + cur_index += SLICE_SIZE; + async_app_sender .send(AsyncNotification::Log) .expect("error sending"); - thread::sleep(FILTER_SLEEP_DURATION); + thread::sleep( + FILTER_SLEEP_DURATION, + ); + } + Err(_) => { + // Failed to get commit info + thread::sleep( + FILTER_SLEEP_DURATION_FAILED_LOCK, + ); + } + } } Err(_) => { - // Failed to get commit info + // Failed to get slice thread::sleep( FILTER_SLEEP_DURATION_FAILED_LOCK, ); } } } - Err(_) => { - // Failed to get slice - thread::sleep( - FILTER_SLEEP_DURATION_FAILED_LOCK, - ); - } } } }); From d70e14991b0ff312e5ddee559104de34e02c74b4 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 11 Feb 2021 20:19:49 +0000 Subject: [PATCH 058/118] Add complement search --- src/components/utils/async_commit_filter.rs | 67 +++++++++++++++------ src/tabs/revlog.rs | 9 ++- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index c235edd4f2..db81f44d9d 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -26,6 +26,7 @@ bitflags! { const SHA = 0b0000_0001; const AUTHOR = 0b0000_0010; const MESSAGE = 0b0000_0100; + const NOT = 0b0000_1000; } } @@ -85,29 +86,55 @@ impl AsyncCommitFilterer { for to_and in filter_strings { let mut is_and = true; for (s, filter) in to_and { - is_and = is_and - && ((filter.contains(FilterBy::SHA) - && commit - .id - .to_string() - .to_lowercase() - .contains(&s.to_lowercase())) - || (filter - .contains(FilterBy::AUTHOR) - && commit - .author + is_and = if filter.contains(FilterBy::NOT) { + is_and + && ((filter.contains(FilterBy::SHA) + && !commit + .id + .to_string() .to_lowercase() - .contains( - &s.to_lowercase(), - )) - || (filter - .contains(FilterBy::MESSAGE) + .contains(&s.to_lowercase())) + || (filter + .contains(FilterBy::AUTHOR) + && !commit + .author + .to_lowercase() + .contains( + &s.to_lowercase(), + )) + || (filter + .contains(FilterBy::MESSAGE) + && !commit + .message + .to_lowercase() + .contains( + &s.to_lowercase(), + ))) + } else { + is_and + && ((filter.contains(FilterBy::SHA) && commit - .message + .id + .to_string() .to_lowercase() - .contains( - &s.to_lowercase(), - ))); + .contains(&s.to_lowercase())) + || (filter + .contains(FilterBy::AUTHOR) + && commit + .author + .to_lowercase() + .contains( + &s.to_lowercase(), + )) + || (filter + .contains(FilterBy::MESSAGE) + && commit + .message + .to_lowercase() + .contains( + &s.to_lowercase(), + ))) + } } if is_and { return true; diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index f87884f433..1b89193a07 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -226,9 +226,16 @@ impl Revlog { to_filter_by = to_filter_by | FilterBy::MESSAGE; } + if first.contains('!') { + to_filter_by = to_filter_by | FilterBy::NOT; + } + if to_filter_by.is_empty() { + to_filter_by = + FilterBy::all() & !FilterBy::NOT; + } else if to_filter_by == FilterBy::NOT { to_filter_by = FilterBy::all(); - } + }; and_vec.push(( split_str[1..].join(" ").trim().to_string(), From a2faf2984ba43e948bb2deb4b5fa998898ad8de1 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Fri, 12 Feb 2021 22:38:24 +0000 Subject: [PATCH 059/118] Support case sensitive filtering --- src/components/utils/async_commit_filter.rs | 102 +++++++++++++++----- src/tabs/revlog.rs | 15 ++- 2 files changed, 90 insertions(+), 27 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index db81f44d9d..5e89d864c8 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -27,6 +27,7 @@ bitflags! { const AUTHOR = 0b0000_0010; const MESSAGE = 0b0000_0100; const NOT = 0b0000_1000; + const CASE_SENSITIVE = 0b0001_0000; } } @@ -76,6 +77,7 @@ impl AsyncCommitFilterer { } } + #[allow(clippy::too_many_lines)] pub fn filter( mut vec_commit_info: Vec, filter_strings: &Vec>, @@ -86,54 +88,102 @@ impl AsyncCommitFilterer { for to_and in filter_strings { let mut is_and = true; for (s, filter) in to_and { - is_and = if filter.contains(FilterBy::NOT) { - is_and - && ((filter.contains(FilterBy::SHA) - && !commit - .id - .to_string() - .to_lowercase() - .contains(&s.to_lowercase())) - || (filter - .contains(FilterBy::AUTHOR) + if filter.contains(FilterBy::CASE_SENSITIVE) { + is_and = if filter.contains(FilterBy::NOT) + { + is_and + && ((filter + .contains(FilterBy::SHA) && !commit + .id + .to_string() + .contains(s)) + || (filter.contains( + FilterBy::AUTHOR, + ) && !commit .author + .contains(s)) + || (filter.contains( + FilterBy::MESSAGE, + ) && !commit + .message + .contains(s))) + } else { + is_and + && ((filter + .contains(FilterBy::SHA) + && commit + .id + .to_string() + .contains(s)) + || (filter.contains( + FilterBy::AUTHOR, + ) && commit + .author + .contains(s)) + || (filter.contains( + FilterBy::MESSAGE, + ) && commit + .message + .contains(s))) + } + } else { + is_and = if filter.contains(FilterBy::NOT) + { + is_and + && ((filter + .contains(FilterBy::SHA) + && !commit + .id + .to_string() .to_lowercase() .contains( &s.to_lowercase(), )) - || (filter - .contains(FilterBy::MESSAGE) - && !commit + || (filter.contains( + FilterBy::AUTHOR, + ) && !commit + .author + .to_lowercase() + .contains( + &s.to_lowercase(), + )) + || (filter.contains( + FilterBy::MESSAGE, + ) && !commit .message .to_lowercase() .contains( &s.to_lowercase(), ))) - } else { - is_and - && ((filter.contains(FilterBy::SHA) - && commit - .id - .to_string() - .to_lowercase() - .contains(&s.to_lowercase())) - || (filter - .contains(FilterBy::AUTHOR) + } else { + is_and + && ((filter + .contains(FilterBy::SHA) && commit + .id + .to_string() + .to_lowercase() + .contains( + &s.to_lowercase(), + )) + || (filter.contains( + FilterBy::AUTHOR, + ) && commit .author .to_lowercase() .contains( &s.to_lowercase(), )) - || (filter - .contains(FilterBy::MESSAGE) - && commit + || (filter.contains( + FilterBy::MESSAGE, + ) && commit .message .to_lowercase() .contains( &s.to_lowercase(), ))) + } } } if is_and { diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 1b89193a07..40d1094c2b 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -226,6 +226,10 @@ impl Revlog { to_filter_by = to_filter_by | FilterBy::MESSAGE; } + if first.contains('c') { + to_filter_by = + to_filter_by | FilterBy::CASE_SENSITIVE; + } if first.contains('!') { to_filter_by = to_filter_by | FilterBy::NOT; } @@ -233,8 +237,17 @@ impl Revlog { if to_filter_by.is_empty() { to_filter_by = FilterBy::all() & !FilterBy::NOT; + } else if to_filter_by + == FilterBy::CASE_SENSITIVE & FilterBy::NOT + { + FilterBy::all(); } else if to_filter_by == FilterBy::NOT { - to_filter_by = FilterBy::all(); + to_filter_by = + FilterBy::all() & !FilterBy::NOT; + } else if to_filter_by == FilterBy::CASE_SENSITIVE + { + to_filter_by = + FilterBy::all() & !FilterBy::NOT; }; and_vec.push(( From 571c40171ea97effec36561d03946b9c16052256 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Fri, 12 Feb 2021 22:51:15 +0000 Subject: [PATCH 060/118] Add comment to filter function explaining what input is expected --- src/components/utils/async_commit_filter.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 5e89d864c8..8658511f33 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -77,6 +77,13 @@ impl AsyncCommitFilterer { } } + /// `filter_strings` should be split by or them and, for example, + /// + /// A || B && C && D || E + /// + /// would be + /// + /// vec [vec![A], vec![B, C, D], vec![E]] #[allow(clippy::too_many_lines)] pub fn filter( mut vec_commit_info: Vec, From d5d24fb4a16e8b7a46b1d035864ae7a03d7e212c Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Fri, 12 Feb 2021 23:09:12 +0000 Subject: [PATCH 061/118] Fix clippy warnings and not revlog filter by --- src/components/find_commit.rs | 4 +- src/components/utils/async_commit_filter.rs | 6 +-- src/tabs/revlog.rs | 51 ++++++++++----------- 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs index cf766092e7..171ff004d4 100644 --- a/src/components/find_commit.rs +++ b/src/components/find_commit.rs @@ -63,7 +63,7 @@ impl Component for FindCommitComponent { } fn is_visible(&self) -> bool { - return self.visible; + self.visible } fn hide(&mut self) { @@ -79,7 +79,7 @@ impl Component for FindCommitComponent { } fn focused(&self) -> bool { - return self.is_focused; + self.is_focused } fn toggle_visible(&mut self) -> Result<()> { diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 8658511f33..d84bfec676 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -56,7 +56,7 @@ impl AsyncCommitFilterer { ) -> Self { Self { filter_strings: Vec::new(), - git_log: git_log, + git_log, filtered_commits: Arc::new(Mutex::new(Vec::new())), filter_count: Arc::new(AtomicUsize::new(0)), filter_finished: Arc::new(AtomicBool::new(false)), @@ -87,7 +87,7 @@ impl AsyncCommitFilterer { #[allow(clippy::too_many_lines)] pub fn filter( mut vec_commit_info: Vec, - filter_strings: &Vec>, + filter_strings: &[Vec<(String, FilterBy)>], ) -> Vec { vec_commit_info .drain(..) @@ -251,7 +251,7 @@ impl AsyncCommitFilterer { usize::MAX, ) { Ok(v) => { - if v.len() == 0 + if v.is_empty() && !async_log.is_pending() { // Assume finished if log not pending and 0 recieved diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 40d1094c2b..33d75d1d80 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -207,7 +207,7 @@ impl Revlog { let mut and_vec = Vec::new(); for or in filter_by_str.split("||") { for split_sub in or.split("&&") { - if let Some(':') = split_sub.chars().nth(0) { + if let Some(':') = split_sub.chars().next() { let mut to_filter_by = FilterBy::empty(); let mut split_str = split_sub.split(' ').collect::>(); @@ -216,22 +216,19 @@ impl Revlog { } let first = split_str[0]; if first.contains('s') { - to_filter_by = to_filter_by | FilterBy::SHA; + to_filter_by |= FilterBy::SHA; } if first.contains('a') { - to_filter_by = - to_filter_by | FilterBy::AUTHOR; + to_filter_by |= FilterBy::AUTHOR; } if first.contains('m') { - to_filter_by = - to_filter_by | FilterBy::MESSAGE; + to_filter_by |= FilterBy::MESSAGE; } if first.contains('c') { - to_filter_by = - to_filter_by | FilterBy::CASE_SENSITIVE; + to_filter_by |= FilterBy::CASE_SENSITIVE; } if first.contains('!') { - to_filter_by = to_filter_by | FilterBy::NOT; + to_filter_by |= FilterBy::NOT; } if to_filter_by.is_empty() { @@ -242,8 +239,8 @@ impl Revlog { { FilterBy::all(); } else if to_filter_by == FilterBy::NOT { - to_filter_by = - FilterBy::all() & !FilterBy::NOT; + to_filter_by = FilterBy::all() + & !FilterBy::CASE_SENSITIVE; } else if to_filter_by == FilterBy::CASE_SENSITIVE { to_filter_by = @@ -264,7 +261,7 @@ impl Revlog { search_vec.push(and_vec.clone()); and_vec.clear(); } - return search_vec; + search_vec } pub fn filter(&mut self, filter_by: &str) -> Result<()> { @@ -320,23 +317,21 @@ impl DrawableComponent for Revlog { self.list.draw(f, chunks[0])?; self.commit_details.draw(f, chunks[1])?; } + } else if self.find_commit.is_visible() { + let log_find_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(90), + Constraint::Percentage(20), + ] + .as_ref(), + ) + .split(area); + self.list.draw(f, log_find_chunks[0])?; + self.find_commit.draw(f, log_find_chunks[1])?; } else { - if self.find_commit.is_visible() { - let log_find_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage(90), - Constraint::Percentage(20), - ] - .as_ref(), - ) - .split(area); - self.list.draw(f, log_find_chunks[0])?; - self.find_commit.draw(f, log_find_chunks[1])?; - } else { - self.list.draw(f, area)?; - } + self.list.draw(f, area)?; } Ok(()) From 712fb82c7574f0786a17ecb7f84e1ef6b0617aee Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Fri, 12 Feb 2021 23:15:03 +0000 Subject: [PATCH 062/118] Fix filter if no leading : --- src/tabs/revlog.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 33d75d1d80..c310e60ba1 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -232,8 +232,9 @@ impl Revlog { } if to_filter_by.is_empty() { - to_filter_by = - FilterBy::all() & !FilterBy::NOT; + to_filter_by = FilterBy::all() + & !FilterBy::NOT + & !FilterBy::CASE_SENSITIVE; } else if to_filter_by == FilterBy::CASE_SENSITIVE & FilterBy::NOT { @@ -254,7 +255,9 @@ impl Revlog { } else { and_vec.push(( split_sub.to_string(), - FilterBy::all(), + FilterBy::all() + & !FilterBy::NOT + & !FilterBy::CASE_SENSITIVE, )) } } From 529cc741793baa85982489b61b69d7f76a8b5504 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 00:27:43 +0000 Subject: [PATCH 063/118] Support filtering by tags --- asyncgit/src/tags.rs | 1 + src/components/utils/async_commit_filter.rs | 75 ++++++++++++++++++--- src/tabs/revlog.rs | 7 +- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/asyncgit/src/tags.rs b/asyncgit/src/tags.rs index f23b54afcd..b38060f594 100644 --- a/asyncgit/src/tags.rs +++ b/asyncgit/src/tags.rs @@ -22,6 +22,7 @@ struct TagsResult { } /// +#[derive(Clone)] pub struct AsyncTags { last: Arc>>, sender: Sender, diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index d84bfec676..aba1cc5b7d 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -1,7 +1,7 @@ use anyhow::Result; use asyncgit::{ - sync::{self, limit_str, CommitInfo}, - AsyncLog, AsyncNotification, CWD, + sync::{self, limit_str, CommitId, CommitInfo}, + AsyncLog, AsyncNotification, AsyncTags, CWD, }; use bitflags::bitflags; use crossbeam_channel::{Sender, TryRecvError}; @@ -28,6 +28,7 @@ bitflags! { const MESSAGE = 0b0000_0100; const NOT = 0b0000_1000; const CASE_SENSITIVE = 0b0001_0000; + const TAGS = 0b0010_0000; } } @@ -39,6 +40,7 @@ pub enum FilterStatus { pub struct AsyncCommitFilterer { git_log: AsyncLog, + git_tags: AsyncTags, filter_strings: Vec>, filtered_commits: Arc>>, filter_count: Arc, @@ -52,11 +54,13 @@ pub struct AsyncCommitFilterer { impl AsyncCommitFilterer { pub fn new( git_log: AsyncLog, + git_tags: AsyncTags, sender: &Sender, ) -> Self { Self { filter_strings: Vec::new(), git_log, + git_tags, filtered_commits: Arc::new(Mutex::new(Vec::new())), filter_count: Arc::new(AtomicUsize::new(0)), filter_finished: Arc::new(AtomicBool::new(false)), @@ -87,6 +91,9 @@ impl AsyncCommitFilterer { #[allow(clippy::too_many_lines)] pub fn filter( mut vec_commit_info: Vec, + tags: &Option< + std::collections::BTreeMap>, + >, filter_strings: &[Vec<(String, FilterBy)>], ) -> Vec { vec_commit_info @@ -99,12 +106,17 @@ impl AsyncCommitFilterer { is_and = if filter.contains(FilterBy::NOT) { is_and - && ((filter - .contains(FilterBy::SHA) - && !commit - .id - .to_string() - .contains(s)) + && (filter + .contains(FilterBy::TAGS) + && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ + !tag_string.contains(s) + }).count() > 0)) + || (filter + .contains(FilterBy::SHA) + && !commit + .id + .to_string() + .contains(s)) || (filter.contains( FilterBy::AUTHOR, ) && !commit @@ -117,7 +129,12 @@ impl AsyncCommitFilterer { .contains(s))) } else { is_and - && ((filter + && (filter + .contains(FilterBy::TAGS) + && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ + !tag_string.contains(s) + }).count() > 0)) + || (filter .contains(FilterBy::SHA) && commit .id @@ -138,7 +155,12 @@ impl AsyncCommitFilterer { is_and = if filter.contains(FilterBy::NOT) { is_and - && ((filter + && (filter + .contains(FilterBy::TAGS) + && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ + !tag_string.to_lowercase().contains(&s.to_lowercase()) + }).count() > 0)) + || (filter .contains(FilterBy::SHA) && !commit .id @@ -165,7 +187,12 @@ impl AsyncCommitFilterer { ))) } else { is_and - && ((filter + && (filter + .contains(FilterBy::TAGS) + && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ + tag_string.to_lowercase().contains(&s.to_lowercase()) + }).count() > 0)) + || (filter .contains(FilterBy::SHA) && commit .id @@ -202,6 +229,7 @@ impl AsyncCommitFilterer { .collect() } + #[allow(clippy::too_many_lines)] pub fn start_filter( &mut self, filter_strings: Vec>, @@ -226,6 +254,30 @@ impl AsyncCommitFilterer { let cur_thread_mutex = Arc::clone(&self.filter_thread_mutex); self.is_pending_local.replace(true); + // If the search does not contain tags, do not include them + let mut contains_tags = false; + for or in &filter_strings { + for (_, filter_by) in or { + if filter_by.contains(FilterBy::TAGS) { + contains_tags = true; + break; + } + } + if contains_tags { + break; + } + } + + let tags = if contains_tags { + if let Ok(o) = self.git_tags.last() { + o + } else { + None + } + } else { + None + }; + rayon_core::spawn(move || { // Only 1 thread can filter at a time let _c = cur_thread_mutex.lock(); @@ -265,6 +317,7 @@ impl AsyncCommitFilterer { let mut filtered = Self::filter( v, + &tags, &filter_strings, ); filter_count.fetch_add( diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index c310e60ba1..a20586903b 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -54,6 +54,7 @@ impl Revlog { key_config: SharedKeyConfig, ) -> Self { let log = AsyncLog::new(sender); + let tags = AsyncTags::new(sender); Self { queue: queue.clone(), commit_details: CommitDetailsComponent::new( @@ -74,10 +75,11 @@ impl Revlog { ), async_filter: AsyncCommitFilterer::new( log.clone(), + tags.clone(), sender, ), git_log: log, - git_tags: AsyncTags::new(sender), + git_tags: tags, visible: false, branch_name: cached::BranchName::new(CWD), key_config, @@ -227,6 +229,9 @@ impl Revlog { if first.contains('c') { to_filter_by |= FilterBy::CASE_SENSITIVE; } + if first.contains('t') { + to_filter_by |= FilterBy::TAGS; + } if first.contains('!') { to_filter_by |= FilterBy::NOT; } From f4f5b6e8cbb4535ea31ccb0cf71ada13d15cec5d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 10:36:55 +0000 Subject: [PATCH 064/118] Support brackets in search --- src/tabs/revlog.rs | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index a20586903b..7b39955a67 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -273,7 +273,9 @@ impl Revlog { } pub fn filter(&mut self, filter_by: &str) -> Result<()> { - let trimmed_string = filter_by.trim().to_string(); + let pre_processed_string = + Self::pre_process_string(filter_by.to_string()); + let trimmed_string = pre_processed_string.trim().to_string(); if filter_by == "" { self.async_filter.stop_filter(); self.is_filtering = false; @@ -287,6 +289,46 @@ impl Revlog { } self.update() } + + /// pre process string to remove any brackets + pub fn pre_process_string(mut s: String) -> String { + while s.contains("&&(") { + let before = s.clone(); + s = Self::remove_out_brackets(&s); + if s == before { + break; + } + } + s + } + + pub fn remove_out_brackets(s: &str) -> String { + if let Some(first_bracket) = s.find("&&(") { + let (first, rest_of_string) = + s.split_at(first_bracket + 3); + if let Some(last_bracket) = rest_of_string.find(')') { + let mut v = vec![]; + let (second, third) = + rest_of_string.split_at(last_bracket); + if let Some((first, third)) = first + .strip_suffix('(') + .zip(third.strip_prefix(')')) + { + for element in second.split("||") { + // Append first, prepend third onto branket element + v.push(format!( + "{}{}{}", + first.clone(), + element.clone(), + third.clone() + )); + } + return v.join("||"); + } + } + } + return s.to_string(); + } } impl DrawableComponent for Revlog { From 9c51df3c4a07f90a7989e395634a0a4acd4fc36b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 11:11:37 +0000 Subject: [PATCH 065/118] Allow nested brackets --- src/tabs/revlog.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 7b39955a67..8f08efa003 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -306,7 +306,9 @@ impl Revlog { if let Some(first_bracket) = s.find("&&(") { let (first, rest_of_string) = s.split_at(first_bracket + 3); - if let Some(last_bracket) = rest_of_string.find(')') { + if let Some(last_bracket) = + Self::get_ending_bracket(rest_of_string) + { let mut v = vec![]; let (second, third) = rest_of_string.split_at(last_bracket); @@ -329,6 +331,44 @@ impl Revlog { } return s.to_string(); } + + pub fn get_ending_bracket(s: &str) -> Option { + let mut brack_count = 0; + let mut char_iter = s.chars(); + let mut ending_brakcet_pos = None; + let mut iter_count = 0; + loop { + if let Some(c) = char_iter.next() { + if c == '&' { + if let Some(c2) = char_iter.next() { + { + iter_count += 1; + if c2 == '&' { + if let Some(c3) = char_iter.next() { + iter_count += 1; + if c3 == '(' { + brack_count += 1; + } + } + } + } + } + } else if c == ')' { + if brack_count == 0 { + // Found + ending_brakcet_pos = Some(iter_count); + break; + } else { + brack_count -= 1; + } + } + } else { + break; + } + iter_count += 1; + } + ending_brakcet_pos + } } impl DrawableComponent for Revlog { From f6863d00534d1b9bfdf5fbdd346d2704c876778a Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 11:14:37 +0000 Subject: [PATCH 066/118] Fix clippy --- src/tabs/revlog.rs | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 8f08efa003..9cf315ad87 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -320,16 +320,14 @@ impl Revlog { // Append first, prepend third onto branket element v.push(format!( "{}{}{}", - first.clone(), - element.clone(), - third.clone() + first, element, third )); } return v.join("||"); } } } - return s.to_string(); + s.to_string() } pub fn get_ending_bracket(s: &str) -> Option { @@ -337,33 +335,29 @@ impl Revlog { let mut char_iter = s.chars(); let mut ending_brakcet_pos = None; let mut iter_count = 0; - loop { - if let Some(c) = char_iter.next() { - if c == '&' { - if let Some(c2) = char_iter.next() { - { - iter_count += 1; - if c2 == '&' { - if let Some(c3) = char_iter.next() { - iter_count += 1; - if c3 == '(' { - brack_count += 1; - } + while let Some(c) = char_iter.next() { + if c == '&' { + if let Some(c2) = char_iter.next() { + { + iter_count += 1; + if c2 == '&' { + if let Some(c3) = char_iter.next() { + iter_count += 1; + if c3 == '(' { + brack_count += 1; } } } } - } else if c == ')' { - if brack_count == 0 { - // Found - ending_brakcet_pos = Some(iter_count); - break; - } else { - brack_count -= 1; - } } - } else { - break; + } else if c == ')' { + if brack_count == 0 { + // Found + ending_brakcet_pos = Some(iter_count); + break; + } else { + brack_count -= 1; + } } iter_count += 1; } From a361c708c81d182236c6eb7f6f260afe1e9cfa41 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 11:18:26 +0000 Subject: [PATCH 067/118] Fix clippy --- src/tabs/revlog.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 9cf315ad87..b6fe74e43b 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -355,9 +355,8 @@ impl Revlog { // Found ending_brakcet_pos = Some(iter_count); break; - } else { - brack_count -= 1; } + brack_count -= 1; } iter_count += 1; } From a0b9af590186aa84513068f5479203b7c2fc8395 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 12:30:31 +0000 Subject: [PATCH 068/118] Support nested brackets and fix not tag search to show commits with no tags --- src/components/utils/async_commit_filter.rs | 6 ++--- src/tabs/revlog.rs | 27 +++++++++------------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index aba1cc5b7d..3912b68272 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -108,7 +108,7 @@ impl AsyncCommitFilterer { is_and && (filter .contains(FilterBy::TAGS) - && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ + && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(true, |commit_tags| commit_tags.iter().filter(|tag_string|{ !tag_string.contains(s) }).count() > 0)) || (filter @@ -132,7 +132,7 @@ impl AsyncCommitFilterer { && (filter .contains(FilterBy::TAGS) && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ - !tag_string.contains(s) + tag_string.contains(s) }).count() > 0)) || (filter .contains(FilterBy::SHA) @@ -157,7 +157,7 @@ impl AsyncCommitFilterer { is_and && (filter .contains(FilterBy::TAGS) - && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ + && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(true, |commit_tags| commit_tags.iter().filter(|tag_string|{ !tag_string.to_lowercase().contains(&s.to_lowercase()) }).count() > 0)) || (filter diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index b6fe74e43b..8da35e72e1 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -246,7 +246,8 @@ impl Revlog { FilterBy::all(); } else if to_filter_by == FilterBy::NOT { to_filter_by = FilterBy::all() - & !FilterBy::CASE_SENSITIVE; + & !FilterBy::CASE_SENSITIVE + & !FilterBy::TAGS; } else if to_filter_by == FilterBy::CASE_SENSITIVE { to_filter_by = @@ -275,6 +276,7 @@ impl Revlog { pub fn filter(&mut self, filter_by: &str) -> Result<()> { let pre_processed_string = Self::pre_process_string(filter_by.to_string()); + println!("{}", pre_processed_string); let trimmed_string = pre_processed_string.trim().to_string(); if filter_by == "" { self.async_filter.stop_filter(); @@ -312,6 +314,7 @@ impl Revlog { let mut v = vec![]; let (second, third) = rest_of_string.split_at(last_bracket); + println!("{}___{}___{}", first, second, third); if let Some((first, third)) = first .strip_suffix('(') .zip(third.strip_prefix(')')) @@ -322,6 +325,10 @@ impl Revlog { "{}{}{}", first, element, third )); + + // std::thread::sleep( + // std::time::Duration::from_secs(2), + //); } return v.join("||"); } @@ -334,22 +341,10 @@ impl Revlog { let mut brack_count = 0; let mut char_iter = s.chars(); let mut ending_brakcet_pos = None; - let mut iter_count = 0; + let mut iter_count: usize = 0; while let Some(c) = char_iter.next() { - if c == '&' { - if let Some(c2) = char_iter.next() { - { - iter_count += 1; - if c2 == '&' { - if let Some(c3) = char_iter.next() { - iter_count += 1; - if c3 == '(' { - brack_count += 1; - } - } - } - } - } + if c == '(' { + brack_count += 1; } else if c == ')' { if brack_count == 0 { // Found From 16473573d808bf9a273f0c846544ba5d428b1cf0 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 12:31:59 +0000 Subject: [PATCH 069/118] Remove unneeded comments --- src/tabs/revlog.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 8da35e72e1..229c42b0d6 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -314,7 +314,6 @@ impl Revlog { let mut v = vec![]; let (second, third) = rest_of_string.split_at(last_bracket); - println!("{}___{}___{}", first, second, third); if let Some((first, third)) = first .strip_suffix('(') .zip(third.strip_prefix(')')) @@ -325,10 +324,6 @@ impl Revlog { "{}{}{}", first, element, third )); - - // std::thread::sleep( - // std::time::Duration::from_secs(2), - //); } return v.join("||"); } From 507d6f8560986519bfb833961e0e1782df94c86e Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 12:38:12 +0000 Subject: [PATCH 070/118] Small varible name change --- src/tabs/revlog.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 229c42b0d6..ad7b5babeb 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -318,11 +318,11 @@ impl Revlog { .strip_suffix('(') .zip(third.strip_prefix(')')) { - for element in second.split("||") { - // Append first, prepend third onto branket element + for inside_bracket_item in second.split("||") { + // Append first, prepend third onto bracket element v.push(format!( "{}{}{}", - first, element, third + first, inside_bracket_item, third )); } return v.join("||"); From 32eb3e27ff6ce4b85ff6d2cf8a786c467177417e Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 12:45:21 +0000 Subject: [PATCH 071/118] Reset LIMIT_COUNT in revlog to 3000 --- asyncgit/src/revlog.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index 921000b372..fd1f77d242 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -35,7 +35,7 @@ pub struct AsyncLog { background: Arc, } -static LIMIT_COUNT: usize = 5; +static LIMIT_COUNT: usize = 3000; static SLEEP_FOREGROUND: Duration = Duration::from_millis(2); static SLEEP_BACKGROUND: Duration = Duration::from_millis(1000); From 501a194bdae7922e9c4ab855d3eed4f99da77e7b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Sat, 13 Feb 2021 12:46:22 +0000 Subject: [PATCH 072/118] Remove unneeded logging --- src/tabs/revlog.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index ad7b5babeb..251efb9043 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -276,7 +276,6 @@ impl Revlog { pub fn filter(&mut self, filter_by: &str) -> Result<()> { let pre_processed_string = Self::pre_process_string(filter_by.to_string()); - println!("{}", pre_processed_string); let trimmed_string = pre_processed_string.trim().to_string(); if filter_by == "" { self.async_filter.stop_filter(); From fe6d81738bbfa8e0e869f4d59c31dbe4910e10d5 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 17:36:54 +0000 Subject: [PATCH 073/118] Remove amount argument from fetch_helper --- asyncgit/src/revlog.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index fd1f77d242..60f7bd6482 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -126,7 +126,6 @@ impl AsyncLog { arc_current, arc_background, &sender, - LIMIT_COUNT, ) .expect("failed to fetch"); @@ -142,15 +141,14 @@ impl AsyncLog { arc_current: Arc>>, arc_background: Arc, sender: &Sender, - amount: usize, ) -> Result<()> { - let mut entries = Vec::with_capacity(amount); + let mut entries = Vec::with_capacity(LIMIT_COUNT); let r = repo(CWD)?; let mut walker = LogWalker::new(&r); loop { entries.clear(); let res_is_err = - walker.read(&mut entries, amount).is_err(); + walker.read(&mut entries, LIMIT_COUNT).is_err(); if !res_is_err { let mut current = arc_current.lock()?; From 447d0952cf5b8f5f4e900cef83e43b1cfa0f7c9b Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 17:38:56 +0000 Subject: [PATCH 074/118] Move comment above derive --- asyncgit/src/sync/commits_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs index d94378e9d5..8a69ed1401 100644 --- a/asyncgit/src/sync/commits_info.rs +++ b/asyncgit/src/sync/commits_info.rs @@ -106,8 +106,8 @@ pub fn get_message( } } -#[inline] /// +#[inline] pub fn limit_str(s: &str, limit: usize) -> &str { if let Some(first) = s.lines().next() { let mut limit = limit.min(first.len()); From e68938d38d4066188990d2ac4d78aa6a8c3600d6 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 17:52:44 +0000 Subject: [PATCH 075/118] Create function get_tags --- src/components/utils/async_commit_filter.rs | 52 +++++++++++---------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 3912b68272..c7b1af946e 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -1,6 +1,6 @@ use anyhow::Result; use asyncgit::{ - sync::{self, limit_str, CommitId, CommitInfo}, + sync::{self, limit_str, CommitId, CommitInfo, Tags}, AsyncLog, AsyncNotification, AsyncTags, CWD, }; use bitflags::bitflags; @@ -229,7 +229,31 @@ impl AsyncCommitFilterer { .collect() } - #[allow(clippy::too_many_lines)] + /// If the filtering string contain filtering by tags + /// return them, else don't get the tags + fn get_tags( + filter_strings: &Vec>, + git_tags: &mut AsyncTags, + ) -> Result> { + let mut contains_tags = false; + for or in filter_strings { + for (_, filter_by) in or { + if filter_by.contains(FilterBy::TAGS) { + contains_tags = true; + break; + } + } + if contains_tags { + break; + } + } + + if contains_tags { + return git_tags.last().map_err(|e| anyhow::anyhow!(e)); + } + Ok(None) + } + pub fn start_filter( &mut self, filter_strings: Vec>, @@ -255,28 +279,8 @@ impl AsyncCommitFilterer { self.is_pending_local.replace(true); // If the search does not contain tags, do not include them - let mut contains_tags = false; - for or in &filter_strings { - for (_, filter_by) in or { - if filter_by.contains(FilterBy::TAGS) { - contains_tags = true; - break; - } - } - if contains_tags { - break; - } - } - - let tags = if contains_tags { - if let Ok(o) = self.git_tags.last() { - o - } else { - None - } - } else { - None - }; + let tags = + Self::get_tags(&filter_strings, &mut self.git_tags)?; rayon_core::spawn(move || { // Only 1 thread can filter at a time From 71582a7005b83d38753b3811ee1387b8de5e256d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 17:56:21 +0000 Subject: [PATCH 076/118] Remove added ; --- src/tabs/revlog.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 251efb9043..da02b5e5c1 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -177,7 +177,7 @@ impl Revlog { if let Ok(commits) = commits { self.list.items().set_items(want_min, commits); - }; + } Ok(()) } From f933bf26d380dca7c3730249c1e8a67bb4725fd9 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 17:58:08 +0000 Subject: [PATCH 077/118] Add comment to get_ending_bracket --- src/tabs/revlog.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index da02b5e5c1..02bf967e8b 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -331,6 +331,7 @@ impl Revlog { s.to_string() } + // Get outer matching brakets in a string pub fn get_ending_bracket(s: &str) -> Option { let mut brack_count = 0; let mut char_iter = s.chars(); From d1c3b546f0bc582e489f04359459186bb235bd45 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 17:59:12 +0000 Subject: [PATCH 078/118] Add comment to remove_out_brackets --- src/tabs/revlog.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 02bf967e8b..1f40e5dfde 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -303,6 +303,7 @@ impl Revlog { s } + /// Remove the brakcets, replacing them with the unbracketed 'full' expression pub fn remove_out_brackets(s: &str) -> String { if let Some(first_bracket) = s.find("&&(") { let (first, rest_of_string) = @@ -331,7 +332,7 @@ impl Revlog { s.to_string() } - // Get outer matching brakets in a string + /// Get outer matching brakets in a string pub fn get_ending_bracket(s: &str) -> Option { let mut brack_count = 0; let mut char_iter = s.chars(); From 1f133c76d20511f15d78206ad7824bfd50e16fbc Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 18:05:38 +0000 Subject: [PATCH 079/118] Only re-filter if string is different --- src/tabs/revlog.rs | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 1f40e5dfde..eb713a5496 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -43,6 +43,7 @@ pub struct Revlog { branch_name: cached::BranchName, key_config: SharedKeyConfig, is_filtering: bool, + filter_string: String, } impl Revlog { @@ -274,21 +275,26 @@ impl Revlog { } pub fn filter(&mut self, filter_by: &str) -> Result<()> { - let pre_processed_string = - Self::pre_process_string(filter_by.to_string()); - let trimmed_string = pre_processed_string.trim().to_string(); - if filter_by == "" { - self.async_filter.stop_filter(); - self.is_filtering = false; - } else { - let filter_strings = - Self::get_what_to_filter_by(&trimmed_string); - self.async_filter - .start_filter(filter_strings) - .map_err(|e| anyhow::anyhow!(e.to_string()))?; - self.is_filtering = true; + if filter_by != &self.filter_string { + self.filter_string = filter_by.to_string(); + let pre_processed_string = + Self::pre_process_string(filter_by.to_string()); + let trimmed_string = + pre_processed_string.trim().to_string(); + if filter_by == "" { + self.async_filter.stop_filter(); + self.is_filtering = false; + } else { + let filter_strings = + Self::get_what_to_filter_by(&trimmed_string); + self.async_filter + .start_filter(filter_strings) + .map_err(|e| anyhow::anyhow!(e.to_string()))?; + self.is_filtering = true; + } + return self.update(); } - self.update() + Ok(()) } /// pre process string to remove any brackets From 6124b9be8a20614303ddac2f8beb8451469eafa1 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 18:06:16 +0000 Subject: [PATCH 080/118] Add filter_string to new revlog --- src/tabs/revlog.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index eb713a5496..c3946dab69 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -85,6 +85,7 @@ impl Revlog { branch_name: cached::BranchName::new(CWD), key_config, is_filtering: false, + filter_string: "".to_string(), } } From 4b07b3ab8ab4c8b264fd133b9cf439537389ce4d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 18:09:37 +0000 Subject: [PATCH 081/118] Change back to set_total_count in commitlist --- src/components/commitlist.rs | 2 +- src/tabs/revlog.rs | 5 ++--- src/tabs/stashlist.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index d8b44cb17a..a39777e15f 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -84,7 +84,7 @@ impl CommitList { } /// - pub fn update_total_count(&mut self, count: usize) { + pub fn set_total_count(&mut self, count: usize) { self.count_total = count; self.selection = cmp::min(self.selection, self.selection_max()); diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index c3946dab69..22d6e1b40a 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -101,11 +101,10 @@ impl Revlog { pub fn update(&mut self) -> Result<()> { if self.visible { let log_changed = if self.is_filtering { - self.list - .update_total_count(self.async_filter.count()); + self.list.set_total_count(self.async_filter.count()); self.async_filter.fetch() == FilterStatus::Filtering } else { - self.list.update_total_count(self.git_log.count()?); + self.list.set_total_count(self.git_log.count()?); self.git_log.fetch()? == FetchStatus::Started }; diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index d1016c0cf5..d6782c61bb 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -48,7 +48,7 @@ impl StashList { let commits = sync::get_commits_info(CWD, stashes.as_slice(), 100)?; - self.list.update_total_count(commits.len()); + self.list.set_total_count(commits.len()); self.list.items().set_items(0, commits); } From d70cc4fca3a052133be9257f0a803664fb131fa0 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 18:25:37 +0000 Subject: [PATCH 082/118] Fix clippy warnings and change while to for in get_ending_bracket --- src/components/utils/async_commit_filter.rs | 2 +- src/tabs/revlog.rs | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index c7b1af946e..d7711f58f7 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -232,7 +232,7 @@ impl AsyncCommitFilterer { /// If the filtering string contain filtering by tags /// return them, else don't get the tags fn get_tags( - filter_strings: &Vec>, + filter_strings: &[Vec<(String, FilterBy)>], git_tags: &mut AsyncTags, ) -> Result> { let mut contains_tags = false; diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 22d6e1b40a..3293bceb6e 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -275,7 +275,7 @@ impl Revlog { } pub fn filter(&mut self, filter_by: &str) -> Result<()> { - if filter_by != &self.filter_string { + if filter_by != self.filter_string { self.filter_string = filter_by.to_string(); let pre_processed_string = Self::pre_process_string(filter_by.to_string()); @@ -341,21 +341,18 @@ impl Revlog { /// Get outer matching brakets in a string pub fn get_ending_bracket(s: &str) -> Option { let mut brack_count = 0; - let mut char_iter = s.chars(); let mut ending_brakcet_pos = None; - let mut iter_count: usize = 0; - while let Some(c) = char_iter.next() { + for (i, c) in s.chars().enumerate() { if c == '(' { brack_count += 1; } else if c == ')' { if brack_count == 0 { // Found - ending_brakcet_pos = Some(iter_count); + ending_brakcet_pos = Some(i); break; } brack_count -= 1; } - iter_count += 1; } ending_brakcet_pos } From 2d3cf33e8415ec050084c501798a89c223341801 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 18:48:54 +0000 Subject: [PATCH 083/118] Fix search by trimming string --- src/tabs/revlog.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 3293bceb6e..945095f63b 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -261,11 +261,11 @@ impl Revlog { )); } else { and_vec.push(( - split_sub.to_string(), + split_sub.trim().to_string(), FilterBy::all() & !FilterBy::NOT & !FilterBy::CASE_SENSITIVE, - )) + )); } } search_vec.push(and_vec.clone()); From 7b610b3d95f4794efd7543ac47b4780ec104ea10 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 18 Feb 2021 18:49:52 +0000 Subject: [PATCH 084/118] Add brackets round tag filter in async_filter --- src/components/utils/async_commit_filter.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index d7711f58f7..4740383088 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -106,11 +106,11 @@ impl AsyncCommitFilterer { is_and = if filter.contains(FilterBy::NOT) { is_and - && (filter + && ((filter .contains(FilterBy::TAGS) && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(true, |commit_tags| commit_tags.iter().filter(|tag_string|{ !tag_string.contains(s) - }).count() > 0)) + }).count() > 0))) || (filter .contains(FilterBy::SHA) && !commit @@ -129,11 +129,11 @@ impl AsyncCommitFilterer { .contains(s))) } else { is_and - && (filter + && ((filter .contains(FilterBy::TAGS) && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ tag_string.contains(s) - }).count() > 0)) + }).count() > 0))) || (filter .contains(FilterBy::SHA) && commit @@ -155,11 +155,11 @@ impl AsyncCommitFilterer { is_and = if filter.contains(FilterBy::NOT) { is_and - && (filter + && ((filter .contains(FilterBy::TAGS) && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(true, |commit_tags| commit_tags.iter().filter(|tag_string|{ !tag_string.to_lowercase().contains(&s.to_lowercase()) - }).count() > 0)) + }).count() > 0))) || (filter .contains(FilterBy::SHA) && !commit @@ -187,11 +187,11 @@ impl AsyncCommitFilterer { ))) } else { is_and - && (filter + && ((filter .contains(FilterBy::TAGS) && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ tag_string.to_lowercase().contains(&s.to_lowercase()) - }).count() > 0)) + }).count() > 0))) || (filter .contains(FilterBy::SHA) && commit From 6ab22fac092eca52b9a4c1029796a91dd0d8f8a7 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Fri, 19 Feb 2021 15:15:45 +0000 Subject: [PATCH 085/118] Add search filter commits to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49c5c7e2c9..cfb6294d2b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ - Stashing (save, apply, drop, and inspect) - Push to remote - Branch List (create, rename, delete) -- Browse commit log, diff committed changes +- Browse commit log, diff committed changes, search/filter commits - Scalable terminal UI layout - Async [input polling](assets/perf_compare.jpg) - Async git API for fluid control From 2a99ed524c59a464cdc2ea19a705eee9a66200a8 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Fri, 19 Feb 2021 15:27:02 +0000 Subject: [PATCH 086/118] Remove is_and && from filter --- src/components/utils/async_commit_filter.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 4740383088..51df3a8665 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -105,8 +105,7 @@ impl AsyncCommitFilterer { if filter.contains(FilterBy::CASE_SENSITIVE) { is_and = if filter.contains(FilterBy::NOT) { - is_and - && ((filter + (filter .contains(FilterBy::TAGS) && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(true, |commit_tags| commit_tags.iter().filter(|tag_string|{ !tag_string.contains(s) @@ -126,10 +125,9 @@ impl AsyncCommitFilterer { FilterBy::MESSAGE, ) && !commit .message - .contains(s))) + .contains(s)) } else { - is_and - && ((filter + (filter .contains(FilterBy::TAGS) && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ tag_string.contains(s) @@ -149,13 +147,12 @@ impl AsyncCommitFilterer { FilterBy::MESSAGE, ) && commit .message - .contains(s))) + .contains(s)) } } else { is_and = if filter.contains(FilterBy::NOT) { - is_and - && ((filter + (filter .contains(FilterBy::TAGS) && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(true, |commit_tags| commit_tags.iter().filter(|tag_string|{ !tag_string.to_lowercase().contains(&s.to_lowercase()) @@ -184,10 +181,9 @@ impl AsyncCommitFilterer { .to_lowercase() .contains( &s.to_lowercase(), - ))) + )) } else { - is_and - && ((filter + (filter .contains(FilterBy::TAGS) && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag_string|{ tag_string.to_lowercase().contains(&s.to_lowercase()) @@ -216,7 +212,7 @@ impl AsyncCommitFilterer { .to_lowercase() .contains( &s.to_lowercase(), - ))) + )) } } } From c777ef3f9dc7cb06f8d221b2819f421a60202e4d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Fri, 19 Feb 2021 16:20:03 +0000 Subject: [PATCH 087/118] Remove ALT down to focus on search --- src/keys.rs | 2 -- src/tabs/revlog.rs | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index 34e84984b1..1d82d6c851 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -34,7 +34,6 @@ pub struct KeyConfig { pub open_commit: KeyEvent, pub open_commit_editor: KeyEvent, pub show_find_commit_text_input: KeyEvent, - pub focus_find_commit: KeyEvent, pub open_help: KeyEvent, pub move_left: KeyEvent, pub move_right: KeyEvent, @@ -89,7 +88,6 @@ impl Default for KeyConfig { open_commit: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()}, open_commit_editor: KeyEvent { code: KeyCode::Char('e'), modifiers:KeyModifiers::CONTROL}, show_find_commit_text_input: KeyEvent {code: KeyCode::Char('s'), modifiers: KeyModifiers::empty()}, - focus_find_commit: KeyEvent {code: KeyCode::Down, modifiers: KeyModifiers::ALT}, open_help: KeyEvent { code: KeyCode::Char('h'), modifiers: KeyModifiers::empty()}, move_left: KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::empty()}, move_right: KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::empty()}, diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 945095f63b..b08dda5e97 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -472,9 +472,6 @@ impl Component for Revlog { self.find_commit.toggle_visible()?; self.find_commit.focus(true); return Ok(true); - } else if k == self.key_config.focus_find_commit { - self.find_commit.focus(true); - return Ok(true); } else if k == self.key_config.exit_popup { self.filter("")?; self.find_commit.clear_input(); From 3fe9a33b78c36da2816e2028059ac7037078039d Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Fri, 19 Feb 2021 16:24:30 +0000 Subject: [PATCH 088/118] Remove comment about getting tags from start_filter --- src/components/utils/async_commit_filter.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 51df3a8665..9a89bcac05 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -274,7 +274,6 @@ impl AsyncCommitFilterer { let cur_thread_mutex = Arc::clone(&self.filter_thread_mutex); self.is_pending_local.replace(true); - // If the search does not contain tags, do not include them let tags = Self::get_tags(&filter_strings, &mut self.git_tags)?; From dd158e0c9868074b5ee190d852c514284e6fa502 Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Fri, 19 Feb 2021 16:39:23 +0000 Subject: [PATCH 089/118] Remove continue filter comment --- src/components/utils/async_commit_filter.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 9a89bcac05..0a2efe0baf 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -371,12 +371,6 @@ impl AsyncCommitFilterer { self.filter_finished.store(true, Ordering::Relaxed); } - /// Use if the next item to be filtered is a substring of the previous item. - /// This then only searches through the previous list - //pub fn continue_filter(&mut self, _s: String) -> Result<()> { - // Ok(()) - //} - pub fn get_filter_items( &mut self, start: usize, From b0ee95143b58a49146286519af5613cabe3dc0fb Mon Sep 17 00:00:00 2001 From: Richard Menzies Date: Thu, 25 Feb 2021 00:44:08 +0000 Subject: [PATCH 090/118] Fix merge problems --- src/components/textinput.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 8938046bd9..e8849fe4bc 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -39,11 +39,8 @@ pub struct TextInputComponent { key_config: SharedKeyConfig, cursor_position: usize, input_type: InputType, -<<<<<<< HEAD should_use_rect: bool, -======= current_area: Cell, ->>>>>>> master } impl TextInputComponent { @@ -65,11 +62,8 @@ impl TextInputComponent { default_msg: default_msg.to_string(), cursor_position: 0, input_type: InputType::Multiline, -<<<<<<< HEAD should_use_rect: false, -======= current_area: Cell::new(Rect::default()), ->>>>>>> master } } From 82ce6fc9f4646a5d2f711655b0a46f2621f3e5ed Mon Sep 17 00:00:00 2001 From: Anton Rapetov Date: Sun, 25 Apr 2021 10:59:49 -0400 Subject: [PATCH 091/118] Add tests for `Revlog::get_what_to_filter_by` --- src/tabs/revlog.rs | 150 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index b08dda5e97..b6538ea493 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -551,3 +551,153 @@ impl Component for Revlog { Ok(()) } } + +#[cfg(test)] +mod test { + use super::Revlog; + use crate::components::async_commit_filter::FilterBy; + + #[test] + fn test_get_what_to_filter_by_flags() { + assert_eq!( + Revlog::get_what_to_filter_by("foo"), + vec![vec![("foo".to_owned(), FilterBy::everywhere())]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":s foo"), + vec![vec![("foo".to_owned(), FilterBy::SHA)]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":sm foo"), + vec![vec![( + "foo".to_owned(), + FilterBy::SHA | FilterBy::MESSAGE + )]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":samt foo"), + vec![vec![("foo".to_owned(), FilterBy::everywhere())]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":!csamt foo"), + vec![vec![("foo".to_owned(), FilterBy::all())]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":!c foo"), + vec![vec![("foo".to_owned(), FilterBy::all())]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":! foo"), + vec![vec![( + "foo".to_owned(), + FilterBy::everywhere() | FilterBy::NOT + )]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":c foo"), + vec![vec![( + "foo".to_owned(), + FilterBy::everywhere() | FilterBy::CASE_SENSITIVE + )]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":!m foo"), + vec![vec![( + "foo".to_owned(), + FilterBy::MESSAGE | FilterBy::NOT + )]] + ); + } + + #[test] + fn test_get_what_to_filter_by_log_op() { + assert_eq!( + Revlog::get_what_to_filter_by("foo && bar"), + vec![vec![ + ("foo".to_owned(), FilterBy::everywhere()), + ("bar".to_owned(), FilterBy::everywhere()) + ]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by("foo || bar"), + vec![ + vec![("foo".to_owned(), FilterBy::everywhere())], + vec![("bar".to_owned(), FilterBy::everywhere())] + ] + ); + + assert_eq!( + Revlog::get_what_to_filter_by("foo && bar || :m baz"), + vec![ + vec![ + ("foo".to_owned(), FilterBy::everywhere()), + ("bar".to_owned(), FilterBy::everywhere()) + ], + vec![("baz".to_owned(), FilterBy::MESSAGE)] + ] + ); + } + + #[test] + fn test_get_what_to_filter_by_spaces() { + assert_eq!( + Revlog::get_what_to_filter_by("foo&&bar"), + vec![vec![ + ("foo".to_owned(), FilterBy::everywhere()), + ("bar".to_owned(), FilterBy::everywhere()) + ]] + ); + assert_eq!( + Revlog::get_what_to_filter_by(" foo && bar "), + vec![vec![ + ("foo".to_owned(), FilterBy::everywhere()), + ("bar".to_owned(), FilterBy::everywhere()) + ]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(" foo bar baz "), + vec![vec![( + "foo bar baz".to_owned(), + FilterBy::everywhere() + )]] + ); + assert_eq!( + Revlog::get_what_to_filter_by(" :m foo bar baz "), + vec![vec![( + "foo bar baz".to_owned(), + FilterBy::MESSAGE + )]] + ); + assert_eq!( + Revlog::get_what_to_filter_by( + " :m foo bar baz && qwe t " + ), + vec![vec![ + ("foo bar baz".to_owned(), FilterBy::MESSAGE), + ("qwe t".to_owned(), FilterBy::everywhere()) + ]] + ); + } + + #[test] + fn test_get_what_to_filter_by_invalid_flags_ignored() { + assert_eq!( + Revlog::get_what_to_filter_by(":q foo"), + vec![vec![("foo".to_owned(), FilterBy::everywhere())]] + ); + assert_eq!( + Revlog::get_what_to_filter_by(":mq foo"), + vec![vec![("foo".to_owned(), FilterBy::MESSAGE)]] + ); + } +} From f12fde4392bca3df38b1001b8d0754b58032c7a9 Mon Sep 17 00:00:00 2001 From: Anton Rapetov Date: Sun, 25 Apr 2021 11:00:57 -0400 Subject: [PATCH 092/118] Refactor, fix a few bugs(with space handling), and add documentation to --- src/components/utils/async_commit_filter.rs | 33 +++++++ src/tabs/revlog.rs | 96 +++++++++------------ 2 files changed, 73 insertions(+), 56 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 0a2efe0baf..181adc75ca 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -6,6 +6,7 @@ use asyncgit::{ use bitflags::bitflags; use crossbeam_channel::{Sender, TryRecvError}; use parking_lot::Mutex; +use std::convert::TryFrom; use std::{ cell::RefCell, sync::{ @@ -32,6 +33,38 @@ bitflags! { } } +impl FilterBy { + pub fn everywhere() -> Self { + Self::all() & !Self::NOT & !Self::CASE_SENSITIVE + } + + pub fn exclude_modifiers(self) -> Self { + self & !Self::NOT & !Self::CASE_SENSITIVE + } +} + +impl Default for FilterBy { + fn default() -> Self { + Self::all() & !Self::NOT & !Self::CASE_SENSITIVE + } +} + +impl TryFrom for FilterBy { + type Error = anyhow::Error; + + fn try_from(v: char) -> Result { + match v { + 's' => Ok(Self::SHA), + 'a' => Ok(Self::AUTHOR), + 'm' => Ok(Self::MESSAGE), + '!' => Ok(Self::NOT), + 'c' => Ok(Self::CASE_SENSITIVE), + 't' => Ok(Self::TAGS), + _ => Err(anyhow::anyhow!(format!("Unknown flag: {}", v))), + } + } +} + #[derive(PartialEq)] pub enum FilterStatus { Filtering, diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index b6538ea493..e8f768db0d 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -20,6 +20,7 @@ use asyncgit::{ }; use crossbeam_channel::Sender; use crossterm::event::Event; +use std::convert::TryFrom; use std::time::Duration; use sync::CommitTags; use tui::{ @@ -203,70 +204,53 @@ impl Revlog { }) } + /// Parses search string into individual sub-searches. + /// Each sub-search is a tuple of (string-to-search, flags-where-to-search) + /// + /// Returns vec of vec of sub-searches. + /// Where search results: + /// 1. from outer vec should be combined via 'disjunction' (or); + /// 2. from inter vec should be combined via 'conjunction' (and). + /// + /// Currently parentheses in the `filter_by_str` are not supported. + /// They should be removed by `Self::pre_process_string`. fn get_what_to_filter_by( filter_by_str: &str, ) -> Vec> { - let mut search_vec = vec![]; + let mut search_vec = Vec::new(); let mut and_vec = Vec::new(); for or in filter_by_str.split("||") { - for split_sub in or.split("&&") { - if let Some(':') = split_sub.chars().next() { - let mut to_filter_by = FilterBy::empty(); - let mut split_str = - split_sub.split(' ').collect::>(); - if split_str.len() == 1 { - split_str.push(""); - } - let first = split_str[0]; - if first.contains('s') { - to_filter_by |= FilterBy::SHA; - } - if first.contains('a') { - to_filter_by |= FilterBy::AUTHOR; - } - if first.contains('m') { - to_filter_by |= FilterBy::MESSAGE; - } - if first.contains('c') { - to_filter_by |= FilterBy::CASE_SENSITIVE; - } - if first.contains('t') { - to_filter_by |= FilterBy::TAGS; - } - if first.contains('!') { - to_filter_by |= FilterBy::NOT; - } - - if to_filter_by.is_empty() { - to_filter_by = FilterBy::all() - & !FilterBy::NOT - & !FilterBy::CASE_SENSITIVE; - } else if to_filter_by - == FilterBy::CASE_SENSITIVE & FilterBy::NOT - { - FilterBy::all(); - } else if to_filter_by == FilterBy::NOT { - to_filter_by = FilterBy::all() - & !FilterBy::CASE_SENSITIVE - & !FilterBy::TAGS; - } else if to_filter_by == FilterBy::CASE_SENSITIVE - { - to_filter_by = - FilterBy::all() & !FilterBy::NOT; - }; - - and_vec.push(( - split_str[1..].join(" ").trim().to_string(), - to_filter_by, - )); - } else { + for split_sub in or.split("&&").map(str::trim) { + if !split_sub.starts_with(":") { and_vec.push(( - split_sub.trim().to_string(), - FilterBy::all() - & !FilterBy::NOT - & !FilterBy::CASE_SENSITIVE, + split_sub.to_string(), + FilterBy::everywhere(), )); + continue; + } + + let mut split_str = split_sub.splitn(2, ' '); + let first = split_str.next().unwrap(); + let mut to_filter_by = first.chars().skip(1).fold( + FilterBy::empty(), + |acc, ch| { + acc | FilterBy::try_from(ch) + .unwrap_or(FilterBy::empty()) + }, + ); + + if to_filter_by.exclude_modifiers().is_empty() { + to_filter_by |= FilterBy::everywhere(); } + + and_vec.push(( + split_str + .next() + .unwrap_or(&"") + .trim_start() + .to_string(), + to_filter_by, + )); } search_vec.push(and_vec.clone()); and_vec.clear(); From 6e31762ed9683ea7b3c1a6933e34c508562e00da Mon Sep 17 00:00:00 2001 From: Anton Rapetov Date: Sun, 25 Apr 2021 14:49:36 -0400 Subject: [PATCH 093/118] Use std::sync::Mutex instead of the one from parking_lot --- Cargo.lock | 1 - Cargo.toml | 1 - src/components/utils/async_commit_filter.rs | 16 ++++++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3585423c1..598bbc5514 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,7 +365,6 @@ dependencies = [ "dirs-next", "itertools", "log", - "parking_lot", "pprof", "rayon-core", "ron", diff --git a/Cargo.toml b/Cargo.toml index 12c685f8ca..8644cf39d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,6 @@ serde = "1.0" anyhow = "1.0" unicode-width = "0.1" textwrap = "0.13" -parking_lot = "0.11" unicode-truncate = "0.2.0" [target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies] diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index de20d44771..12a313a453 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -5,13 +5,12 @@ use asyncgit::{ }; use bitflags::bitflags; use crossbeam_channel::{Sender, TryRecvError}; -use parking_lot::Mutex; use std::convert::TryFrom; use std::{ cell::RefCell, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, - Arc, + Arc, Mutex, }, thread, time::Duration, @@ -313,11 +312,11 @@ impl AsyncCommitFilterer { rayon_core::spawn(move || { // Only 1 thread can filter at a time - let _c = cur_thread_mutex.lock(); - let _p = prev_thread_mutex.lock(); + let _c = cur_thread_mutex.lock().unwrap(); + let _p = prev_thread_mutex.lock().unwrap(); filter_finished.store(false, Ordering::Relaxed); filter_count.store(0, Ordering::Relaxed); - filtered_commits.lock().clear(); + filtered_commits.lock().unwrap().clear(); let mut cur_index: usize = 0; loop { match rx.try_recv() { @@ -357,8 +356,9 @@ impl AsyncCommitFilterer { filtered.len(), Ordering::Relaxed, ); - let mut fc = - filtered_commits.lock(); + let mut fc = filtered_commits + .lock() + .unwrap(); fc.append(&mut filtered); drop(fc); cur_index += SLICE_SIZE; @@ -411,7 +411,7 @@ impl AsyncCommitFilterer { amount: usize, message_length_limit: usize, ) -> Result> { - let fc = self.filtered_commits.lock(); + let fc = self.filtered_commits.lock().unwrap(); let len = fc.len(); let min = start.min(len); let max = min + amount; From efa44c2584a3f959ac03c7fba95483675b4cfc0d Mon Sep 17 00:00:00 2001 From: Anton Rapetov Date: Sun, 25 Apr 2021 14:58:03 -0400 Subject: [PATCH 094/118] Return original formating in KeyConfig::get_hint in src/keys.rs --- src/keys.rs | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index ce88736657..d8757205bf 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -218,21 +218,27 @@ impl KeyConfig { | KeyCode::BackTab | KeyCode::Delete | KeyCode::Insert - | KeyCode::Esc => format!( - "{}{}", - Self::get_modifier_hint(ev.modifiers), - self.get_key_symbol(ev.code) - ), - KeyCode::Char(c) => format!( - "{}{}", - Self::get_modifier_hint(ev.modifiers), - c - ), - KeyCode::F(u) => format!( - "{}F{}", - Self::get_modifier_hint(ev.modifiers), - u - ), + | KeyCode::Esc => { + format!( + "{}{}", + Self::get_modifier_hint(ev.modifiers), + self.get_key_symbol(ev.code) + ) + } + KeyCode::Char(c) => { + format!( + "{}{}", + Self::get_modifier_hint(ev.modifiers), + c + ) + } + KeyCode::F(u) => { + format!( + "{}F{}", + Self::get_modifier_hint(ev.modifiers), + u + ) + } KeyCode::Null => Self::get_modifier_hint(ev.modifiers), } } From 47cdc0934e22b7eb09d0bc2cd618b2e9d8d75776 Mon Sep 17 00:00:00 2001 From: Anton Rapetov Date: Sat, 1 May 2021 20:02:34 -0400 Subject: [PATCH 095/118] Remove used AsyncCommitFilterer::filter_strings field --- src/components/utils/async_commit_filter.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 12a313a453..f8e2ac2ac9 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -74,7 +74,6 @@ pub enum FilterStatus { pub struct AsyncCommitFilterer { git_log: AsyncLog, git_tags: AsyncTags, - filter_strings: Vec>, filtered_commits: Arc>>, filter_count: Arc, filter_finished: Arc, @@ -91,7 +90,6 @@ impl AsyncCommitFilterer { sender: &Sender, ) -> Self { Self { - filter_strings: Vec::new(), git_log, git_tags, filtered_commits: Arc::new(Mutex::new(Vec::new())), @@ -289,8 +287,6 @@ impl AsyncCommitFilterer { ) -> Result<()> { self.stop_filter(); - self.filter_strings = filter_strings.clone(); - let filtered_commits = Arc::clone(&self.filtered_commits); let filter_count = Arc::clone(&self.filter_count); let async_log = self.git_log.clone(); From 0afcc9e0b6e7ca2482bc75ac838bbf1d294c3cf9 Mon Sep 17 00:00:00 2001 From: Anton Rapetov Date: Sat, 1 May 2021 22:24:59 -0400 Subject: [PATCH 096/118] Reformat with rustfmt --- asyncgit/src/sync/commits_info.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs index 94b921de1d..0858eb3573 100644 --- a/asyncgit/src/sync/commits_info.rs +++ b/asyncgit/src/sync/commits_info.rs @@ -5,7 +5,9 @@ use scopetime::scope_time; use unicode_truncate::UnicodeTruncateStr; /// identifies a single commit -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +#[derive( + Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, +)] pub struct CommitId(Oid); impl CommitId { From 38eeeeb5174917c6c3e5910ac25756c42fa5ca2b Mon Sep 17 00:00:00 2001 From: Anton Rapetov Date: Sat, 8 May 2021 11:06:15 -0400 Subject: [PATCH 097/118] Fix clippy errors --- src/components/utils/async_commit_filter.rs | 16 ++++++++++------ src/tabs/revlog.rs | 12 +++++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index f8e2ac2ac9..aaea4468f4 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Error, Result}; use asyncgit::{ sync::{self, CommitId, CommitInfo, Tags}, AsyncLog, AsyncNotification, AsyncTags, CWD, @@ -308,11 +308,12 @@ impl AsyncCommitFilterer { rayon_core::spawn(move || { // Only 1 thread can filter at a time - let _c = cur_thread_mutex.lock().unwrap(); - let _p = prev_thread_mutex.lock().unwrap(); + let _c = cur_thread_mutex.lock().expect("mutex poisoned"); + let _p = + prev_thread_mutex.lock().expect("mutex poisoned"); filter_finished.store(false, Ordering::Relaxed); filter_count.store(0, Ordering::Relaxed); - filtered_commits.lock().unwrap().clear(); + filtered_commits.lock().expect("mutex poisoned").clear(); let mut cur_index: usize = 0; loop { match rx.try_recv() { @@ -354,7 +355,7 @@ impl AsyncCommitFilterer { ); let mut fc = filtered_commits .lock() - .unwrap(); + .expect("mutex poisoned"); fc.append(&mut filtered); drop(fc); cur_index += SLICE_SIZE; @@ -407,7 +408,10 @@ impl AsyncCommitFilterer { amount: usize, message_length_limit: usize, ) -> Result> { - let fc = self.filtered_commits.lock().unwrap(); + let fc = self + .filtered_commits + .lock() + .map_err(|_| Error::msg("mutex poisoned"))?; let len = fc.len(); let min = start.min(len); let max = min + amount; diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index a5a99205f4..307c85e4f8 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -221,7 +221,7 @@ impl Revlog { let mut and_vec = Vec::new(); for or in filter_by_str.split("||") { for split_sub in or.split("&&").map(str::trim) { - if !split_sub.starts_with(":") { + if !split_sub.starts_with(':') { and_vec.push(( split_sub.to_string(), FilterBy::everywhere(), @@ -230,12 +230,14 @@ impl Revlog { } let mut split_str = split_sub.splitn(2, ' '); - let first = split_str.next().unwrap(); + let first = split_str + .next() + .expect("Split must return at least one element"); let mut to_filter_by = first.chars().skip(1).fold( FilterBy::empty(), |acc, ch| { acc | FilterBy::try_from(ch) - .unwrap_or(FilterBy::empty()) + .unwrap_or_else(|_| FilterBy::empty()) }, ); @@ -246,7 +248,7 @@ impl Revlog { and_vec.push(( split_str .next() - .unwrap_or(&"") + .unwrap_or("") .trim_start() .to_string(), to_filter_by, @@ -265,7 +267,7 @@ impl Revlog { Self::pre_process_string(filter_by.to_string()); let trimmed_string = pre_processed_string.trim().to_string(); - if filter_by == "" { + if filter_by.is_empty() { self.async_filter.stop_filter(); self.is_filtering = false; } else { From c98622122b7a7df6a520aad26e0215800435c09d Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sat, 10 Jun 2023 18:31:35 -0300 Subject: [PATCH 098/118] fix help command triggering while searching --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index dabee812a6..d0f3107f37 100644 --- a/src/app.rs +++ b/src/app.rs @@ -607,8 +607,8 @@ impl App { tags_popup, reset_popup, options_popup, - help, revlog, + help, status_tab, files_tab, stashing_tab, From ff8743f87dafb390af784ffe0903a988f7df7345 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sat, 10 Jun 2023 18:36:34 -0300 Subject: [PATCH 099/118] cargo fmt --- src/app.rs | 6 +- src/components/commitlist.rs | 12 +- src/components/find_commit.rs | 184 +++-- src/components/mod.rs | 4 +- src/components/utils/async_commit_filter.rs | 790 ++++++++++--------- src/components/utils/mod.rs | 2 +- src/keys/key_list.rs | 2 +- src/queue.rs | 4 +- src/strings.rs | 10 +- src/tabs/revlog.rs | 810 ++++++++++---------- src/tabs/stashlist.rs | 2 +- 11 files changed, 926 insertions(+), 900 deletions(-) diff --git a/src/app.rs b/src/app.rs index d0f3107f37..15f0363652 100644 --- a/src/app.rs +++ b/src/app.rs @@ -892,9 +892,9 @@ impl App { self.push_tags_popup.push_tags()?; flags.insert(NeedsUpdate::ALL); } - InternalEvent::FilterLog(string_to_filter_by) => { - self.revlog.filter(&string_to_filter_by)? - } + InternalEvent::FilterLog(string_to_filter_by) => { + self.revlog.filter(&string_to_filter_by)?; + } InternalEvent::StatusLastFileMoved => { self.status_tab.last_file_moved()?; } diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 394d5cd4ea..38591e884d 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -95,12 +95,12 @@ impl CommitList { self.current_size.get() } - /// - pub fn set_total_count(&mut self, count: usize) { - self.count_total = count; - self.selection = - cmp::min(self.selection, self.selection_max()); - } + /// + pub fn set_total_count(&mut self, count: usize) { + self.count_total = count; + self.selection = + cmp::min(self.selection, self.selection_max()); + } /// #[allow(clippy::missing_const_for_fn)] diff --git a/src/components/find_commit.rs b/src/components/find_commit.rs index 8101e97177..93891d270d 100644 --- a/src/components/find_commit.rs +++ b/src/components/find_commit.rs @@ -1,119 +1,117 @@ use super::{ - textinput::TextInputComponent, CommandBlocking, CommandInfo, - Component, DrawableComponent, EventState, + textinput::TextInputComponent, CommandBlocking, CommandInfo, + Component, DrawableComponent, EventState, }; use crate::{ - keys::{SharedKeyConfig, key_match}, - queue::{InternalEvent, Queue}, - strings, - ui::style::SharedTheme, + keys::{key_match, SharedKeyConfig}, + queue::{InternalEvent, Queue}, + strings, + ui::style::SharedTheme, }; use anyhow::Result; use crossterm::event::Event; use ratatui::{backend::Backend, layout::Rect, Frame}; pub struct FindCommitComponent { - input: TextInputComponent, - queue: Queue, - is_focused: bool, - visible: bool, - key_config: SharedKeyConfig, + input: TextInputComponent, + queue: Queue, + is_focused: bool, + visible: bool, + key_config: SharedKeyConfig, } impl DrawableComponent for FindCommitComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - self.input.draw(f, rect)?; - Ok(()) - } + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + self.input.draw(f, rect)?; + Ok(()) + } } impl Component for FindCommitComponent { - fn commands( - &self, - _out: &mut Vec, - _force_all: bool, - ) -> CommandBlocking { - CommandBlocking::PassingOn - } + fn commands( + &self, + _out: &mut Vec, + _force_all: bool, + ) -> CommandBlocking { + CommandBlocking::PassingOn + } - fn event(&mut self, ev: &Event) -> Result { - if self.is_visible() && self.focused() { - if let Event::Key(e) = ev { - if key_match(e, self.key_config.keys.exit_popup) { - // Prevent text input closing - self.focus(false); - self.visible = false; - return Ok(EventState::Consumed); - } - } - if self.input.event(ev)?.is_consumed() { - self.queue.push( - InternalEvent::FilterLog( - self.input.get_text().to_string(), - ), - ); - return Ok(EventState::Consumed); - } - } - Ok(EventState::NotConsumed) - } + fn event(&mut self, ev: &Event) -> Result { + if self.is_visible() && self.focused() { + if let Event::Key(e) = ev { + if key_match(e, self.key_config.keys.exit_popup) { + // Prevent text input closing + self.focus(false); + self.visible = false; + return Ok(EventState::Consumed); + } + } + if self.input.event(ev)?.is_consumed() { + self.queue.push(InternalEvent::FilterLog( + self.input.get_text().to_string(), + )); + return Ok(EventState::Consumed); + } + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } - fn show(&mut self) -> Result<()> { - self.visible = true; - Ok(()) - } + fn hide(&mut self) { + self.visible = false; + } + fn show(&mut self) -> Result<()> { + self.visible = true; + Ok(()) + } - fn focus(&mut self, focus: bool) { - self.is_focused = focus; - } + fn focus(&mut self, focus: bool) { + self.is_focused = focus; + } - fn focused(&self) -> bool { - self.is_focused - } + fn focused(&self) -> bool { + self.is_focused + } - fn toggle_visible(&mut self) -> Result<()> { - self.visible = !self.visible; - Ok(()) - } + fn toggle_visible(&mut self) -> Result<()> { + self.visible = !self.visible; + Ok(()) + } } impl FindCommitComponent { - /// - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - let mut input_component = TextInputComponent::new( - theme, - key_config.clone(), - &strings::find_commit_title(&key_config), - &strings::find_commit_msg(&key_config), - false, - ); - input_component.show().expect("Will not error"); - input_component.embed(); - Self { - queue, - input: input_component, - key_config, - visible: false, - is_focused: false, - } - } + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + let mut input_component = TextInputComponent::new( + theme, + key_config.clone(), + &strings::find_commit_title(&key_config), + &strings::find_commit_msg(&key_config), + false, + ); + input_component.show().expect("Will not error"); + input_component.embed(); + Self { + queue, + input: input_component, + key_config, + visible: false, + is_focused: false, + } + } - pub fn clear_input(&mut self) { - self.input.clear(); - } + pub fn clear_input(&mut self) { + self.input.clear(); + } } diff --git a/src/components/mod.rs b/src/components/mod.rs index 19cab16050..f0fd16b9e8 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -11,10 +11,10 @@ mod create_branch; mod cred; mod diff; mod externaleditor; -mod find_commit; mod fetch; mod file_find_popup; mod file_revlog; +mod find_commit; mod help; mod inspect_commit; mod msg; @@ -49,10 +49,10 @@ pub use compare_commits::CompareCommitsComponent; pub use create_branch::CreateBranchComponent; pub use diff::DiffComponent; pub use externaleditor::ExternalEditorComponent; -pub use find_commit::FindCommitComponent; pub use fetch::FetchComponent; pub use file_find_popup::FileFindPopup; pub use file_revlog::{FileRevOpen, FileRevlogComponent}; +pub use find_commit::FindCommitComponent; pub use help::HelpComponent; pub use inspect_commit::{InspectCommitComponent, InspectCommitOpen}; pub use msg::MsgComponent; diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 0539ad717f..fd0c787bd3 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -1,444 +1,470 @@ use anyhow::{Error, Result}; use asyncgit::{ - sync::{self, CommitInfo, Tags, RepoPathRef}, - AsyncLog, AsyncTags, AsyncGitNotification, + sync::{self, CommitInfo, RepoPathRef, Tags}, + AsyncGitNotification, AsyncLog, AsyncTags, }; use bitflags::bitflags; use crossbeam_channel::{Sender, TryRecvError}; use std::convert::TryFrom; use std::{ - cell::RefCell, - sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering}, - Arc, Mutex, - }, - thread, - time::Duration, + cell::RefCell, + sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, Mutex, + }, + thread, + time::Duration, }; use unicode_truncate::UnicodeTruncateStr; const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(10); const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = - Duration::from_millis(500); + Duration::from_millis(500); const SLICE_SIZE: usize = 1200; bitflags! { - pub struct FilterBy: u32 { - const SHA = 0b0000_0001; - const AUTHOR = 0b0000_0010; - const MESSAGE = 0b0000_0100; - const NOT = 0b0000_1000; - const CASE_SENSITIVE = 0b0001_0000; - const TAGS = 0b0010_0000; - } + pub struct FilterBy: u32 { + const SHA = 0b0000_0001; + const AUTHOR = 0b0000_0010; + const MESSAGE = 0b0000_0100; + const NOT = 0b0000_1000; + const CASE_SENSITIVE = 0b0001_0000; + const TAGS = 0b0010_0000; + } } impl FilterBy { - pub fn everywhere() -> Self { - Self::all() & !Self::NOT & !Self::CASE_SENSITIVE - } + pub fn everywhere() -> Self { + Self::all() & !Self::NOT & !Self::CASE_SENSITIVE + } - pub fn exclude_modifiers(self) -> Self { - self & !Self::NOT & !Self::CASE_SENSITIVE - } + pub fn exclude_modifiers(self) -> Self { + self & !Self::NOT & !Self::CASE_SENSITIVE + } } impl Default for FilterBy { - fn default() -> Self { - Self::all() & !Self::NOT & !Self::CASE_SENSITIVE - } + fn default() -> Self { + Self::all() & !Self::NOT & !Self::CASE_SENSITIVE + } } impl TryFrom for FilterBy { - type Error = anyhow::Error; + type Error = anyhow::Error; - fn try_from(v: char) -> Result { - match v { - 's' => Ok(Self::SHA), - 'a' => Ok(Self::AUTHOR), - 'm' => Ok(Self::MESSAGE), - '!' => Ok(Self::NOT), - 'c' => Ok(Self::CASE_SENSITIVE), - 't' => Ok(Self::TAGS), - _ => Err(anyhow::anyhow!(format!("Unknown flag: {}", v))), - } - } + fn try_from(v: char) -> Result { + match v { + 's' => Ok(Self::SHA), + 'a' => Ok(Self::AUTHOR), + 'm' => Ok(Self::MESSAGE), + '!' => Ok(Self::NOT), + 'c' => Ok(Self::CASE_SENSITIVE), + 't' => Ok(Self::TAGS), + _ => Err(anyhow::anyhow!(format!("Unknown flag: {}", v))), + } + } } #[derive(PartialEq)] pub enum FilterStatus { - Filtering, - Finished, + Filtering, + Finished, } pub struct AsyncCommitFilterer { - repo: RepoPathRef, - git_log: AsyncLog, - git_tags: AsyncTags, - filtered_commits: Arc>>, - filter_count: Arc, - filter_finished: Arc, - is_pending_local: RefCell, - filter_thread_sender: Option>, - filter_thread_mutex: Arc>, - sender: Sender, + repo: RepoPathRef, + git_log: AsyncLog, + git_tags: AsyncTags, + filtered_commits: Arc>>, + filter_count: Arc, + filter_finished: Arc, + is_pending_local: RefCell, + filter_thread_sender: Option>, + filter_thread_mutex: Arc>, + sender: Sender, } impl AsyncCommitFilterer { - pub fn new( + pub fn new( repo: RepoPathRef, - git_log: AsyncLog, - git_tags: AsyncTags, - sender: &Sender, - ) -> Self { - Self { - repo, - git_log, - git_tags, - filtered_commits: Arc::new(Mutex::new(Vec::new())), - filter_count: Arc::new(AtomicUsize::new(0)), - filter_finished: Arc::new(AtomicBool::new(false)), - filter_thread_mutex: Arc::new(Mutex::new(())), - is_pending_local: RefCell::new(false), - filter_thread_sender: None, - sender: sender.clone(), - } - } + git_log: AsyncLog, + git_tags: AsyncTags, + sender: &Sender, + ) -> Self { + Self { + repo, + git_log, + git_tags, + filtered_commits: Arc::new(Mutex::new(Vec::new())), + filter_count: Arc::new(AtomicUsize::new(0)), + filter_finished: Arc::new(AtomicBool::new(false)), + filter_thread_mutex: Arc::new(Mutex::new(())), + is_pending_local: RefCell::new(false), + filter_thread_sender: None, + sender: sender.clone(), + } + } - pub fn is_pending(&self) -> bool { - let mut b = self.is_pending_local.borrow_mut(); - if *b { - *b = self.fetch() == FilterStatus::Filtering; - *b - } else { - false - } - } + pub fn is_pending(&self) -> bool { + let mut b = self.is_pending_local.borrow_mut(); + if *b { + *b = self.fetch() == FilterStatus::Filtering; + *b + } else { + false + } + } - /// `filter_strings` should be split by or them and, for example, - /// - /// A || B && C && D || E - /// - /// would be - /// - /// vec [vec![A], vec![B, C, D], vec![E]] - #[allow(clippy::too_many_lines)] - pub fn filter( - mut vec_commit_info: Vec, - tags: &Option, - filter_strings: &[Vec<(String, FilterBy)>], - ) -> Vec { - vec_commit_info - .drain(..) - .filter(|commit| { - for to_and in filter_strings { - let mut is_and = true; - for (s, filter) in to_and { - if filter.contains(FilterBy::CASE_SENSITIVE) { - is_and = if filter.contains(FilterBy::NOT) - { - (filter - .contains(FilterBy::TAGS) - && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(true, |commit_tags| commit_tags.iter().filter(|tag|{ + /// `filter_strings` should be split by or them and, for example, + /// + /// A || B && C && D || E + /// + /// would be + /// + /// vec [vec![A], vec![B, C, D], vec![E]] + #[allow(clippy::too_many_lines)] + pub fn filter( + mut vec_commit_info: Vec, + tags: &Option, + filter_strings: &[Vec<(String, FilterBy)>], + ) -> Vec { + vec_commit_info + .drain(..) + .filter(|commit| { + for to_and in filter_strings { + let mut is_and = true; + for (s, filter) in to_and { + if filter.contains(FilterBy::CASE_SENSITIVE) { + is_and = + if filter.contains(FilterBy::NOT) { + (filter.contains(FilterBy::TAGS) + && tags.as_ref().map_or( + false, + |t| { + t.get(&commit.id) + .map_or( + true, + |commit_tags| { + commit_tags.iter().filter(|tag|{ !tag.name.contains(s) - }).count() > 0))) - || (filter - .contains(FilterBy::SHA) - && !commit - .id - .to_string() - .contains(s)) - || (filter.contains( - FilterBy::AUTHOR, - ) && !commit - .author - .contains(s)) - || (filter.contains( - FilterBy::MESSAGE, - ) && !commit - .message - .contains(s)) - } else { - (filter - .contains(FilterBy::TAGS) - && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag|{ + }).count() > 0 + }, + ) + }, + )) || (filter + .contains(FilterBy::SHA) + && !commit + .id + .to_string() + .contains(s)) || (filter + .contains(FilterBy::AUTHOR) + && !commit.author.contains(s)) + || (filter.contains( + FilterBy::MESSAGE, + ) && !commit + .message + .contains(s)) + } else { + (filter.contains(FilterBy::TAGS) + && tags.as_ref().map_or( + false, + |t| { + t.get(&commit.id) + .map_or( + false, + |commit_tags| { + commit_tags.iter().filter(|tag|{ tag.name.contains(s) - }).count() > 0))) - || (filter - .contains(FilterBy::SHA) - && commit - .id - .to_string() - .contains(s)) - || (filter.contains( - FilterBy::AUTHOR, - ) && commit - .author - .contains(s)) - || (filter.contains( - FilterBy::MESSAGE, - ) && commit - .message - .contains(s)) - } - } else { - is_and = if filter.contains(FilterBy::NOT) - { - (filter - .contains(FilterBy::TAGS) - && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(true, |commit_tags| commit_tags.iter().filter(|tag|{ - !tag.name.to_lowercase().contains(&s.to_lowercase()) - }).count() > 0))) - || (filter - .contains(FilterBy::SHA) - && !commit - .id - .to_string() - .to_lowercase() - .contains( - &s.to_lowercase(), - )) - || (filter.contains( - FilterBy::AUTHOR, - ) && !commit - .author - .to_lowercase() - .contains( - &s.to_lowercase(), - )) - || (filter.contains( - FilterBy::MESSAGE, - ) && !commit - .message - .to_lowercase() - .contains( - &s.to_lowercase(), - )) - } else { - (filter - .contains(FilterBy::TAGS) - && tags.as_ref().map_or(false, |t| t.get(&commit.id).map_or(false, |commit_tags| commit_tags.iter().filter(|tag|{ - tag.name.to_lowercase().contains(&s.to_lowercase()) - }).count() > 0))) - || (filter - .contains(FilterBy::SHA) - && commit - .id - .to_string() - .to_lowercase() - .contains( - &s.to_lowercase(), - )) - || (filter.contains( - FilterBy::AUTHOR, - ) && commit - .author - .to_lowercase() - .contains( - &s.to_lowercase(), - )) - || (filter.contains( - FilterBy::MESSAGE, - ) && commit - .message - .to_lowercase() - .contains( - &s.to_lowercase(), - )) - } - } - } - if is_and { - return true; - } - } - false - }) - .collect() - } + }).count() > 0 + }, + ) + }, + )) || (filter + .contains(FilterBy::SHA) + && commit + .id + .to_string() + .contains(s)) || (filter + .contains(FilterBy::AUTHOR) + && commit.author.contains(s)) + || (filter.contains( + FilterBy::MESSAGE, + ) && commit + .message + .contains(s)) + } + } else { + is_and = if filter.contains(FilterBy::NOT) + { + (filter.contains(FilterBy::TAGS) + && tags.as_ref().map_or( + false, + |t| { + t.get(&commit.id).map_or( + true, + |commit_tags| { + commit_tags + .iter() + .filter( + |tag| { + !tag.name.to_lowercase().contains(&s.to_lowercase()) + }, + ) + .count() > 0 + }, + ) + }, + )) || (filter.contains(FilterBy::SHA) + && !commit + .id + .to_string() + .to_lowercase() + .contains(&s.to_lowercase())) + || (filter + .contains(FilterBy::AUTHOR) + && !commit + .author + .to_lowercase() + .contains( + &s.to_lowercase(), + )) || (filter + .contains(FilterBy::MESSAGE) + && !commit + .message + .to_lowercase() + .contains(&s.to_lowercase())) + } else { + (filter.contains(FilterBy::TAGS) + && tags.as_ref().map_or( + false, + |t| { + t.get(&commit.id).map_or( + false, + |commit_tags| { + commit_tags + .iter() + .filter( + |tag| { + tag.name.to_lowercase().contains(&s.to_lowercase()) + }, + ) + .count() > 0 + }, + ) + }, + )) || (filter.contains(FilterBy::SHA) + && commit + .id + .to_string() + .to_lowercase() + .contains(&s.to_lowercase())) + || (filter + .contains(FilterBy::AUTHOR) + && commit + .author + .to_lowercase() + .contains( + &s.to_lowercase(), + )) || (filter + .contains(FilterBy::MESSAGE) + && commit + .message + .to_lowercase() + .contains(&s.to_lowercase())) + } + } + } + if is_and { + return true; + } + } + false + }) + .collect() + } - /// If the filtering string contain filtering by tags - /// return them, else don't get the tags - fn get_tags( - filter_strings: &[Vec<(String, FilterBy)>], - git_tags: &mut AsyncTags, - ) -> Result> { - let mut contains_tags = false; - for or in filter_strings { - for (_, filter_by) in or { - if filter_by.contains(FilterBy::TAGS) { - contains_tags = true; - break; - } - } - if contains_tags { - break; - } - } + /// If the filtering string contain filtering by tags + /// return them, else don't get the tags + fn get_tags( + filter_strings: &[Vec<(String, FilterBy)>], + git_tags: &mut AsyncTags, + ) -> Result> { + let mut contains_tags = false; + for or in filter_strings { + for (_, filter_by) in or { + if filter_by.contains(FilterBy::TAGS) { + contains_tags = true; + break; + } + } + if contains_tags { + break; + } + } - if contains_tags { - return git_tags.last().map_err(|e| anyhow::anyhow!(e)); - } - Ok(None) - } + if contains_tags { + return git_tags.last().map_err(|e| anyhow::anyhow!(e)); + } + Ok(None) + } - pub fn start_filter( - &mut self, - filter_strings: Vec>, - ) -> Result<()> { - self.stop_filter(); + pub fn start_filter( + &mut self, + filter_strings: Vec>, + ) -> Result<()> { + self.stop_filter(); - let filtered_commits = Arc::clone(&self.filtered_commits); - let filter_count = Arc::clone(&self.filter_count); - let async_log = self.git_log.clone(); - let filter_finished = Arc::clone(&self.filter_finished); + let filtered_commits = Arc::clone(&self.filtered_commits); + let filter_count = Arc::clone(&self.filter_count); + let async_log = self.git_log.clone(); + let filter_finished = Arc::clone(&self.filter_finished); - let (tx, rx) = crossbeam_channel::unbounded(); + let (tx, rx) = crossbeam_channel::unbounded(); - self.filter_thread_sender = Some(tx); - let async_app_sender = self.sender.clone(); + self.filter_thread_sender = Some(tx); + let async_app_sender = self.sender.clone(); - let prev_thread_mutex = Arc::clone(&self.filter_thread_mutex); - self.filter_thread_mutex = Arc::new(Mutex::new(())); + let prev_thread_mutex = Arc::clone(&self.filter_thread_mutex); + self.filter_thread_mutex = Arc::new(Mutex::new(())); - let cur_thread_mutex = Arc::clone(&self.filter_thread_mutex); - self.is_pending_local.replace(true); + let cur_thread_mutex = Arc::clone(&self.filter_thread_mutex); + self.is_pending_local.replace(true); - let tags = - Self::get_tags(&filter_strings, &mut self.git_tags)?; + let tags = + Self::get_tags(&filter_strings, &mut self.git_tags)?; - let repo = self.repo.clone(); + let repo = self.repo.clone(); - rayon_core::spawn(move || { - // Only 1 thread can filter at a time - let _c = cur_thread_mutex.lock().expect("mutex poisoned"); - let _p = - prev_thread_mutex.lock().expect("mutex poisoned"); - filter_finished.store(false, Ordering::Relaxed); - filter_count.store(0, Ordering::Relaxed); - filtered_commits.lock().expect("mutex poisoned").clear(); - let mut cur_index: usize = 0; - loop { - match rx.try_recv() { - Ok(_) | Err(TryRecvError::Disconnected) => { - break; - } - _ => { - // Get the git_log and start filtering through it - match async_log - .get_slice(cur_index, SLICE_SIZE) - { - Ok(ids) => { - match sync::get_commits_info( - &repo.borrow(), - &ids, - usize::MAX, - ) { - Ok(v) => { - if v.is_empty() - && !async_log.is_pending() - { - // Assume finished if log not pending and 0 recieved - filter_finished.store( - true, - Ordering::Relaxed, - ); - break; - } + rayon_core::spawn(move || { + // Only 1 thread can filter at a time + let _c = cur_thread_mutex.lock().expect("mutex poisoned"); + let _p = + prev_thread_mutex.lock().expect("mutex poisoned"); + filter_finished.store(false, Ordering::Relaxed); + filter_count.store(0, Ordering::Relaxed); + filtered_commits.lock().expect("mutex poisoned").clear(); + let mut cur_index: usize = 0; + loop { + match rx.try_recv() { + Ok(_) | Err(TryRecvError::Disconnected) => { + break; + } + _ => { + // Get the git_log and start filtering through it + match async_log + .get_slice(cur_index, SLICE_SIZE) + { + Ok(ids) => { + match sync::get_commits_info( + &repo.borrow(), + &ids, + usize::MAX, + ) { + Ok(v) => { + if v.is_empty() + && !async_log.is_pending() + { + // Assume finished if log not pending and 0 recieved + filter_finished.store( + true, + Ordering::Relaxed, + ); + break; + } - let mut filtered = - Self::filter( - v, - &tags, - &filter_strings, - ); - filter_count.fetch_add( - filtered.len(), - Ordering::Relaxed, - ); - let mut fc = filtered_commits - .lock() - .expect("mutex poisoned"); - fc.append(&mut filtered); - drop(fc); - cur_index += SLICE_SIZE; - async_app_sender + let mut filtered = + Self::filter( + v, + &tags, + &filter_strings, + ); + filter_count.fetch_add( + filtered.len(), + Ordering::Relaxed, + ); + let mut fc = filtered_commits + .lock() + .expect("mutex poisoned"); + fc.append(&mut filtered); + drop(fc); + cur_index += SLICE_SIZE; + async_app_sender .send(AsyncGitNotification::Log) .expect("error sending"); - thread::sleep( - FILTER_SLEEP_DURATION, - ); - } - Err(_) => { - // Failed to get commit info - thread::sleep( + thread::sleep( + FILTER_SLEEP_DURATION, + ); + } + Err(_) => { + // Failed to get commit info + thread::sleep( FILTER_SLEEP_DURATION_FAILED_LOCK, ); - } - } - } - Err(_) => { - // Failed to get slice - thread::sleep( - FILTER_SLEEP_DURATION_FAILED_LOCK, - ); - } - } - } - } - } - }); - Ok(()) - } + } + } + } + Err(_) => { + // Failed to get slice + thread::sleep( + FILTER_SLEEP_DURATION_FAILED_LOCK, + ); + } + } + } + } + } + }); + Ok(()) + } - /// Stop the filter if one was running, otherwise does nothing. - /// Is it possible to restart from this stage by calling restart - pub fn stop_filter(&self) { - // Any error this gives can be safely ignored, - // it will send if reciever exists, otherwise does nothing - if let Some(sender) = &self.filter_thread_sender { - match sender.try_send(true) { - Ok(_) | Err(_) => {} - }; - } - self.is_pending_local.replace(false); - self.filter_finished.store(true, Ordering::Relaxed); - } + /// Stop the filter if one was running, otherwise does nothing. + /// Is it possible to restart from this stage by calling restart + pub fn stop_filter(&self) { + // Any error this gives can be safely ignored, + // it will send if reciever exists, otherwise does nothing + if let Some(sender) = &self.filter_thread_sender { + match sender.try_send(true) { + Ok(_) | Err(_) => {} + }; + } + self.is_pending_local.replace(false); + self.filter_finished.store(true, Ordering::Relaxed); + } - pub fn get_filter_items( - &mut self, - start: usize, - amount: usize, - message_length_limit: usize, - ) -> Result> { - let fc = self - .filtered_commits - .lock() - .map_err(|_| Error::msg("mutex poisoned"))?; - let len = fc.len(); - let min = start.min(len); - let max = min + amount; - let max = max.min(len); - let mut commits_requested = fc[min..max].to_vec(); - for c in &mut commits_requested { - c.message = c - .message - .unicode_truncate(message_length_limit) - .0 - .to_owned(); - } - Ok(commits_requested) - } + pub fn get_filter_items( + &mut self, + start: usize, + amount: usize, + message_length_limit: usize, + ) -> Result> { + let fc = self + .filtered_commits + .lock() + .map_err(|_| Error::msg("mutex poisoned"))?; + let len = fc.len(); + let min = start.min(len); + let max = min + amount; + let max = max.min(len); + let mut commits_requested = fc[min..max].to_vec(); + for c in &mut commits_requested { + c.message = c + .message + .unicode_truncate(message_length_limit) + .0 + .to_owned(); + } + Ok(commits_requested) + } - pub fn count(&self) -> usize { - self.filter_count.load(Ordering::Relaxed) - } + pub fn count(&self) -> usize { + self.filter_count.load(Ordering::Relaxed) + } - pub fn fetch(&self) -> FilterStatus { - if self.filter_finished.load(Ordering::Relaxed) { - FilterStatus::Finished - } else { - FilterStatus::Filtering - } - } + pub fn fetch(&self) -> FilterStatus { + if self.filter_finished.load(Ordering::Relaxed) { + FilterStatus::Finished + } else { + FilterStatus::Filtering + } + } } diff --git a/src/components/utils/mod.rs b/src/components/utils/mod.rs index 1e5c8556df..d4291c7acc 100644 --- a/src/components/utils/mod.rs +++ b/src/components/utils/mod.rs @@ -1,6 +1,7 @@ use chrono::{DateTime, Local, NaiveDateTime, Utc}; use unicode_width::UnicodeWidthStr; +pub mod async_commit_filter; #[cfg(feature = "ghemoji")] pub mod emoji; pub mod filetree; @@ -8,7 +9,6 @@ pub mod logitems; pub mod scroll_horizontal; pub mod scroll_vertical; pub mod statustree; -pub mod async_commit_filter; /// macro to simplify running code that might return Err. /// It will show a popup in that case diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs index a43705890b..aaac5b94ec 100644 --- a/src/keys/key_list.rs +++ b/src/keys/key_list.rs @@ -50,7 +50,7 @@ pub struct KeysList { pub exit_popup: GituiKeyEvent, pub open_commit: GituiKeyEvent, pub open_commit_editor: GituiKeyEvent, - pub show_find_commit_text_input: GituiKeyEvent, + pub show_find_commit_text_input: GituiKeyEvent, pub open_help: GituiKeyEvent, pub open_options: GituiKeyEvent, pub move_left: GituiKeyEvent, diff --git a/src/queue.rs b/src/queue.rs index 15f11d16c2..773b545dda 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -108,8 +108,8 @@ pub enum InternalEvent { Pull(String), /// PushTags, - /// - FilterLog(String), + /// + FilterLog(String), /// OptionSwitched(AppOption), /// diff --git a/src/strings.rs b/src/strings.rs index 8b4a8e9fdd..39de2ae06f 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -289,10 +289,10 @@ pub fn file_log_title( format!("Revisions of '{file_path}' ({selected}/{revisions})") } pub fn find_commit_title(_key_config: &SharedKeyConfig) -> String { - "Find Commit".to_string() + "Find Commit".to_string() } pub fn find_commit_msg(_key_config: &SharedKeyConfig) -> String { - "Search Sha, Author and Message".to_string() + "Search Sha, Author and Message".to_string() } pub fn blame_title(_key_config: &SharedKeyConfig) -> String { "Blame".to_string() @@ -610,8 +610,8 @@ pub mod commands { ) } - pub fn find_commit(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( + pub fn find_commit(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( format!( "Find Commit [{}]", key_config @@ -620,7 +620,7 @@ pub mod commands { "show find commit box to search by sha, author or message", CMD_GROUP_LOG, ) - } + } pub fn diff_home_end( key_config: &SharedKeyConfig, diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 91fab1303b..558b6ce7fe 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -1,12 +1,12 @@ use crate::{ components::{ - async_commit_filter::{ - AsyncCommitFilterer, FilterBy, FilterStatus, - }, + async_commit_filter::{ + AsyncCommitFilterer, FilterBy, FilterStatus, + }, visibility_blocking, CommandBlocking, CommandInfo, CommitDetailsComponent, CommitList, Component, DrawableComponent, EventState, FileTreeOpen, - InspectCommitOpen, FindCommitComponent, + FindCommitComponent, InspectCommitOpen, }, keys::{key_match, SharedKeyConfig}, queue::{InternalEvent, Queue, StackablePopupOpen}, @@ -37,8 +37,8 @@ pub struct Revlog { repo: RepoPathRef, commit_details: CommitDetailsComponent, list: CommitList, - find_commit: FindCommitComponent, - async_filter: AsyncCommitFilterer, + find_commit: FindCommitComponent, + async_filter: AsyncCommitFilterer, git_log: AsyncLog, git_tags: AsyncTags, git_local_branches: AsyncSingleJob, @@ -46,8 +46,8 @@ pub struct Revlog { queue: Queue, visible: bool, key_config: SharedKeyConfig, - is_filtering: bool, - filter_string: String, + is_filtering: bool, + filter_string: String, } impl Revlog { @@ -59,8 +59,8 @@ impl Revlog { theme: SharedTheme, key_config: SharedKeyConfig, ) -> Self { - let log = AsyncLog::new(repo.borrow().clone(), sender, None); - let tags = AsyncTags::new(repo.borrow().clone(), sender); + let log = AsyncLog::new(repo.borrow().clone(), sender, None); + let tags = AsyncTags::new(repo.borrow().clone(), sender); Self { repo: repo.clone(), queue: queue.clone(), @@ -78,25 +78,25 @@ impl Revlog { queue.clone(), key_config.clone(), ), - find_commit: FindCommitComponent::new( - queue.clone(), - theme, - key_config.clone(), - ), - async_filter: AsyncCommitFilterer::new( - repo.clone(), - log.clone(), - tags.clone(), - sender, - ), + find_commit: FindCommitComponent::new( + queue.clone(), + theme, + key_config.clone(), + ), + async_filter: AsyncCommitFilterer::new( + repo.clone(), + log.clone(), + tags.clone(), + sender, + ), git_log: log, git_tags: tags, git_local_branches: AsyncSingleJob::new(sender.clone()), git_remote_branches: AsyncSingleJob::new(sender.clone()), visible: false, key_config, - is_filtering: false, - filter_string: "".to_string(), + is_filtering: false, + filter_string: "".to_string(), } } @@ -104,7 +104,7 @@ impl Revlog { pub fn any_work_pending(&self) -> bool { self.git_log.is_pending() || self.git_tags.is_pending() - || self.async_filter.is_pending() + || self.async_filter.is_pending() || self.git_local_branches.is_pending() || self.git_remote_branches.is_pending() || self.commit_details.any_work_pending() @@ -113,13 +113,13 @@ impl Revlog { /// pub fn update(&mut self) -> Result<()> { if self.is_visible() { - let log_changed = if self.is_filtering { - self.list.set_total_count(self.async_filter.count()); - self.async_filter.fetch() == FilterStatus::Filtering - } else { - self.list.set_total_count(self.git_log.count()?); - self.git_log.fetch()? == FetchStatus::Started - }; + let log_changed = if self.is_filtering { + self.list.set_total_count(self.async_filter.count()); + self.async_filter.fetch() == FilterStatus::Filtering + } else { + self.list.set_total_count(self.git_log.count()?); + self.git_log.fetch()? == FetchStatus::Started + }; let selection = self.list.selection(); let selection_max = self.list.selection_max(); @@ -196,28 +196,28 @@ impl Revlog { let want_min = self.list.selection().saturating_sub(SLICE_SIZE / 2); - let commits = if self.is_filtering { - self.async_filter - .get_filter_items( - want_min, - SLICE_SIZE, - self.list - .current_size() - .map_or(100u16, |size| size.0) - .into(), - ) - .map_err(|e| anyhow::anyhow!(e.to_string())) - } else { - sync::get_commits_info( - &self.repo.borrow(), - &self.git_log.get_slice(want_min, SLICE_SIZE)?, - self.list - .current_size() - .map_or(100u16, |size| size.0) - .into(), - ) - .map_err(|e| anyhow::anyhow!(e.to_string())) - }; + let commits = if self.is_filtering { + self.async_filter + .get_filter_items( + want_min, + SLICE_SIZE, + self.list + .current_size() + .map_or(100u16, |size| size.0) + .into(), + ) + .map_err(|e| anyhow::anyhow!(e.to_string())) + } else { + sync::get_commits_info( + &self.repo.borrow(), + &self.git_log.get_slice(want_min, SLICE_SIZE)?, + self.list + .current_size() + .map_or(100u16, |size| size.0) + .into(), + ) + .map_err(|e| anyhow::anyhow!(e.to_string())) + }; if let Ok(commits) = commits { self.list.items().set_items(want_min, commits); @@ -241,144 +241,144 @@ impl Revlog { }) } - /// Parses search string into individual sub-searches. - /// Each sub-search is a tuple of (string-to-search, flags-where-to-search) - /// - /// Returns vec of vec of sub-searches. - /// Where search results: - /// 1. from outer vec should be combined via 'disjunction' (or); - /// 2. from inter vec should be combined via 'conjunction' (and). - /// - /// Currently parentheses in the `filter_by_str` are not supported. - /// They should be removed by `Self::pre_process_string`. - fn get_what_to_filter_by( - filter_by_str: &str, - ) -> Vec> { - let mut search_vec = Vec::new(); - let mut and_vec = Vec::new(); - for or in filter_by_str.split("||") { - for split_sub in or.split("&&").map(str::trim) { - if !split_sub.starts_with(':') { - and_vec.push(( - split_sub.to_string(), - FilterBy::everywhere(), - )); - continue; - } - - let mut split_str = split_sub.splitn(2, ' '); - let first = split_str - .next() - .expect("Split must return at least one element"); - let mut to_filter_by = first.chars().skip(1).fold( - FilterBy::empty(), - |acc, ch| { - acc | FilterBy::try_from(ch) - .unwrap_or_else(|_| FilterBy::empty()) - }, - ); - - if to_filter_by.exclude_modifiers().is_empty() { - to_filter_by |= FilterBy::everywhere(); - } - - and_vec.push(( - split_str - .next() - .unwrap_or("") - .trim_start() - .to_string(), - to_filter_by, - )); - } - search_vec.push(and_vec.clone()); - and_vec.clear(); - } - search_vec - } - - pub fn filter(&mut self, filter_by: &str) -> Result<()> { - if filter_by != self.filter_string { - self.filter_string = filter_by.to_string(); - let pre_processed_string = - Self::pre_process_string(filter_by.to_string()); - let trimmed_string = - pre_processed_string.trim().to_string(); - if filter_by.is_empty() { - self.async_filter.stop_filter(); - self.is_filtering = false; - } else { - let filter_strings = - Self::get_what_to_filter_by(&trimmed_string); - self.async_filter - .start_filter(filter_strings) - .map_err(|e| anyhow::anyhow!(e.to_string()))?; - self.is_filtering = true; - } - return self.update(); - } - Ok(()) - } - - /// pre process string to remove any brackets - pub fn pre_process_string(mut s: String) -> String { - while s.contains("&&(") { - let before = s.clone(); - s = Self::remove_out_brackets(&s); - if s == before { - break; - } - } - s - } - - /// Remove the brakcets, replacing them with the unbracketed 'full' expression - pub fn remove_out_brackets(s: &str) -> String { - if let Some(first_bracket) = s.find("&&(") { - let (first, rest_of_string) = - s.split_at(first_bracket + 3); - if let Some(last_bracket) = - Self::get_ending_bracket(rest_of_string) - { - let mut v = vec![]; - let (second, third) = - rest_of_string.split_at(last_bracket); - if let Some((first, third)) = first - .strip_suffix('(') - .zip(third.strip_prefix(')')) - { - for inside_bracket_item in second.split("||") { - // Append first, prepend third onto bracket element - v.push(format!( - "{}{}{}", - first, inside_bracket_item, third - )); - } - return v.join("||"); - } - } - } - s.to_string() - } - - /// Get outer matching brakets in a string - pub fn get_ending_bracket(s: &str) -> Option { - let mut brack_count = 0; - let mut ending_brakcet_pos = None; - for (i, c) in s.chars().enumerate() { - if c == '(' { - brack_count += 1; - } else if c == ')' { - if brack_count == 0 { - // Found - ending_brakcet_pos = Some(i); - break; - } - brack_count -= 1; - } - } - ending_brakcet_pos - } + /// Parses search string into individual sub-searches. + /// Each sub-search is a tuple of (string-to-search, flags-where-to-search) + /// + /// Returns vec of vec of sub-searches. + /// Where search results: + /// 1. from outer vec should be combined via 'disjunction' (or); + /// 2. from inter vec should be combined via 'conjunction' (and). + /// + /// Currently parentheses in the `filter_by_str` are not supported. + /// They should be removed by `Self::pre_process_string`. + fn get_what_to_filter_by( + filter_by_str: &str, + ) -> Vec> { + let mut search_vec = Vec::new(); + let mut and_vec = Vec::new(); + for or in filter_by_str.split("||") { + for split_sub in or.split("&&").map(str::trim) { + if !split_sub.starts_with(':') { + and_vec.push(( + split_sub.to_string(), + FilterBy::everywhere(), + )); + continue; + } + + let mut split_str = split_sub.splitn(2, ' '); + let first = split_str + .next() + .expect("Split must return at least one element"); + let mut to_filter_by = first.chars().skip(1).fold( + FilterBy::empty(), + |acc, ch| { + acc | FilterBy::try_from(ch) + .unwrap_or_else(|_| FilterBy::empty()) + }, + ); + + if to_filter_by.exclude_modifiers().is_empty() { + to_filter_by |= FilterBy::everywhere(); + } + + and_vec.push(( + split_str + .next() + .unwrap_or("") + .trim_start() + .to_string(), + to_filter_by, + )); + } + search_vec.push(and_vec.clone()); + and_vec.clear(); + } + search_vec + } + + pub fn filter(&mut self, filter_by: &str) -> Result<()> { + if filter_by != self.filter_string { + self.filter_string = filter_by.to_string(); + let pre_processed_string = + Self::pre_process_string(filter_by.to_string()); + let trimmed_string = + pre_processed_string.trim().to_string(); + if filter_by.is_empty() { + self.async_filter.stop_filter(); + self.is_filtering = false; + } else { + let filter_strings = + Self::get_what_to_filter_by(&trimmed_string); + self.async_filter + .start_filter(filter_strings) + .map_err(|e| anyhow::anyhow!(e.to_string()))?; + self.is_filtering = true; + } + return self.update(); + } + Ok(()) + } + + /// pre process string to remove any brackets + pub fn pre_process_string(mut s: String) -> String { + while s.contains("&&(") { + let before = s.clone(); + s = Self::remove_out_brackets(&s); + if s == before { + break; + } + } + s + } + + /// Remove the brakcets, replacing them with the unbracketed 'full' expression + pub fn remove_out_brackets(s: &str) -> String { + if let Some(first_bracket) = s.find("&&(") { + let (first, rest_of_string) = + s.split_at(first_bracket + 3); + if let Some(last_bracket) = + Self::get_ending_bracket(rest_of_string) + { + let mut v = vec![]; + let (second, third) = + rest_of_string.split_at(last_bracket); + if let Some((first, third)) = first + .strip_suffix('(') + .zip(third.strip_prefix(')')) + { + for inside_bracket_item in second.split("||") { + // Append first, prepend third onto bracket element + v.push(format!( + "{}{}{}", + first, inside_bracket_item, third + )); + } + return v.join("||"); + } + } + } + s.to_string() + } + + /// Get outer matching brakets in a string + pub fn get_ending_bracket(s: &str) -> Option { + let mut brack_count = 0; + let mut ending_brakcet_pos = None; + for (i, c) in s.chars().enumerate() { + if c == '(' { + brack_count += 1; + } else if c == ')' { + if brack_count == 0 { + // Found + ending_brakcet_pos = Some(i); + break; + } + brack_count -= 1; + } + } + ending_brakcet_pos + } pub fn select_commit(&mut self, id: CommitId) -> Result<()> { let position = self.git_log.position(id)?; @@ -419,52 +419,52 @@ impl DrawableComponent for Revlog { f: &mut Frame, area: Rect, ) -> Result<()> { - if self.commit_details.is_visible() { - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage(60), - Constraint::Percentage(40), - ] - .as_ref(), - ) - .split(area); - - if self.find_commit.is_visible() { - let log_find_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage(90), - Constraint::Percentage(20), - ] - .as_ref(), - ) - .split(chunks[0]); - self.list.draw(f, log_find_chunks[0])?; - self.find_commit.draw(f, log_find_chunks[1])?; - self.commit_details.draw(f, chunks[1])?; - } else { - self.list.draw(f, chunks[0])?; - self.commit_details.draw(f, chunks[1])?; - } - } else if self.find_commit.is_visible() { - let log_find_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage(90), - Constraint::Percentage(20), - ] - .as_ref(), - ) - .split(area); - self.list.draw(f, log_find_chunks[0])?; - self.find_commit.draw(f, log_find_chunks[1])?; - } else { - self.list.draw(f, area)?; - } + if self.commit_details.is_visible() { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage(60), + Constraint::Percentage(40), + ] + .as_ref(), + ) + .split(area); + + if self.find_commit.is_visible() { + let log_find_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(90), + Constraint::Percentage(20), + ] + .as_ref(), + ) + .split(chunks[0]); + self.list.draw(f, log_find_chunks[0])?; + self.find_commit.draw(f, log_find_chunks[1])?; + self.commit_details.draw(f, chunks[1])?; + } else { + self.list.draw(f, chunks[0])?; + self.commit_details.draw(f, chunks[1])?; + } + } else if self.find_commit.is_visible() { + let log_find_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(90), + Constraint::Percentage(20), + ] + .as_ref(), + ) + .split(area); + self.list.draw(f, log_find_chunks[0])?; + self.find_commit.draw(f, log_find_chunks[1])?; + } else { + self.list.draw(f, area)?; + } Ok(()) } @@ -475,11 +475,10 @@ impl Component for Revlog { #[allow(clippy::too_many_lines)] fn event(&mut self, ev: &Event) -> Result { if self.visible { - let mut event_used = self.find_commit.event(ev)?; - if !event_used.is_consumed() { - event_used = self.list.event(ev)?; - } - + let mut event_used = self.find_commit.event(ev)?; + if !event_used.is_consumed() { + event_used = self.list.event(ev)?; + } if event_used.is_consumed() { self.update()?; @@ -524,17 +523,20 @@ impl Component for Revlog { ) { self.queue.push(InternalEvent::SelectBranch); return Ok(EventState::Consumed); - } else if key_match( - k, - self.key_config.keys.show_find_commit_text_input - ) { - self.find_commit.toggle_visible()?; - self.find_commit.focus(true); - return Ok(EventState::Consumed); - } else if key_match(k, self.key_config.keys.exit_popup) { - self.filter("")?; - self.find_commit.clear_input(); - self.update()?; + } else if key_match( + k, + self.key_config.keys.show_find_commit_text_input, + ) { + self.find_commit.toggle_visible()?; + self.find_commit.focus(true); + return Ok(EventState::Consumed); + } else if key_match( + k, + self.key_config.keys.exit_popup, + ) { + self.filter("")?; + self.find_commit.clear_input(); + self.update()?; } else if key_match( k, self.key_config.keys.status_reset_item, @@ -672,11 +674,11 @@ impl Component for Revlog { || force_all, )); - out.push(CommandInfo::new( - strings::commands::find_commit(&self.key_config), - true, - self.visible || force_all, - )); + out.push(CommandInfo::new( + strings::commands::find_commit(&self.key_config), + true, + self.visible || force_all, + )); out.push(CommandInfo::new( strings::commands::copy_hash(&self.key_config), @@ -765,150 +767,150 @@ impl Component for Revlog { #[cfg(test)] mod test { - use super::Revlog; - use crate::components::async_commit_filter::FilterBy; - - #[test] - fn test_get_what_to_filter_by_flags() { - assert_eq!( - Revlog::get_what_to_filter_by("foo"), - vec![vec![("foo".to_owned(), FilterBy::everywhere())]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by(":s foo"), - vec![vec![("foo".to_owned(), FilterBy::SHA)]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by(":sm foo"), - vec![vec![( - "foo".to_owned(), - FilterBy::SHA | FilterBy::MESSAGE - )]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by(":samt foo"), - vec![vec![("foo".to_owned(), FilterBy::everywhere())]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by(":!csamt foo"), - vec![vec![("foo".to_owned(), FilterBy::all())]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by(":!c foo"), - vec![vec![("foo".to_owned(), FilterBy::all())]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by(":! foo"), - vec![vec![( - "foo".to_owned(), - FilterBy::everywhere() | FilterBy::NOT - )]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by(":c foo"), - vec![vec![( - "foo".to_owned(), - FilterBy::everywhere() | FilterBy::CASE_SENSITIVE - )]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by(":!m foo"), - vec![vec![( - "foo".to_owned(), - FilterBy::MESSAGE | FilterBy::NOT - )]] - ); - } - - #[test] - fn test_get_what_to_filter_by_log_op() { - assert_eq!( - Revlog::get_what_to_filter_by("foo && bar"), - vec![vec![ - ("foo".to_owned(), FilterBy::everywhere()), - ("bar".to_owned(), FilterBy::everywhere()) - ]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by("foo || bar"), - vec![ - vec![("foo".to_owned(), FilterBy::everywhere())], - vec![("bar".to_owned(), FilterBy::everywhere())] - ] - ); - - assert_eq!( - Revlog::get_what_to_filter_by("foo && bar || :m baz"), - vec![ - vec![ - ("foo".to_owned(), FilterBy::everywhere()), - ("bar".to_owned(), FilterBy::everywhere()) - ], - vec![("baz".to_owned(), FilterBy::MESSAGE)] - ] - ); - } - - #[test] - fn test_get_what_to_filter_by_spaces() { - assert_eq!( - Revlog::get_what_to_filter_by("foo&&bar"), - vec![vec![ - ("foo".to_owned(), FilterBy::everywhere()), - ("bar".to_owned(), FilterBy::everywhere()) - ]] - ); - assert_eq!( - Revlog::get_what_to_filter_by(" foo && bar "), - vec![vec![ - ("foo".to_owned(), FilterBy::everywhere()), - ("bar".to_owned(), FilterBy::everywhere()) - ]] - ); - - assert_eq!( - Revlog::get_what_to_filter_by(" foo bar baz "), - vec![vec![( - "foo bar baz".to_owned(), - FilterBy::everywhere() - )]] - ); - assert_eq!( - Revlog::get_what_to_filter_by(" :m foo bar baz "), - vec![vec![( - "foo bar baz".to_owned(), - FilterBy::MESSAGE - )]] - ); - assert_eq!( - Revlog::get_what_to_filter_by( - " :m foo bar baz && qwe t " - ), - vec![vec![ - ("foo bar baz".to_owned(), FilterBy::MESSAGE), - ("qwe t".to_owned(), FilterBy::everywhere()) - ]] - ); - } - - #[test] - fn test_get_what_to_filter_by_invalid_flags_ignored() { - assert_eq!( - Revlog::get_what_to_filter_by(":q foo"), - vec![vec![("foo".to_owned(), FilterBy::everywhere())]] - ); - assert_eq!( - Revlog::get_what_to_filter_by(":mq foo"), - vec![vec![("foo".to_owned(), FilterBy::MESSAGE)]] - ); - } + use super::Revlog; + use crate::components::async_commit_filter::FilterBy; + + #[test] + fn test_get_what_to_filter_by_flags() { + assert_eq!( + Revlog::get_what_to_filter_by("foo"), + vec![vec![("foo".to_owned(), FilterBy::everywhere())]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":s foo"), + vec![vec![("foo".to_owned(), FilterBy::SHA)]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":sm foo"), + vec![vec![( + "foo".to_owned(), + FilterBy::SHA | FilterBy::MESSAGE + )]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":samt foo"), + vec![vec![("foo".to_owned(), FilterBy::everywhere())]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":!csamt foo"), + vec![vec![("foo".to_owned(), FilterBy::all())]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":!c foo"), + vec![vec![("foo".to_owned(), FilterBy::all())]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":! foo"), + vec![vec![( + "foo".to_owned(), + FilterBy::everywhere() | FilterBy::NOT + )]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":c foo"), + vec![vec![( + "foo".to_owned(), + FilterBy::everywhere() | FilterBy::CASE_SENSITIVE + )]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(":!m foo"), + vec![vec![( + "foo".to_owned(), + FilterBy::MESSAGE | FilterBy::NOT + )]] + ); + } + + #[test] + fn test_get_what_to_filter_by_log_op() { + assert_eq!( + Revlog::get_what_to_filter_by("foo && bar"), + vec![vec![ + ("foo".to_owned(), FilterBy::everywhere()), + ("bar".to_owned(), FilterBy::everywhere()) + ]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by("foo || bar"), + vec![ + vec![("foo".to_owned(), FilterBy::everywhere())], + vec![("bar".to_owned(), FilterBy::everywhere())] + ] + ); + + assert_eq!( + Revlog::get_what_to_filter_by("foo && bar || :m baz"), + vec![ + vec![ + ("foo".to_owned(), FilterBy::everywhere()), + ("bar".to_owned(), FilterBy::everywhere()) + ], + vec![("baz".to_owned(), FilterBy::MESSAGE)] + ] + ); + } + + #[test] + fn test_get_what_to_filter_by_spaces() { + assert_eq!( + Revlog::get_what_to_filter_by("foo&&bar"), + vec![vec![ + ("foo".to_owned(), FilterBy::everywhere()), + ("bar".to_owned(), FilterBy::everywhere()) + ]] + ); + assert_eq!( + Revlog::get_what_to_filter_by(" foo && bar "), + vec![vec![ + ("foo".to_owned(), FilterBy::everywhere()), + ("bar".to_owned(), FilterBy::everywhere()) + ]] + ); + + assert_eq!( + Revlog::get_what_to_filter_by(" foo bar baz "), + vec![vec![( + "foo bar baz".to_owned(), + FilterBy::everywhere() + )]] + ); + assert_eq!( + Revlog::get_what_to_filter_by(" :m foo bar baz "), + vec![vec![( + "foo bar baz".to_owned(), + FilterBy::MESSAGE + )]] + ); + assert_eq!( + Revlog::get_what_to_filter_by( + " :m foo bar baz && qwe t " + ), + vec![vec![ + ("foo bar baz".to_owned(), FilterBy::MESSAGE), + ("qwe t".to_owned(), FilterBy::everywhere()) + ]] + ); + } + + #[test] + fn test_get_what_to_filter_by_invalid_flags_ignored() { + assert_eq!( + Revlog::get_what_to_filter_by(":q foo"), + vec![vec![("foo".to_owned(), FilterBy::everywhere())]] + ); + assert_eq!( + Revlog::get_what_to_filter_by(":mq foo"), + vec![vec![("foo".to_owned(), FilterBy::MESSAGE)]] + ); + } } diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index 86adb7b1e1..006bbf80b9 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -54,7 +54,7 @@ impl StashList { 100, )?; - self.list.set_total_count(commits.len()); + self.list.set_total_count(commits.len()); self.list.items().set_items(0, commits); } From 734b50a7319633eb3a0087753b9a68539cfd1a4f Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sat, 10 Jun 2023 19:43:37 -0300 Subject: [PATCH 100/118] fix warnings and errors --- src/components/utils/async_commit_filter.rs | 10 ++++++---- src/tabs/revlog.rs | 8 +++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index fd0c787bd3..5b46d8d970 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -60,12 +60,12 @@ impl TryFrom for FilterBy { '!' => Ok(Self::NOT), 'c' => Ok(Self::CASE_SENSITIVE), 't' => Ok(Self::TAGS), - _ => Err(anyhow::anyhow!(format!("Unknown flag: {}", v))), + _ => Err(anyhow::anyhow!("Unknown flag: {v}")), } } } -#[derive(PartialEq)] +#[derive(PartialEq, Eq)] pub enum FilterStatus { Filtering, Finished, @@ -124,12 +124,12 @@ impl AsyncCommitFilterer { /// vec [vec![A], vec![B, C, D], vec![E]] #[allow(clippy::too_many_lines)] pub fn filter( - mut vec_commit_info: Vec, + vec_commit_info: Vec, tags: &Option, filter_strings: &[Vec<(String, FilterBy)>], ) -> Vec { vec_commit_info - .drain(..) + .into_iter() .filter(|commit| { for to_and in filter_strings { let mut is_and = true; @@ -335,6 +335,7 @@ impl AsyncCommitFilterer { let repo = self.repo.clone(); + #[allow(clippy::significant_drop_tightening)] rayon_core::spawn(move || { // Only 1 thread can filter at a time let _c = cur_thread_mutex.lock().expect("mutex poisoned"); @@ -446,6 +447,7 @@ impl AsyncCommitFilterer { let max = min + amount; let max = max.min(len); let mut commits_requested = fc[min..max].to_vec(); + drop(fc); for c in &mut commits_requested { c.message = c .message diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 558b6ce7fe..8be82737e7 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -96,7 +96,7 @@ impl Revlog { visible: false, key_config, is_filtering: false, - filter_string: "".to_string(), + filter_string: String::new(), } } @@ -350,8 +350,7 @@ impl Revlog { for inside_bracket_item in second.split("||") { // Append first, prepend third onto bracket element v.push(format!( - "{}{}{}", - first, inside_bracket_item, third + "{first}{inside_bracket_item}{third}" )); } return v.join("||"); @@ -444,11 +443,10 @@ impl DrawableComponent for Revlog { .split(chunks[0]); self.list.draw(f, log_find_chunks[0])?; self.find_commit.draw(f, log_find_chunks[1])?; - self.commit_details.draw(f, chunks[1])?; } else { self.list.draw(f, chunks[0])?; - self.commit_details.draw(f, chunks[1])?; } + self.commit_details.draw(f, chunks[1])?; } else if self.find_commit.is_visible() { let log_find_chunks = Layout::default() .direction(Direction::Vertical) From ac1b25077c975770daa6d0f07d67147ad4148f76 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 11 Jun 2023 15:51:06 -0300 Subject: [PATCH 101/118] use let guards to avoid identation --- src/components/utils/async_commit_filter.rs | 106 ++++++++------------ 1 file changed, 41 insertions(+), 65 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 5b46d8d970..f1312b7430 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -347,72 +347,48 @@ impl AsyncCommitFilterer { let mut cur_index: usize = 0; loop { match rx.try_recv() { - Ok(_) | Err(TryRecvError::Disconnected) => { - break; - } - _ => { - // Get the git_log and start filtering through it - match async_log - .get_slice(cur_index, SLICE_SIZE) - { - Ok(ids) => { - match sync::get_commits_info( - &repo.borrow(), - &ids, - usize::MAX, - ) { - Ok(v) => { - if v.is_empty() - && !async_log.is_pending() - { - // Assume finished if log not pending and 0 recieved - filter_finished.store( - true, - Ordering::Relaxed, - ); - break; - } - - let mut filtered = - Self::filter( - v, - &tags, - &filter_strings, - ); - filter_count.fetch_add( - filtered.len(), - Ordering::Relaxed, - ); - let mut fc = filtered_commits - .lock() - .expect("mutex poisoned"); - fc.append(&mut filtered); - drop(fc); - cur_index += SLICE_SIZE; - async_app_sender - .send(AsyncGitNotification::Log) - .expect("error sending"); - thread::sleep( - FILTER_SLEEP_DURATION, - ); - } - Err(_) => { - // Failed to get commit info - thread::sleep( - FILTER_SLEEP_DURATION_FAILED_LOCK, - ); - } - } - } - Err(_) => { - // Failed to get slice - thread::sleep( - FILTER_SLEEP_DURATION_FAILED_LOCK, - ); - } - } - } + Ok(_) | Err(TryRecvError::Disconnected) => break, + _ => {} } + + // Get the git_log and start filtering through it + let Ok(ids) = async_log.get_slice( + cur_index, + SLICE_SIZE + ) else { + thread::sleep(FILTER_SLEEP_DURATION_FAILED_LOCK); + continue; + }; + + let Ok(v) = sync::get_commits_info( + &repo.borrow(), + &ids, + usize::MAX, + ) else { + thread::sleep(FILTER_SLEEP_DURATION_FAILED_LOCK); + continue; + }; + + if v.is_empty() && !async_log.is_pending() { + // Assume finished if log not pending and 0 recieved + filter_finished.store(true, Ordering::Relaxed); + break; + } + + let mut filtered = + Self::filter(v, &tags, &filter_strings); + filter_count + .fetch_add(filtered.len(), Ordering::Relaxed); + let mut fc = + filtered_commits.lock().expect("mutex poisoned"); + fc.append(&mut filtered); + drop(fc); + cur_index += SLICE_SIZE; + async_app_sender + .send(AsyncGitNotification::Log) + .expect("error sending"); + + thread::sleep(FILTER_SLEEP_DURATION); } }); Ok(()) From f62bc5064fc568a4e6b275c69b1f81532637a678 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 11 Jun 2023 15:54:48 -0300 Subject: [PATCH 102/118] avoid unecessary calls to drop --- src/components/utils/async_commit_filter.rs | 33 ++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index f1312b7430..f0c51f77f2 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -379,10 +379,12 @@ impl AsyncCommitFilterer { Self::filter(v, &tags, &filter_strings); filter_count .fetch_add(filtered.len(), Ordering::Relaxed); - let mut fc = - filtered_commits.lock().expect("mutex poisoned"); - fc.append(&mut filtered); - drop(fc); + + filtered_commits + .lock() + .expect("mutex poisoned") + .append(&mut filtered); + cur_index += SLICE_SIZE; async_app_sender .send(AsyncGitNotification::Log) @@ -414,16 +416,19 @@ impl AsyncCommitFilterer { amount: usize, message_length_limit: usize, ) -> Result> { - let fc = self - .filtered_commits - .lock() - .map_err(|_| Error::msg("mutex poisoned"))?; - let len = fc.len(); - let min = start.min(len); - let max = min + amount; - let max = max.min(len); - let mut commits_requested = fc[min..max].to_vec(); - drop(fc); + let mut commits_requested = { + let fc = self + .filtered_commits + .lock() + .map_err(|_| Error::msg("mutex poisoned"))?; + let len = fc.len(); + let min = start.min(len); + let max = min + amount; + let max = max.min(len); + + fc[min..max].to_vec() + }; + for c in &mut commits_requested { c.message = c .message From 938a1b72ca3bd89016f32b682872d0be29ada161 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 11 Jun 2023 16:37:28 -0300 Subject: [PATCH 103/118] Make quit key respect consumed events Previously there was a special logic that make the quit key have priority over all components, unless any popup was visible. This was conflicting with the find commit text input, which happens to not be a popup. Fix that by handling the quit key the same way as any other app key, which appears to already handle popups somehow. Not sure if this change is breaking some logic. --- src/app.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/app.rs b/src/app.rs index 15f0363652..d9a521c446 100644 --- a/src/app.rs +++ b/src/app.rs @@ -395,7 +395,7 @@ impl App { log::trace!("event: {:?}", ev); if let InputEvent::Input(ev) = ev { - if self.check_hard_exit(&ev) || self.check_quit(&ev) { + if self.check_hard_exit(&ev) { return Ok(()); } @@ -448,6 +448,9 @@ impl App { ) { self.options_popup.show()?; NeedsUpdate::ALL + } else if key_match(k, self.key_config.keys.quit) { + self.do_quit = QuitState::Close; + NeedsUpdate::empty() } else { NeedsUpdate::empty() }; @@ -647,19 +650,6 @@ impl App { ] ); - fn check_quit(&mut self, ev: &Event) -> bool { - if self.any_popup_visible() { - return false; - } - if let Event::Key(e) = ev { - if key_match(e, self.key_config.keys.quit) { - self.do_quit = QuitState::Close; - return true; - } - } - false - } - fn check_hard_exit(&mut self, ev: &Event) -> bool { if let Event::Key(e) = ev { if key_match(e, self.key_config.keys.exit) { From 48381093c56e428776d0ec52db6369c87ca6f5fa Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Fri, 16 Jun 2023 22:28:28 -0300 Subject: [PATCH 104/118] Replace unused type in channel by unit type --- src/components/utils/async_commit_filter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index f0c51f77f2..3d734fe68c 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -79,7 +79,7 @@ pub struct AsyncCommitFilterer { filter_count: Arc, filter_finished: Arc, is_pending_local: RefCell, - filter_thread_sender: Option>, + filter_thread_sender: Option>, filter_thread_mutex: Arc>, sender: Sender, } @@ -402,7 +402,7 @@ impl AsyncCommitFilterer { // Any error this gives can be safely ignored, // it will send if reciever exists, otherwise does nothing if let Some(sender) = &self.filter_thread_sender { - match sender.try_send(true) { + match sender.try_send(()) { Ok(_) | Err(_) => {} }; } From bcb174f3cac5da4b2bc5f600572797520144f890 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Fri, 16 Jun 2023 22:55:51 -0300 Subject: [PATCH 105/118] Dont continue in filter loop in case of error --- src/components/utils/async_commit_filter.rs | 40 ++++++++++++--------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 3d734fe68c..8a3d1de5a9 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -18,8 +18,6 @@ use std::{ use unicode_truncate::UnicodeTruncateStr; const FILTER_SLEEP_DURATION: Duration = Duration::from_millis(10); -const FILTER_SLEEP_DURATION_FAILED_LOCK: Duration = - Duration::from_millis(500); const SLICE_SIZE: usize = 1200; bitflags! { @@ -345,34 +343,36 @@ impl AsyncCommitFilterer { filter_count.store(0, Ordering::Relaxed); filtered_commits.lock().expect("mutex poisoned").clear(); let mut cur_index: usize = 0; - loop { + let result = loop { match rx.try_recv() { - Ok(_) | Err(TryRecvError::Disconnected) => break, + Ok(_) | Err(TryRecvError::Disconnected) => { + break Ok(()) + } _ => {} } // Get the git_log and start filtering through it - let Ok(ids) = async_log.get_slice( - cur_index, - SLICE_SIZE - ) else { - thread::sleep(FILTER_SLEEP_DURATION_FAILED_LOCK); - continue; + let ids = match async_log + .get_slice(cur_index, SLICE_SIZE) + { + Ok(ids) => ids, + // Only errors if the lock is poisoned + Err(err) => break Err(err), }; - let Ok(v) = sync::get_commits_info( + let v = match sync::get_commits_info( &repo.borrow(), &ids, usize::MAX, - ) else { - thread::sleep(FILTER_SLEEP_DURATION_FAILED_LOCK); - continue; + ) { + Ok(v) => v, + // May error while querying the repo or commits + Err(err) => break Err(err), }; + // Assume finished if log not pending and 0 recieved if v.is_empty() && !async_log.is_pending() { - // Assume finished if log not pending and 0 recieved - filter_finished.store(true, Ordering::Relaxed); - break; + break Ok(()); } let mut filtered = @@ -391,6 +391,12 @@ impl AsyncCommitFilterer { .expect("error sending"); thread::sleep(FILTER_SLEEP_DURATION); + }; + + filter_finished.store(true, Ordering::Relaxed); + + if let Err(e) = result { + log::error!("async job error: {}", e); } }); Ok(()) From b45c9e277fd1a49773d4cda8f3d69f4b8b74f635 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Fri, 16 Jun 2023 23:07:18 -0300 Subject: [PATCH 106/118] Avoid using two Mutex's for filter thread Previously, the filter thread would hold the Mutex locked by the previous thread invocation, and hold a newly created Mutex. I could not see why that would be necessary, instead of just locking the same Mutex. (Also not sure about holding a Mutex at all) --- src/components/utils/async_commit_filter.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 8a3d1de5a9..548519ba34 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -322,10 +322,8 @@ impl AsyncCommitFilterer { self.filter_thread_sender = Some(tx); let async_app_sender = self.sender.clone(); - let prev_thread_mutex = Arc::clone(&self.filter_thread_mutex); - self.filter_thread_mutex = Arc::new(Mutex::new(())); - - let cur_thread_mutex = Arc::clone(&self.filter_thread_mutex); + let filter_thread_mutex = + Arc::clone(&self.filter_thread_mutex); self.is_pending_local.replace(true); let tags = @@ -336,9 +334,9 @@ impl AsyncCommitFilterer { #[allow(clippy::significant_drop_tightening)] rayon_core::spawn(move || { // Only 1 thread can filter at a time - let _c = cur_thread_mutex.lock().expect("mutex poisoned"); - let _p = - prev_thread_mutex.lock().expect("mutex poisoned"); + let _c = + filter_thread_mutex.lock().expect("mutex poisoned"); + filter_finished.store(false, Ordering::Relaxed); filter_count.store(0, Ordering::Relaxed); filtered_commits.lock().expect("mutex poisoned").clear(); From 95609f1f64de195046ccc7691c8235e2508b7157 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Wed, 21 Jun 2023 15:34:17 -0300 Subject: [PATCH 107/118] Use AtomicBool instead of channel for stop signal --- src/components/utils/async_commit_filter.rs | 27 +++++++-------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 548519ba34..a6824df7e3 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -4,7 +4,7 @@ use asyncgit::{ AsyncGitNotification, AsyncLog, AsyncTags, }; use bitflags::bitflags; -use crossbeam_channel::{Sender, TryRecvError}; +use crossbeam_channel::Sender; use std::convert::TryFrom; use std::{ cell::RefCell, @@ -77,7 +77,8 @@ pub struct AsyncCommitFilterer { filter_count: Arc, filter_finished: Arc, is_pending_local: RefCell, - filter_thread_sender: Option>, + /// Tells the last filter thread to stop early when set to true. + filter_stop_signal: Arc, filter_thread_mutex: Arc>, sender: Sender, } @@ -98,7 +99,7 @@ impl AsyncCommitFilterer { filter_finished: Arc::new(AtomicBool::new(false)), filter_thread_mutex: Arc::new(Mutex::new(())), is_pending_local: RefCell::new(false), - filter_thread_sender: None, + filter_stop_signal: Arc::new(AtomicBool::new(false)), sender: sender.clone(), } } @@ -317,9 +318,9 @@ impl AsyncCommitFilterer { let async_log = self.git_log.clone(); let filter_finished = Arc::clone(&self.filter_finished); - let (tx, rx) = crossbeam_channel::unbounded(); + self.filter_stop_signal = Arc::new(AtomicBool::new(false)); + let filter_stop_signal = Arc::clone(&self.filter_stop_signal); - self.filter_thread_sender = Some(tx); let async_app_sender = self.sender.clone(); let filter_thread_mutex = @@ -342,11 +343,8 @@ impl AsyncCommitFilterer { filtered_commits.lock().expect("mutex poisoned").clear(); let mut cur_index: usize = 0; let result = loop { - match rx.try_recv() { - Ok(_) | Err(TryRecvError::Disconnected) => { - break Ok(()) - } - _ => {} + if filter_stop_signal.load(Ordering::Relaxed) { + break Ok(()); } // Get the git_log and start filtering through it @@ -401,15 +399,8 @@ impl AsyncCommitFilterer { } /// Stop the filter if one was running, otherwise does nothing. - /// Is it possible to restart from this stage by calling restart pub fn stop_filter(&self) { - // Any error this gives can be safely ignored, - // it will send if reciever exists, otherwise does nothing - if let Some(sender) = &self.filter_thread_sender { - match sender.try_send(()) { - Ok(_) | Err(_) => {} - }; - } + self.filter_stop_signal.store(true, Ordering::Relaxed); self.is_pending_local.replace(false); self.filter_finished.store(true, Ordering::Relaxed); } From bf3281ba60248f9e5b9b2150b28abeb9625f9573 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Thu, 29 Jun 2023 22:29:46 -0300 Subject: [PATCH 108/118] Use filter_finished for is_pending logic --- src/components/utils/async_commit_filter.rs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index a6824df7e3..153aba1389 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -7,7 +7,6 @@ use bitflags::bitflags; use crossbeam_channel::Sender; use std::convert::TryFrom; use std::{ - cell::RefCell, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, Mutex, @@ -75,8 +74,8 @@ pub struct AsyncCommitFilterer { git_tags: AsyncTags, filtered_commits: Arc>>, filter_count: Arc, + /// True if the filter thread is currently not running. filter_finished: Arc, - is_pending_local: RefCell, /// Tells the last filter thread to stop early when set to true. filter_stop_signal: Arc, filter_thread_mutex: Arc>, @@ -96,22 +95,15 @@ impl AsyncCommitFilterer { git_tags, filtered_commits: Arc::new(Mutex::new(Vec::new())), filter_count: Arc::new(AtomicUsize::new(0)), - filter_finished: Arc::new(AtomicBool::new(false)), + filter_finished: Arc::new(AtomicBool::new(true)), filter_thread_mutex: Arc::new(Mutex::new(())), - is_pending_local: RefCell::new(false), filter_stop_signal: Arc::new(AtomicBool::new(false)), sender: sender.clone(), } } pub fn is_pending(&self) -> bool { - let mut b = self.is_pending_local.borrow_mut(); - if *b { - *b = self.fetch() == FilterStatus::Filtering; - *b - } else { - false - } + !self.filter_finished.load(Ordering::Relaxed) } /// `filter_strings` should be split by or them and, for example, @@ -325,7 +317,6 @@ impl AsyncCommitFilterer { let filter_thread_mutex = Arc::clone(&self.filter_thread_mutex); - self.is_pending_local.replace(true); let tags = Self::get_tags(&filter_strings, &mut self.git_tags)?; @@ -401,8 +392,6 @@ impl AsyncCommitFilterer { /// Stop the filter if one was running, otherwise does nothing. pub fn stop_filter(&self) { self.filter_stop_signal.store(true, Ordering::Relaxed); - self.is_pending_local.replace(false); - self.filter_finished.store(true, Ordering::Relaxed); } pub fn get_filter_items( From db623d57dcf02131452f5231d325569ef010a1f3 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Fri, 30 Jun 2023 21:41:08 -0300 Subject: [PATCH 109/118] Fix update logic --- src/components/utils/async_commit_filter.rs | 37 +++++++++++---------- src/tabs/revlog.rs | 13 +++++--- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 153aba1389..7a77b6d8c8 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -5,7 +5,7 @@ use asyncgit::{ }; use bitflags::bitflags; use crossbeam_channel::Sender; -use std::convert::TryFrom; +use std::{convert::TryFrom, marker::PhantomData}; use std::{ sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, @@ -62,12 +62,6 @@ impl TryFrom for FilterBy { } } -#[derive(PartialEq, Eq)] -pub enum FilterStatus { - Filtering, - Finished, -} - pub struct AsyncCommitFilterer { repo: RepoPathRef, git_log: AsyncLog, @@ -80,6 +74,9 @@ pub struct AsyncCommitFilterer { filter_stop_signal: Arc, filter_thread_mutex: Arc>, sender: Sender, + + /// `start_filter` logic relies on it being non-reentrant. + _non_sync: PhantomData>, } impl AsyncCommitFilterer { @@ -99,6 +96,7 @@ impl AsyncCommitFilterer { filter_thread_mutex: Arc::new(Mutex::new(())), filter_stop_signal: Arc::new(AtomicBool::new(false)), sender: sender.clone(), + _non_sync: PhantomData, } } @@ -305,7 +303,17 @@ impl AsyncCommitFilterer { ) -> Result<()> { self.stop_filter(); + // `stop_filter` blocks until the previous threads finish, and + // Self is !Sync, so two threads cannot be spawn at the same + // time. + // + // We rely on these assumptions to keep `filtered_commits` + // consistent. + let filtered_commits = Arc::clone(&self.filtered_commits); + + filtered_commits.lock().expect("mutex poisoned").clear(); + let filter_count = Arc::clone(&self.filter_count); let async_log = self.git_log.clone(); let filter_finished = Arc::clone(&self.filter_finished); @@ -331,7 +339,6 @@ impl AsyncCommitFilterer { filter_finished.store(false, Ordering::Relaxed); filter_count.store(0, Ordering::Relaxed); - filtered_commits.lock().expect("mutex poisoned").clear(); let mut cur_index: usize = 0; let result = loop { if filter_stop_signal.load(Ordering::Relaxed) { @@ -389,9 +396,13 @@ impl AsyncCommitFilterer { Ok(()) } - /// Stop the filter if one was running, otherwise does nothing. + /// Stop the filter thread if one was running, otherwise does nothing. This blocks until the + /// filter thread is finished. pub fn stop_filter(&self) { self.filter_stop_signal.store(true, Ordering::Relaxed); + + // wait for the filter thread to finish + drop(self.filter_thread_mutex.lock()); } pub fn get_filter_items( @@ -426,12 +437,4 @@ impl AsyncCommitFilterer { pub fn count(&self) -> usize { self.filter_count.load(Ordering::Relaxed) } - - pub fn fetch(&self) -> FilterStatus { - if self.filter_finished.load(Ordering::Relaxed) { - FilterStatus::Finished - } else { - FilterStatus::Filtering - } - } } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 8be82737e7..1595e64d8e 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -1,8 +1,6 @@ use crate::{ components::{ - async_commit_filter::{ - AsyncCommitFilterer, FilterBy, FilterStatus, - }, + async_commit_filter::{AsyncCommitFilterer, FilterBy}, visibility_blocking, CommandBlocking, CommandInfo, CommitDetailsComponent, CommitList, Component, DrawableComponent, EventState, FileTreeOpen, @@ -114,11 +112,15 @@ impl Revlog { pub fn update(&mut self) -> Result<()> { if self.is_visible() { let log_changed = if self.is_filtering { + false + } else { + self.git_log.fetch()? == FetchStatus::Started + }; + + if self.is_filtering { self.list.set_total_count(self.async_filter.count()); - self.async_filter.fetch() == FilterStatus::Filtering } else { self.list.set_total_count(self.git_log.count()?); - self.git_log.fetch()? == FetchStatus::Started }; let selection = self.list.selection(); @@ -315,6 +317,7 @@ impl Revlog { .map_err(|e| anyhow::anyhow!(e.to_string()))?; self.is_filtering = true; } + self.list.clear(); return self.update(); } Ok(()) From 277869ee48655d60c45f102689a734b7db21f99e Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Fri, 7 Jul 2023 12:36:24 -0300 Subject: [PATCH 110/118] Remove key_list_file A leftover file, from fixing conflicts conflicts, probably. --- src/keys/key_list_file.rs | 195 -------------------------------------- 1 file changed, 195 deletions(-) delete mode 100644 src/keys/key_list_file.rs diff --git a/src/keys/key_list_file.rs b/src/keys/key_list_file.rs deleted file mode 100644 index 17c20a10bc..0000000000 --- a/src/keys/key_list_file.rs +++ /dev/null @@ -1,195 +0,0 @@ -use anyhow::Result; -use ron::{self}; -use serde::{Deserialize, Serialize}; -use std::{fs::File, io::Read, path::PathBuf}; - -use super::key_list::{GituiKeyEvent, KeysList}; - -#[derive(Serialize, Deserialize, Default)] -pub struct KeysListFile { - pub tab_status: Option, - pub tab_log: Option, - pub tab_files: Option, - pub tab_stashing: Option, - pub tab_stashes: Option, - pub tab_toggle: Option, - pub tab_toggle_reverse: Option, - pub toggle_workarea: Option, - pub focus_right: Option, - pub focus_left: Option, - pub focus_above: Option, - pub focus_below: Option, - pub exit: Option, - pub quit: Option, - pub exit_popup: Option, - pub open_commit: Option, - pub open_commit_editor: Option, - pub show_find_commit_text_input: Option, - pub open_help: Option, - pub open_options: Option, - pub move_left: Option, - pub move_right: Option, - pub tree_collapse_recursive: Option, - pub tree_expand_recursive: Option, - pub home: Option, - pub end: Option, - pub move_up: Option, - pub move_down: Option, - pub popup_up: Option, - pub popup_down: Option, - pub page_down: Option, - pub page_up: Option, - pub shift_up: Option, - pub shift_down: Option, - pub enter: Option, - pub blame: Option, - pub edit_file: Option, - pub file_history: Option, - pub status_stage_all: Option, - pub status_reset_item: Option, - pub status_ignore_file: Option, - pub diff_stage_lines: Option, - pub diff_reset_lines: Option, - pub stashing_save: Option, - pub stashing_toggle_untracked: Option, - pub stashing_toggle_index: Option, - pub stash_apply: Option, - pub stash_open: Option, - pub stash_drop: Option, - pub cmd_bar_toggle: Option, - pub log_tag_commit: Option, - pub log_mark_commit: Option, - pub commit_amend: Option, - pub copy: Option, - pub create_branch: Option, - pub rename_branch: Option, - pub select_branch: Option, - pub delete_branch: Option, - pub merge_branch: Option, - pub rebase_branch: Option, - pub compare_commits: Option, - pub tags: Option, - pub delete_tag: Option, - pub select_tag: Option, - pub push: Option, - pub open_file_tree: Option, - pub file_find: Option, - pub force_push: Option, - pub pull: Option, - pub abort_merge: Option, - pub undo_commit: Option, - pub stage_unstage_item: Option, - pub tag_annotate: Option, - pub view_submodules: Option, - pub view_submodule_parent: Option, - pub update_dubmodule: Option, -} - -impl KeysListFile { - pub fn read_file(config_file: PathBuf) -> Result { - let mut f = File::open(config_file)?; - let mut buffer = Vec::new(); - f.read_to_end(&mut buffer)?; - Ok(ron::de::from_bytes(&buffer)?) - } - - #[rustfmt::skip] - pub fn get_list(self) -> KeysList { - let default = KeysList::default(); - - KeysList { - tab_status: self.tab_status.unwrap_or(default.tab_status), - tab_log: self.tab_log.unwrap_or(default.tab_log), - tab_files: self.tab_files.unwrap_or(default.tab_files), - tab_stashing: self.tab_stashing.unwrap_or(default.tab_stashing), - tab_stashes: self.tab_stashes.unwrap_or(default.tab_stashes), - tab_toggle: self.tab_toggle.unwrap_or(default.tab_toggle), - tab_toggle_reverse: self.tab_toggle_reverse.unwrap_or(default.tab_toggle_reverse), - toggle_workarea: self.toggle_workarea.unwrap_or(default.toggle_workarea), - focus_right: self.focus_right.unwrap_or(default.focus_right), - focus_left: self.focus_left.unwrap_or(default.focus_left), - focus_above: self.focus_above.unwrap_or(default.focus_above), - focus_below: self.focus_below.unwrap_or(default.focus_below), - exit: self.exit.unwrap_or(default.exit), - quit: self.quit.unwrap_or(default.quit), - exit_popup: self.exit_popup.unwrap_or(default.exit_popup), - open_commit: self.open_commit.unwrap_or(default.open_commit), - open_commit_editor: self.open_commit_editor.unwrap_or(default.open_commit_editor), - show_find_commit_text_input: self.show_find_commit_text_input.unwrap_or(default.show_find_commit_text_input), - open_help: self.open_help.unwrap_or(default.open_help), - open_options: self.open_options.unwrap_or(default.open_options), - move_left: self.move_left.unwrap_or(default.move_left), - move_right: self.move_right.unwrap_or(default.move_right), - tree_collapse_recursive: self.tree_collapse_recursive.unwrap_or(default.tree_collapse_recursive), - tree_expand_recursive: self.tree_expand_recursive.unwrap_or(default.tree_expand_recursive), - home: self.home.unwrap_or(default.home), - end: self.end.unwrap_or(default.end), - move_up: self.move_up.unwrap_or(default.move_up), - move_down: self.move_down.unwrap_or(default.move_down), - popup_up: self.popup_up.unwrap_or(default.popup_up), - popup_down: self.popup_down.unwrap_or(default.popup_down), - page_down: self.page_down.unwrap_or(default.page_down), - page_up: self.page_up.unwrap_or(default.page_up), - shift_up: self.shift_up.unwrap_or(default.shift_up), - shift_down: self.shift_down.unwrap_or(default.shift_down), - enter: self.enter.unwrap_or(default.enter), - blame: self.blame.unwrap_or(default.blame), - edit_file: self.edit_file.unwrap_or(default.edit_file), - file_history: self.file_history.unwrap_or(default.file_history), - status_stage_all: self.status_stage_all.unwrap_or(default.status_stage_all), - status_reset_item: self.status_reset_item.unwrap_or(default.status_reset_item), - status_ignore_file: self.status_ignore_file.unwrap_or(default.status_ignore_file), - diff_stage_lines: self.diff_stage_lines.unwrap_or(default.diff_stage_lines), - diff_reset_lines: self.diff_reset_lines.unwrap_or(default.diff_reset_lines), - stashing_save: self.stashing_save.unwrap_or(default.stashing_save), - stashing_toggle_untracked: self.stashing_toggle_untracked.unwrap_or(default.stashing_toggle_untracked), - stashing_toggle_index: self.stashing_toggle_index.unwrap_or(default.stashing_toggle_index), - stash_apply: self.stash_apply.unwrap_or(default.stash_apply), - stash_open: self.stash_open.unwrap_or(default.stash_open), - stash_drop: self.stash_drop.unwrap_or(default.stash_drop), - cmd_bar_toggle: self.cmd_bar_toggle.unwrap_or(default.cmd_bar_toggle), - log_tag_commit: self.log_tag_commit.unwrap_or(default.log_tag_commit), - log_mark_commit: self.log_mark_commit.unwrap_or(default.log_mark_commit), - commit_amend: self.commit_amend.unwrap_or(default.commit_amend), - copy: self.copy.unwrap_or(default.copy), - create_branch: self.create_branch.unwrap_or(default.create_branch), - rename_branch: self.rename_branch.unwrap_or(default.rename_branch), - select_branch: self.select_branch.unwrap_or(default.select_branch), - delete_branch: self.delete_branch.unwrap_or(default.delete_branch), - merge_branch: self.merge_branch.unwrap_or(default.merge_branch), - rebase_branch: self.rebase_branch.unwrap_or(default.rebase_branch), - compare_commits: self.compare_commits.unwrap_or(default.compare_commits), - tags: self.tags.unwrap_or(default.tags), - delete_tag: self.delete_tag.unwrap_or(default.delete_tag), - select_tag: self.select_tag.unwrap_or(default.select_tag), - push: self.push.unwrap_or(default.push), - open_file_tree: self.open_file_tree.unwrap_or(default.open_file_tree), - file_find: self.file_find.unwrap_or(default.file_find), - force_push: self.force_push.unwrap_or(default.force_push), - pull: self.pull.unwrap_or(default.pull), - abort_merge: self.abort_merge.unwrap_or(default.abort_merge), - undo_commit: self.undo_commit.unwrap_or(default.undo_commit), - stage_unstage_item: self.stage_unstage_item.unwrap_or(default.stage_unstage_item), - tag_annotate: self.tag_annotate.unwrap_or(default.tag_annotate), - view_submodules: self.view_submodules.unwrap_or(default.view_submodules), - view_submodule_parent: self.view_submodule_parent.unwrap_or(default.view_submodule_parent), - update_submodule: self.update_dubmodule.unwrap_or(default.update_submodule), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_load_vim_style_example() { - assert_eq!( - KeysListFile::read_file( - "vim_style_key_config.ron".into() - ) - .is_ok(), - true - ); - } -} From 80eaa73369399b8900ab060d7a1232d4b356336c Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Fri, 7 Jul 2023 14:37:21 -0300 Subject: [PATCH 111/118] Refactor AsyncCommitFilter::filter and add unit tests --- asyncgit/src/sync/commits_info.rs | 13 +- src/components/utils/async_commit_filter.rs | 342 +++++++++++--------- src/tabs/revlog.rs | 51 ++- 3 files changed, 246 insertions(+), 160 deletions(-) diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs index ec8bf3cd4a..d6791c32d7 100644 --- a/asyncgit/src/sync/commits_info.rs +++ b/asyncgit/src/sync/commits_info.rs @@ -22,6 +22,17 @@ impl CommitId { Self(id) } + /// Parse a hex-formatted object id into an Oid structure. + /// + /// # Errors + /// + /// Returns an error if the string is empty, is longer than 40 hex + /// characters, or contains any non-hex characters. + pub fn from_hex_str(id: &str) -> Result { + let oid = Oid::from_str(id)?; + Ok(Self::new(oid)) + } + /// pub(crate) const fn get_oid(self) -> Oid { self.0 @@ -52,7 +63,7 @@ impl From for CommitId { } /// -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct CommitInfo { /// pub message: String, diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 7a77b6d8c8..4b3aec2d5a 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -5,7 +5,7 @@ use asyncgit::{ }; use bitflags::bitflags; use crossbeam_channel::Sender; -use std::{convert::TryFrom, marker::PhantomData}; +use std::{borrow::Cow, convert::TryFrom, marker::PhantomData}; use std::{ sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, @@ -120,158 +120,77 @@ impl AsyncCommitFilterer { vec_commit_info .into_iter() .filter(|commit| { - for to_and in filter_strings { - let mut is_and = true; - for (s, filter) in to_and { - if filter.contains(FilterBy::CASE_SENSITIVE) { - is_and = - if filter.contains(FilterBy::NOT) { - (filter.contains(FilterBy::TAGS) - && tags.as_ref().map_or( - false, - |t| { - t.get(&commit.id) - .map_or( - true, - |commit_tags| { - commit_tags.iter().filter(|tag|{ - !tag.name.contains(s) - }).count() > 0 - }, - ) - }, - )) || (filter - .contains(FilterBy::SHA) - && !commit - .id - .to_string() - .contains(s)) || (filter - .contains(FilterBy::AUTHOR) - && !commit.author.contains(s)) - || (filter.contains( - FilterBy::MESSAGE, - ) && !commit - .message - .contains(s)) - } else { - (filter.contains(FilterBy::TAGS) - && tags.as_ref().map_or( - false, - |t| { - t.get(&commit.id) - .map_or( - false, - |commit_tags| { - commit_tags.iter().filter(|tag|{ - tag.name.contains(s) - }).count() > 0 - }, - ) - }, - )) || (filter - .contains(FilterBy::SHA) - && commit - .id - .to_string() - .contains(s)) || (filter - .contains(FilterBy::AUTHOR) - && commit.author.contains(s)) - || (filter.contains( - FilterBy::MESSAGE, - ) && commit - .message - .contains(s)) - } - } else { - is_and = if filter.contains(FilterBy::NOT) - { - (filter.contains(FilterBy::TAGS) - && tags.as_ref().map_or( - false, - |t| { - t.get(&commit.id).map_or( - true, - |commit_tags| { - commit_tags - .iter() - .filter( - |tag| { - !tag.name.to_lowercase().contains(&s.to_lowercase()) - }, - ) - .count() > 0 - }, - ) - }, - )) || (filter.contains(FilterBy::SHA) - && !commit - .id - .to_string() - .to_lowercase() - .contains(&s.to_lowercase())) - || (filter - .contains(FilterBy::AUTHOR) - && !commit - .author - .to_lowercase() - .contains( - &s.to_lowercase(), - )) || (filter - .contains(FilterBy::MESSAGE) - && !commit - .message - .to_lowercase() - .contains(&s.to_lowercase())) - } else { - (filter.contains(FilterBy::TAGS) - && tags.as_ref().map_or( - false, - |t| { - t.get(&commit.id).map_or( - false, - |commit_tags| { - commit_tags - .iter() - .filter( - |tag| { - tag.name.to_lowercase().contains(&s.to_lowercase()) - }, - ) - .count() > 0 - }, - ) - }, - )) || (filter.contains(FilterBy::SHA) - && commit - .id - .to_string() - .to_lowercase() - .contains(&s.to_lowercase())) - || (filter - .contains(FilterBy::AUTHOR) - && commit - .author - .to_lowercase() - .contains( - &s.to_lowercase(), - )) || (filter - .contains(FilterBy::MESSAGE) - && commit - .message - .to_lowercase() - .contains(&s.to_lowercase())) - } - } - } - if is_and { - return true; - } - } - false + Self::filter_one(filter_strings, tags, commit) }) .collect() } + fn filter_one( + filter_strings: &[Vec<(String, FilterBy)>], + tags: &Option, + commit: &CommitInfo, + ) -> bool { + for to_and in filter_strings { + if Self::filter_and(to_and, tags, commit) { + return true; + } + } + false + } + + fn filter_and( + to_and: &Vec<(String, FilterBy)>, + tags: &Option, + commit: &CommitInfo, + ) -> bool { + for (s, filter) in to_and { + let by_sha = filter.contains(FilterBy::SHA); + let by_aut = filter.contains(FilterBy::AUTHOR); + let by_mes = filter.contains(FilterBy::MESSAGE); + let by_tag = filter.contains(FilterBy::TAGS); + + let id: String; + let author: Cow; + let message: Cow; + if filter.contains(FilterBy::CASE_SENSITIVE) { + id = commit.id.to_string(); + author = Cow::Borrowed(&commit.author); + message = Cow::Borrowed(&commit.message); + } else { + id = commit.id.to_string().to_lowercase(); + author = Cow::Owned(commit.author.to_lowercase()); + message = Cow::Owned(commit.message.to_lowercase()); + }; + + let is_match = { + let tag_contains = tags.as_ref().map_or(false, |t| { + t.get(&commit.id).map_or(false, |commit_tags| { + commit_tags + .iter() + .filter(|tag| tag.name.contains(s)) + .count() > 0 + }) + }); + + (by_tag && tag_contains) + || (by_sha && id.contains(s)) + || (by_aut && author.contains(s)) + || (by_mes && message.contains(s)) + }; + + let is_match = if filter.contains(FilterBy::NOT) { + !is_match + } else { + is_match + }; + + if !is_match { + return false; + } + } + true + } + /// If the filtering string contain filtering by tags /// return them, else don't get the tags fn get_tags( @@ -438,3 +357,128 @@ impl AsyncCommitFilterer { self.filter_count.load(Ordering::Relaxed) } } + +#[cfg(test)] +mod test { + use asyncgit::sync::{CommitId, CommitInfo}; + + use crate::tabs::Revlog; + + use super::AsyncCommitFilterer; + + fn commit( + time: i64, + message: &str, + author: &str, + id: &str, + ) -> CommitInfo { + CommitInfo { + message: message.to_string(), + time, + author: author.to_string(), + id: CommitId::from_hex_str(id) + .expect("invalid commit id"), + } + } + + fn filter( + commits: Vec, + filter: &str, + ) -> Vec { + let filter_string = Revlog::get_what_to_filter_by(filter); + dbg!(&filter_string); + AsyncCommitFilterer::filter(commits, &None, &filter_string) + } + + #[test] + fn test_filter() { + let commits = vec![ + commit(0, "a", "b", "0"), + commit(1, "0", "0", "a"), + commit(2, "0", "A", "b"), + commit(3, "0", "0", "0"), + ]; + + let filtered = |indices: &[usize]| { + indices + .iter() + .map(|i| commits[*i].clone()) + .collect::>() + }; + + assert_eq!( + filter(commits.clone(), "a"), // + filtered(&[0, 1, 2]) + ); + assert_eq!( + filter(commits.clone(), "A"), // + filtered(&[0, 1, 2]) + ); + + assert_eq!( + filter(commits.clone(), ":m a"), // + filtered(&[0]), + ); + assert_eq!( + filter(commits.clone(), ":s a"), // + filtered(&[1]), + ); + assert_eq!( + filter(commits.clone(), ":a a"), // + filtered(&[2]), + ); + + assert_eq!( + filter(commits.clone(), ":! a"), // + filtered(&[3]), + ); + + assert_eq!( + filter(commits.clone(), ":!m a"), // + filtered(&[1, 2, 3]), + ); + assert_eq!( + filter(commits.clone(), ":!s a"), // + filtered(&[0, 2, 3]), + ); + assert_eq!( + filter(commits.clone(), ":!a a"), // + filtered(&[0, 1, 3]), + ); + + assert_eq!( + filter(commits.clone(), "a && b"), // + filtered(&[0, 2]), + ); + + assert_eq!( + filter(commits.clone(), ":m a && :a b"), // + filtered(&[0]), + ); + assert_eq!( + filter(commits.clone(), "b && :!m a"), // + filtered(&[2]), + ); + assert_eq!( + filter(commits.clone(), ":! b && a"), // + filtered(&[1]), + ); + assert_eq!( + filter(commits.clone(), ":! b && :! a"), // + filtered(&[3]), + ); + + assert_eq!( + filter(commits.clone(), ":c a"), // + filtered(&[0, 1]), + ); + assert_eq!( + filter(commits.clone(), ":c A"), // + filtered(&[2]), + ); + assert_eq!( + filter(commits.clone(), ":!c a"), // + filtered(&[2, 3]), + ); + } +} diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 1595e64d8e..40c13b38a6 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -253,7 +253,7 @@ impl Revlog { /// /// Currently parentheses in the `filter_by_str` are not supported. /// They should be removed by `Self::pre_process_string`. - fn get_what_to_filter_by( + pub fn get_what_to_filter_by( filter_by_str: &str, ) -> Vec> { let mut search_vec = Vec::new(); @@ -262,7 +262,7 @@ impl Revlog { for split_sub in or.split("&&").map(str::trim) { if !split_sub.starts_with(':') { and_vec.push(( - split_sub.to_string(), + split_sub.to_lowercase(), FilterBy::everywhere(), )); continue; @@ -284,14 +284,17 @@ impl Revlog { to_filter_by |= FilterBy::everywhere(); } - and_vec.push(( - split_str - .next() - .unwrap_or("") - .trim_start() - .to_string(), - to_filter_by, - )); + let mut s = split_str + .next() + .unwrap_or("") + .trim_start() + .to_string(); + + if !to_filter_by.contains(FilterBy::CASE_SENSITIVE) { + s = s.to_lowercase(); + } + + and_vec.push((s, to_filter_by)); } search_vec.push(and_vec.clone()); and_vec.clear(); @@ -778,6 +781,11 @@ mod test { vec![vec![("foo".to_owned(), FilterBy::everywhere())]] ); + assert_eq!( + Revlog::get_what_to_filter_by("Foo"), + vec![vec![("foo".to_owned(), FilterBy::everywhere())]] + ); + assert_eq!( Revlog::get_what_to_filter_by(":s foo"), vec![vec![("foo".to_owned(), FilterBy::SHA)]] @@ -791,6 +799,18 @@ mod test { )]] ); + assert_eq!( + Revlog::get_what_to_filter_by(": Foo"), + vec![vec![("foo".to_owned(), FilterBy::everywhere())]] + ); + assert_eq!( + Revlog::get_what_to_filter_by(":c Foo"), + vec![vec![( + "Foo".to_owned(), + FilterBy::everywhere() | FilterBy::CASE_SENSITIVE + )]], + ); + assert_eq!( Revlog::get_what_to_filter_by(":samt foo"), vec![vec![("foo".to_owned(), FilterBy::everywhere())]] @@ -859,6 +879,17 @@ mod test { vec![("baz".to_owned(), FilterBy::MESSAGE)] ] ); + + assert_eq!( + Revlog::get_what_to_filter_by("foo || :m bar && baz"), + vec![ + vec![("foo".to_owned(), FilterBy::everywhere())], + vec![ + ("bar".to_owned(), FilterBy::MESSAGE), + ("baz".to_owned(), FilterBy::everywhere()) + ] + ] + ); } #[test] From e0e3785df6214280225b9fb0acc31b71c47760be Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 9 Jul 2023 13:53:17 -0300 Subject: [PATCH 112/118] Drop support for `&&(` --- src/tabs/revlog.rs | 68 ++-------------------------------------------- 1 file changed, 2 insertions(+), 66 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 40c13b38a6..a2d789a694 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -251,8 +251,7 @@ impl Revlog { /// 1. from outer vec should be combined via 'disjunction' (or); /// 2. from inter vec should be combined via 'conjunction' (and). /// - /// Currently parentheses in the `filter_by_str` are not supported. - /// They should be removed by `Self::pre_process_string`. + /// Parentheses in the `filter_by_str` are not supported. pub fn get_what_to_filter_by( filter_by_str: &str, ) -> Vec> { @@ -305,16 +304,12 @@ impl Revlog { pub fn filter(&mut self, filter_by: &str) -> Result<()> { if filter_by != self.filter_string { self.filter_string = filter_by.to_string(); - let pre_processed_string = - Self::pre_process_string(filter_by.to_string()); - let trimmed_string = - pre_processed_string.trim().to_string(); if filter_by.is_empty() { self.async_filter.stop_filter(); self.is_filtering = false; } else { let filter_strings = - Self::get_what_to_filter_by(&trimmed_string); + Self::get_what_to_filter_by(filter_by); self.async_filter .start_filter(filter_strings) .map_err(|e| anyhow::anyhow!(e.to_string()))?; @@ -326,65 +321,6 @@ impl Revlog { Ok(()) } - /// pre process string to remove any brackets - pub fn pre_process_string(mut s: String) -> String { - while s.contains("&&(") { - let before = s.clone(); - s = Self::remove_out_brackets(&s); - if s == before { - break; - } - } - s - } - - /// Remove the brakcets, replacing them with the unbracketed 'full' expression - pub fn remove_out_brackets(s: &str) -> String { - if let Some(first_bracket) = s.find("&&(") { - let (first, rest_of_string) = - s.split_at(first_bracket + 3); - if let Some(last_bracket) = - Self::get_ending_bracket(rest_of_string) - { - let mut v = vec![]; - let (second, third) = - rest_of_string.split_at(last_bracket); - if let Some((first, third)) = first - .strip_suffix('(') - .zip(third.strip_prefix(')')) - { - for inside_bracket_item in second.split("||") { - // Append first, prepend third onto bracket element - v.push(format!( - "{first}{inside_bracket_item}{third}" - )); - } - return v.join("||"); - } - } - } - s.to_string() - } - - /// Get outer matching brakets in a string - pub fn get_ending_bracket(s: &str) -> Option { - let mut brack_count = 0; - let mut ending_brakcet_pos = None; - for (i, c) in s.chars().enumerate() { - if c == '(' { - brack_count += 1; - } else if c == ')' { - if brack_count == 0 { - // Found - ending_brakcet_pos = Some(i); - break; - } - brack_count -= 1; - } - } - ending_brakcet_pos - } - pub fn select_commit(&mut self, id: CommitId) -> Result<()> { let position = self.git_log.position(id)?; From acb268fb07140edd2ac8c54bda45a4227b6a504e Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 9 Jul 2023 14:05:55 -0300 Subject: [PATCH 113/118] Revert rename of `set_count_total` --- src/components/commitlist.rs | 4 ++-- src/tabs/revlog.rs | 4 ++-- src/tabs/stashlist.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 7f2fc0ba9f..ba799eaf71 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -96,8 +96,8 @@ impl CommitList { } /// - pub fn set_total_count(&mut self, count: usize) { - self.count_total = count; + pub fn set_count_total(&mut self, total: usize) { + self.count_total = total; self.selection = cmp::min(self.selection, self.selection_max()); } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index a2d789a694..e2b731fbde 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -118,9 +118,9 @@ impl Revlog { }; if self.is_filtering { - self.list.set_total_count(self.async_filter.count()); + self.list.set_count_total(self.async_filter.count()); } else { - self.list.set_total_count(self.git_log.count()?); + self.list.set_count_total(self.git_log.count()?); }; let selection = self.list.selection(); diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index 006bbf80b9..151ec87be1 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -54,7 +54,7 @@ impl StashList { 100, )?; - self.list.set_total_count(commits.len()); + self.list.set_count_total(commits.len()); self.list.items().set_items(0, commits); } From 57dbad2e9a5d19f87973381f4b45318c1bb689b1 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 9 Jul 2023 14:41:55 -0300 Subject: [PATCH 114/118] Add unit-test for filtering tags --- src/components/utils/async_commit_filter.rs | 89 +++++++++++++++++++-- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 4b3aec2d5a..8da7ac8ad3 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -167,8 +167,7 @@ impl AsyncCommitFilterer { t.get(&commit.id).map_or(false, |commit_tags| { commit_tags .iter() - .filter(|tag| tag.name.contains(s)) - .count() > 0 + .any(|tag| tag.name.contains(s)) }) }); @@ -360,7 +359,9 @@ impl AsyncCommitFilterer { #[cfg(test)] mod test { - use asyncgit::sync::{CommitId, CommitInfo}; + use std::collections::BTreeMap; + + use asyncgit::sync::{CommitId, CommitInfo, Tag, Tags}; use crate::tabs::Revlog; @@ -393,10 +394,10 @@ mod test { #[test] fn test_filter() { let commits = vec![ - commit(0, "a", "b", "0"), - commit(1, "0", "0", "a"), - commit(2, "0", "A", "b"), - commit(3, "0", "0", "0"), + commit(0, "a", "b", "00"), + commit(1, "0", "0", "a1"), + commit(2, "0", "A", "b2"), + commit(3, "0", "0", "03"), ]; let filtered = |indices: &[usize]| { @@ -481,4 +482,78 @@ mod test { filtered(&[2, 3]), ); } + + fn filter_with_tags( + commits: Vec, + tags: &Option, + filter: &str, + ) -> Vec { + let filter_string = Revlog::get_what_to_filter_by(filter); + dbg!(&filter_string); + AsyncCommitFilterer::filter(commits, tags, &filter_string) + } + + #[test] + fn test_filter_with_tags() { + let commits = vec![ + commit(0, "a", "b", "00"), + commit(1, "0", "0", "a1"), + commit(2, "0", "A", "b2"), + commit(3, "0", "0", "03"), + ]; + + let filtered = |indices: &[usize]| { + indices + .iter() + .map(|i| commits[*i].clone()) + .collect::>() + }; + + let tags = { + let mut tags: BTreeMap> = + BTreeMap::new(); + let mut tag = |index: usize, name: &str, annotation| { + tags.entry(commits[index].id).or_default().push( + Tag { + name: name.to_string(), + annotation, + }, + ); + }; + + tag(0, "v0", None); + tag(2, "v1", None); + tag(1, "ot", None); + + tags + }; + dbg!(&tags); + let tags = &Some(tags); + + assert_eq!( + filter_with_tags(commits.clone(), tags, ":t v0"), // + filtered(&[0]) + ); + assert_eq!( + filter_with_tags(commits.clone(), tags, ":t v1"), // + filtered(&[2]) + ); + assert_eq!( + filter_with_tags(commits.clone(), tags, ":t v"), // + filtered(&[0, 2]) + ); + assert_eq!( + filter_with_tags(commits.clone(), tags, ":!t v"), // + filtered(&[1, 3]) + ); + + assert_eq!( + filter_with_tags(commits.clone(), tags, ":t"), // + filtered(&[0, 1, 2]) + ); + assert_eq!( + filter_with_tags(commits.clone(), tags, ":!t"), // + filtered(&[3]) + ); + } } From 0595cb6ab0fe175f305a0a124c44c5146453b29f Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 9 Jul 2023 15:30:19 -0300 Subject: [PATCH 115/118] Add unit-tests for contains_tag Was checking why searching for tag were not working. But `contains_tag` was alright. --- src/components/utils/async_commit_filter.rs | 37 +++++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/components/utils/async_commit_filter.rs b/src/components/utils/async_commit_filter.rs index 8da7ac8ad3..8567d5db1c 100644 --- a/src/components/utils/async_commit_filter.rs +++ b/src/components/utils/async_commit_filter.rs @@ -190,12 +190,10 @@ impl AsyncCommitFilterer { true } - /// If the filtering string contain filtering by tags - /// return them, else don't get the tags - fn get_tags( + /// Check if the filtering string contain filtering by tags. + fn contains_tag( filter_strings: &[Vec<(String, FilterBy)>], - git_tags: &mut AsyncTags, - ) -> Result> { + ) -> bool { let mut contains_tags = false; for or in filter_strings { for (_, filter_by) in or { @@ -209,10 +207,7 @@ impl AsyncCommitFilterer { } } - if contains_tags { - return git_tags.last().map_err(|e| anyhow::anyhow!(e)); - } - Ok(None) + contains_tags } pub fn start_filter( @@ -244,8 +239,12 @@ impl AsyncCommitFilterer { let filter_thread_mutex = Arc::clone(&self.filter_thread_mutex); - let tags = - Self::get_tags(&filter_strings, &mut self.git_tags)?; + let tags = Self::contains_tag(&filter_strings) + .then(|| { + self.git_tags.last().map_err(|e| anyhow::anyhow!(e)) + }) + .transpose()? + .flatten(); let repo = self.repo.clone(); @@ -556,4 +555,20 @@ mod test { filtered(&[3]) ); } + + #[test] + fn test_contains_tag() { + assert!(AsyncCommitFilterer::contains_tag( + &Revlog::get_what_to_filter_by("") + )); + assert!(AsyncCommitFilterer::contains_tag( + &Revlog::get_what_to_filter_by(":") + )); + assert!(AsyncCommitFilterer::contains_tag( + &Revlog::get_what_to_filter_by(":t") + )); + assert!(!AsyncCommitFilterer::contains_tag( + &Revlog::get_what_to_filter_by(":sma") + )); + } } From c43b39d56348794ec63a9e65d115df7a807a070a Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 9 Jul 2023 15:32:00 -0300 Subject: [PATCH 116/118] Make AsyncTags share its result AsyncCommitFilterer were assuming that that would be the case. --- asyncgit/src/tags.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/asyncgit/src/tags.rs b/asyncgit/src/tags.rs index 6369307663..3e57159e84 100644 --- a/asyncgit/src/tags.rs +++ b/asyncgit/src/tags.rs @@ -22,7 +22,7 @@ pub struct TagsResult { /// #[derive(Clone)] pub struct AsyncTags { - last: Option<(Instant, TagsResult)>, + last: Arc>>, sender: Sender, job: AsyncSingleJob, repo: RepoPath, @@ -36,7 +36,7 @@ impl AsyncTags { ) -> Self { Self { repo, - last: None, + last: Arc::new(Mutex::new(None)), sender: sender.clone(), job: AsyncSingleJob::new(sender.clone()), } @@ -44,7 +44,11 @@ impl AsyncTags { /// last fetched result pub fn last(&self) -> Result> { - Ok(self.last.as_ref().map(|result| result.1.tags.clone())) + Ok(self + .last + .lock()? + .as_ref() + .map(|result| result.1.tags.clone())) } /// @@ -53,10 +57,12 @@ impl AsyncTags { } /// - fn is_outdated(&self, dur: Duration) -> bool { - self.last + fn is_outdated(&self, dur: Duration) -> Result { + Ok(self + .last + .lock()? .as_ref() - .map_or(true, |(last_time, _)| last_time.elapsed() > dur) + .map_or(true, |(last_time, _)| last_time.elapsed() > dur)) } /// @@ -71,7 +77,7 @@ impl AsyncTags { return Ok(()); } - let outdated = self.is_outdated(dur); + let outdated = self.is_outdated(dur)?; if !force && !outdated { return Ok(()); @@ -82,6 +88,7 @@ impl AsyncTags { if outdated { self.job.spawn(AsyncTagsJob::new( self.last + .lock()? .as_ref() .map_or(0, |(_, result)| result.hash), repo, @@ -89,7 +96,7 @@ impl AsyncTags { if let Some(job) = self.job.take_last() { if let Some(Ok(result)) = job.result() { - self.last = Some(result); + *self.last.lock()? = Some(result); } } } else { From 7b49d20ef273865ec377d0ed0593c7445067d164 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 9 Jul 2023 15:38:07 -0300 Subject: [PATCH 117/118] Updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b17b40c18d..3bac67b4bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * support 'n'/'p' key to move to the next/prev hunk in diff component [[@hamflx](https://github.com/hamflx)] ([#1523](https://github.com/extrawurst/gitui/issues/1523)) * simplify theme overrides [[@cruessler](https://github.com/cruessler)] ([#1367](https://github.com/extrawurst/gitui/issues/1367)) +* allow searching for commits in the revlog tab by sha, author, message or tag [[@WizardOhio24](https://github.com/WizardOhio24), [@willir](https://github.com/willir), [@Rodrigodd](https://github.com/Rodrigodd)] [#1753](https://github.com/extrawurst/gitui/issues/1753) ### Fixes * fix commit dialog char count for multibyte characters ([#1726](https://github.com/extrawurst/gitui/issues/1726)) From 6516e857eda1a87e991130cf4da00d93da3d2700 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sun, 9 Jul 2023 16:07:40 -0300 Subject: [PATCH 118/118] Fix vim_style_key_config.ron] --- vim_style_key_config.ron | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vim_style_key_config.ron b/vim_style_key_config.ron index effc8da503..16d2d6b391 100644 --- a/vim_style_key_config.ron +++ b/vim_style_key_config.ron @@ -41,8 +41,8 @@ stash_open: Some(( code: Char('l'), modifiers: ( bits: 0,),)), - show_find_commit_text_input: ( code: Char('s'), modifiers: ( bits: 0,),), - focus_find_commit: ( code: Char('j'), modifiers: ( bits: 3,),), + show_find_commit_text_input: Some(( code: Char('s'), modifiers: ( bits: 0,),)), + focus_find_commit: Some(( code: Char('j'), modifiers: ( bits: 3,),)), abort_merge: Some(( code: Char('M'), modifiers: ( bits: 1,),)), )