From bdf2f38066bc394584bc977f8ac1ad9d66aa1130 Mon Sep 17 00:00:00 2001 From: Tim Oram Date: Wed, 21 Dec 2022 14:33:13 -0330 Subject: Integrate search thread and handling --- src/core/src/application.rs | 29 ++++++++++++- src/core/src/events/meta_event.rs | 2 + src/core/src/lib.rs | 4 +- src/core/src/process/artifact.rs | 56 ++++++++++++++++++++++-- src/core/src/process/mod.rs | 23 ++++++++++ src/core/src/process/results.rs | 40 +++++++++++++++++ src/core/src/process/tests.rs | 70 +++++++++++++++++++++++++++++- src/core/src/search/mod.rs | 3 +- src/core/src/search/thread.rs | 7 +-- src/core/src/search/update_handler.rs | 4 ++ src/core/src/testutil/assert_results.rs | 3 ++ src/core/src/testutil/mocked_searchable.rs | 17 ++++++++ src/core/src/testutil/mod.rs | 4 ++ src/core/src/testutil/process_test.rs | 39 +++++++++-------- src/core/src/testutil/with_search.rs | 10 +++++ 15 files changed, 282 insertions(+), 29 deletions(-) create mode 100644 src/core/src/search/update_handler.rs create mode 100644 src/core/src/testutil/mocked_searchable.rs create mode 100644 src/core/src/testutil/with_search.rs diff --git a/src/core/src/application.rs b/src/core/src/application.rs index 636465f..7c66b29 100644 --- a/src/core/src/application.rs +++ b/src/core/src/application.rs @@ -4,7 +4,7 @@ use anyhow::Result; use config::Config; use display::Display; use git::Repository; -use input::{EventHandler, EventReaderFn}; +use input::{Event, EventHandler, EventReaderFn}; use parking_lot::Mutex; use runtime::{Runtime, Threadable}; use todo_file::TodoFile; @@ -12,10 +12,12 @@ use view::View; use crate::{ events, - events::KeyBindings, + events::{KeyBindings, MetaEvent}, help::build_help, module::{self, ExitStatus, ModuleHandler}, process::{self, Process}, + search, + search::UpdateHandlerFn, Args, Exit, }; @@ -71,12 +73,18 @@ where ModuleProvider: module::ModuleProvider + Send + 'static let view_state = view_threads.state(); threads.push(Box::new(view_threads)); + let search_update_handler = Self::create_search_update_handler(input_state.clone()); + let search_threads = search::Thread::new(search_update_handler); + let search_state = search_threads.state(); + threads.push(Box::new(search_threads)); + let process = Process::new( initial_display_size, todo_file, module_handler, input_state, view_state, + search_state, runtime.statuses(), ); let process_threads = process::Thread::new(process.clone()); @@ -160,6 +168,10 @@ where ModuleProvider: module::ModuleProvider + Send + 'static Ok(todo_file) } + + fn create_search_update_handler(input_state: events::State) -> impl Fn() + Send + Sync { + move || input_state.push_event(Event::MetaEvent(MetaEvent::SearchUpdate)) + } } #[cfg(all(unix, test))] @@ -290,6 +302,19 @@ mod tests { ); } + #[test] + #[serial_test::serial] + fn search_update_handler_handles_update() { + let event_provider = create_event_reader(|| Ok(None)); + let input_threads = events::Thread::new(event_provider); + let input_state = input_threads.state(); + let update_handler = + Application::>::create_search_update_handler(input_state.clone()); + update_handler(); + + assert_eq!(input_state.read_event(), Event::MetaEvent(MetaEvent::SearchUpdate)); + } + #[test] #[serial_test::serial] fn run_until_finished_success() { diff --git a/src/core/src/events/meta_event.rs b/src/core/src/events/meta_event.rs index 542731f..ede488b 100644 --- a/src/core/src/events/meta_event.rs +++ b/src/core/src/events/meta_event.rs @@ -70,6 +70,8 @@ pub(crate) enum MetaEvent { ExternalCommandSuccess, /// The external command was an error meta event. ExternalCommandError, + /// Search was updated + SearchUpdate, } impl input::CustomEvent for MetaEvent {} diff --git a/src/core/src/lib.rs b/src/core/src/lib.rs index 88d03a4..53922a7 100644 --- a/src/core/src/lib.rs +++ b/src/core/src/lib.rs @@ -113,7 +113,8 @@ clippy::redundant_closure_for_method_calls, clippy::wildcard_enum_match_arm, missing_docs, - rustdoc::missing_crate_level_docs + rustdoc::missing_crate_level_docs, + unused )] mod application; @@ -127,7 +128,6 @@ mod license; mod module; mod modules; mod process; -#[allow(dead_code)] mod search; #[cfg(test)] mod tests; diff --git a/src/core/src/process/artifact.rs b/src/core/src/process/artifact.rs index 12f09f0..18bb89e 100644 --- a/src/core/src/process/artifact.rs +++ b/src/core/src/process/artifact.rs @@ -1,17 +1,67 @@ +use std::fmt::{Debug, Formatter}; + use anyhow::Error; use crate::{ events::Event, module::{ExitStatus, State}, + search::Searchable, }; -#[derive(Debug)] #[allow(variant_size_differences)] pub(crate) enum Artifact { - Event(Event), ChangeState(State), + EnqueueResize, Error(Error, Option), + Event(Event), ExitStatus(ExitStatus), ExternalCommand((String, Vec)), - EnqueueResize, + SearchCancel, + SearchTerm(String), + Searchable(Box), +} + +impl Debug for Artifact { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match *self { + Self::ChangeState(state) => write!(f, "ChangeState({state:?})"), + Self::EnqueueResize => write!(f, "EnqueueResize"), + Self::Error(ref err, state) => write!(f, "Error({err:?}, {state:?})"), + Self::Event(event) => write!(f, "Event({event:?})"), + Self::ExitStatus(status) => write!(f, "ExitStatus({status:?})"), + Self::ExternalCommand((ref command, ref args)) => write!(f, "ExternalCommand({command:?}, {args:?})"), + Self::SearchCancel => write!(f, "SearchCancel"), + Self::SearchTerm(ref term) => write!(f, "SearchTerm({term:?})"), + Self::Searchable(_) => write!(f, "Searchable(dyn Searchable)"), + } + } +} + +#[cfg(test)] +mod tests { + use anyhow::anyhow; + use rstest::rstest; + + use super::*; + use crate::{ + search::{Interrupter, SearchResult}, + testutil::MockedSearchable, + }; + + #[rstest] + #[case::change_state(Artifact::ChangeState(State::List), "ChangeState(List)")] + #[case::enqueue_resize(Artifact::EnqueueResize, "EnqueueResize")] + #[case::error(Artifact::Error(anyhow!("Error"), Some(State::List)), "Error(Error, Some(List))")] + #[case::event(Artifact::Event(Event::None), "Event(None)")] + #[case::exit_status(Artifact::ExitStatus(ExitStatus::Abort), "ExitStatus(Abort)")] + #[case::external_command(Artifact::ExternalCommand((String::from("foo"), vec![])), "ExternalCommand(\"foo\", [])")] + #[case::search_cancel(Artifact::SearchCancel, "SearchCancel")] + #[case::search_term(Artifact::SearchTerm(String::from("foo")), "SearchTerm(\"foo\")")] + #[case::searchable( + Artifact::Searchable(Box::new(MockedSearchable::new())), + "Searchable(dyn Searchable)" + )] + fn debug(#[case] artifact: Artifact, #[case] expected: &str) { + assert_eq!(format!("{artifact:?}"), expected); + } } diff --git a/src/core/src/process/mod.rs b/src/core/src/process/mod.rs index ba849aa..d6d29e2 100644 --- a/src/core/src/process/mod.rs +++ b/src/core/src/process/mod.rs @@ -26,6 +26,7 @@ use crate::{ events, events::{Event, MetaEvent}, module::{self, ExitStatus, ModuleHandler, State}, + search::{self, Action, Searchable}, }; pub(crate) struct Process { @@ -39,6 +40,7 @@ pub(crate) struct Process { thread_statuses: ThreadStatuses, todo_file: Arc>, view_state: view::State, + search_state: search::State, } impl Clone for Process { @@ -54,6 +56,7 @@ impl Clone for Process { thread_statuses: self.thread_statuses.clone(), todo_file: Arc::clone(&self.todo_file), view_state: self.view_state.clone(), + search_state: self.search_state.clone(), } } } @@ -65,6 +68,7 @@ impl Process { module_handler: ModuleHandler, input_state: events::State, view_state: view::State, + search_state: search::State, thread_statuses: ThreadStatuses, ) -> Self { Self { @@ -77,6 +81,7 @@ impl Process { initial_display_size.width(), initial_display_size.height(), ))), + search_state, state: Arc::new(Mutex::new(State::WindowSizeError)), thread_statuses, todo_file, @@ -266,6 +271,21 @@ impl Process { result } + fn handle_search_cancel(&self) -> Results { + self.search_state.send_update(Action::Cancel); + Results::new() + } + + fn handle_search_term(&self, term: String) -> Results { + self.search_state.send_update(Action::Start(term)); + Results::new() + } + + fn handle_searchable(&self, searchable: Box) -> Results { + self.search_state.send_update(Action::SetSearchable(searchable)); + Results::new() + } + fn handle_results(&self, mut results: Results) { while let Some(artifact) = results.artifact() { results.append(match artifact { @@ -275,6 +295,9 @@ impl Process { Artifact::Event(event) => self.handle_event_artifact(event), Artifact::ExitStatus(exit_status) => self.handle_exit_status(exit_status), Artifact::ExternalCommand(command) => self.handle_external_command(&command), + Artifact::SearchCancel => self.handle_search_cancel(), + Artifact::SearchTerm(search_term) => self.handle_search_term(search_term), + Artifact::Searchable(searchable) => self.handle_searchable(searchable), }); } } diff --git a/src/core/src/process/results.rs b/src/core/src/process/results.rs index fc0eb21..61dd569 100644 --- a/src/core/src/process/results.rs +++ b/src/core/src/process/results.rs @@ -6,6 +6,7 @@ use crate::{ events::Event, module::{ExitStatus, State}, process::artifact::Artifact, + search::Searchable, }; #[derive(Debug)] @@ -40,6 +41,14 @@ impl Results { self.artifacts.push_back(Artifact::ChangeState(new_state)); } + pub(crate) fn search_cancel(&mut self) { + self.artifacts.push_back(Artifact::SearchCancel); + } + + pub(crate) fn search_term(&mut self, term: &str) { + self.artifacts.push_back(Artifact::SearchTerm(String::from(term))); + } + pub(crate) fn external_command(&mut self, command: String, arguments: Vec) { self.artifacts .push_back(Artifact::ExternalCommand((command, arguments))); @@ -90,11 +99,20 @@ impl From for Results { } } +impl From> for Results { + fn from(searchable: Box) -> Self { + Self { + artifacts: VecDeque::from(vec![Artifact::Searchable(searchable)]), + } + } +} + #[cfg(test)] mod tests { use anyhow::anyhow; use super::*; + use crate::testutil::MockedSearchable; #[test] fn empty() { @@ -146,6 +164,28 @@ mod tests { assert!(matches!(results.artifact(), Some(Artifact::ChangeState(State::List)))); } + #[test] + fn search_cancel() { + let mut results = Results::new(); + results.search_cancel(); + assert!(matches!(results.artifact(), Some(Artifact::SearchCancel))); + } + + #[test] + fn search_term() { + let mut results = Results::new(); + let search_term = String::from("foo"); + results.search_term(search_term.as_str()); + assert!(matches!(results.artifact(), Some(Artifact::SearchTerm(search_term)))); + } + + #[test] + fn searchable() { + let mocked_searchable: Box = Box::new(MockedSearchable::new()); + let mut results = Results::from(mocked_searchable); + assert!(matches!(results.artifact(), Some(Artifact::Searchable(_)))); + } + #[test] fn external_command() { let mut results = Results::new(); diff --git a/src/core/src/process/tests.rs b/src/core/src/process/tests.rs index 043054e..e91a631 100644 --- a/src/core/src/process/tests.rs +++ b/src/core/src/process/tests.rs @@ -11,7 +11,14 @@ use crate::{ assert_results, events::KeyBindings, module::{Module, DEFAULT_INPUT_OPTIONS, DEFAULT_VIEW_DATA}, - testutil::{create_default_test_module_handler, create_test_module_handler, process_test, ProcessTestContext}, + search::{Interrupter, SearchResult}, + testutil::{ + create_default_test_module_handler, + create_test_module_handler, + process_test, + MockedSearchable, + ProcessTestContext, + }, }; #[derive(Clone)] @@ -583,3 +590,64 @@ fn handle_results_external_command_success() { }, ); } + +#[test] +fn handle_search_cancel() { + let module = TestModule::new(); + process_test( + create_test_module_handler(module), + |ProcessTestContext { + process, + search_context, + .. + }| { + let mut results = Results::new(); + results.search_cancel(); + process.handle_results(results); + assert!(matches!(search_context.state.receive_update(), Action::Cancel)); + }, + ); +} + +#[test] +fn handle_search_term() { + let module = TestModule::new(); + process_test( + create_test_module_handler(module), + |ProcessTestContext { + process, + search_context, + .. + }| { + let mut results = Results::new(); + let search_term = String::from("foo"); + results.search_term(search_term.as_str()); + process.handle_results(results); + assert!(matches!( + search_context.state.receive_update(), + Action::Start(search_term) + )); + }, + ); +} + +#[test] +fn handle_searchable() { + let module = TestModule::new(); + process_test( + create_test_module_handler(module), + |ProcessTestContext { + process, + search_context, + .. + }| { + let searchable: Box = Box::new(MockedSearchable {}); + let results = Results::from(searchable); + process.handle_results(results); + assert!(matches!( + search_context.state.receive_update(), + Action::SetSearchable(_) + )); + }, + ); +} diff --git a/src/core/src/search/mod.rs b/src/core/src/search/mod.rs index 1fbf7f9..767b11b 100644 --- a/src/core/src/search/mod.rs +++ b/src/core/src/search/mod.rs @@ -5,8 +5,8 @@ mod search_state; mod searchable; mod state; mod thread; +mod update_handler; -#[allow(unused_imports)] pub(crate) use self::{ action::Action, interrupter::Interrupter, @@ -15,4 +15,5 @@ pub(crate) use self::{ searchable::Searchable, state::State, thread::Thread, + update_handler::UpdateHandlerFn, }; diff --git a/src/core/src/search/thread.rs b/src/core/src/search/thread.rs index f968ed5..c39eabb 100644 --- a/src/core/src/search/thread.rs +++ b/src/core/src/search/thread.rs @@ -13,6 +13,7 @@ use crate::search::{ search_result::SearchResult, searchable::Searchable, State, + UpdateHandlerFn, }; pub(crate) const THREAD_NAME: &str = "search"; @@ -20,13 +21,13 @@ const MINIMUM_PAUSE_RATE: Duration = Duration::from_millis(50); const SEARCH_INTERRUPT_TIME: Duration = Duration::from_millis(10); #[derive(Debug)] -pub(crate) struct Thread { +pub(crate) struct Thread { state: State, search_update_handler: Arc, } impl Threadable for Thread -where UpdateHandler: Fn() + Sync + Send + 'static +where UpdateHandler: UpdateHandlerFn + 'static { #[inline] fn install(&self, installer: &Installer) { @@ -116,7 +117,7 @@ where UpdateHandler: Fn() + Sync + Send + 'static } impl Thread -where UpdateHandler: Fn() + Sync + Send +where UpdateHandler: UpdateHandlerFn { pub(crate) fn new(search_update_handler: UpdateHandler) -> Self { Self { diff --git a/src/core/src/search/update_handler.rs b/src/core/src/search/update_handler.rs new file mode 100644 index 0000000..c8c6598 --- /dev/null +++ b/src/core/src/search/update_handler.rs @@ -0,0 +1,4 @@ +/// Function for handling +pub(crate) trait UpdateHandlerFn: Fn() + Sync + Send {} + +impl UpdateHandlerFn for FN {} diff --git a/src/core/src/testutil/assert_results.rs b/src/core/src/testutil/assert_results.rs index be4cae2..76ad5b5 100644 --- a/src/core/src/testutil/assert_results.rs +++ b/src/core/src/testutil/assert_results.rs @@ -20,6 +20,9 @@ fn _assert_results_format(artifacts: &[Artifact]) -> String { format!("ExternalCommand({:?} {:?})", command.0, command.1.join(",")) }, Artifact::EnqueueResize => String::from("EnqueueResize"), + Artifact::SearchCancel => String::from("SearchCancel"), + Artifact::SearchTerm(ref term) => format!("SearchTerm({term})"), + Artifact::Searchable(ref _searchable) => String::from("SearchCancel(_)"), } }) .collect::>() diff --git a/src/core/src/testutil/mocked_searchable.rs b/src/core/src/testutil/mocked_searchable.rs new file mode 100644 index 0000000..703234f --- /dev/null +++ b/src/core/src/testutil/mocked_searchable.rs @@ -0,0 +1,17 @@ +use crate::search::{Interrupter, SearchResult, Searchable}; + +pub(crate) struct MockedSearchable; + +impl MockedSearchable { + pub(crate) const fn new() -> Self { + Self {} + } +} + +impl Searchable for MockedSearchable { + fn reset(&mut self) {} + + fn search(&mut self, _: Interrupter, _: &str) -> SearchResult { + SearchResult::None + } +} diff --git a/src/core/src/testutil/mod.rs b/src/core/src/testutil/mod.rs index 116c832..7975040 100644 --- a/src/core/src/testutil/mod.rs +++ b/src/core/src/testutil/mod.rs @@ -1,17 +1,20 @@ mod assert_results; mod create_event_reader; mod create_test_keybindings; +mod mocked_searchable; mod module_test; mod process_test; mod read_event_test; mod set_git_directory; mod test_module_provider; mod with_event_handler; +mod with_search; pub(crate) use self::{ assert_results::_assert_results, create_event_reader::create_event_reader, create_test_keybindings::{create_test_custom_keybindings, create_test_keybindings}, + mocked_searchable::MockedSearchable, module_test::module_test, process_test::{process_test, TestContext as ProcessTestContext}, read_event_test::read_event_test, @@ -23,4 +26,5 @@ pub(crate) use self::{ TestModuleProvider, }, with_event_handler::{with_event_handler, EventHandlerTestContext}, + with_search::{with_search, TestContext as SearchTestContext}, }; diff --git a/src/core/src/testutil/process_test.rs b/src/core/src/testutil/process_test.rs index 0d0fb62..98d9dcc 100644 --- a/src/core/src/testutil/process_test.rs +++ b/src/core/src/testutil/process_test.rs @@ -10,12 +10,13 @@ use crate::{ events::Event, module::{self, ModuleHandler}, process::Process, - testutil::{with_event_handler, EventHandlerTestContext}, + testutil::{with_event_handler, with_search, EventHandlerTestContext, SearchTestContext}, }; pub(crate) struct TestContext { pub(crate) event_handler_context: EventHandlerTestContext, pub(crate) process: Process, + pub(crate) search_context: SearchTestContext, pub(crate) todo_file_path: PathBuf, pub(crate) view_context: ViewContext, } @@ -29,23 +30,27 @@ pub(crate) fn process_test(callback: C) +where C: FnOnce(TestContext) { + callback(TestContext { state: State::new() }); +} -- cgit v1.2.3