diff options
author | Tim Oram <dev@mitmaro.ca> | 2022-12-21 14:33:13 -0330 |
---|---|---|
committer | Tim Oram <dev@mitmaro.ca> | 2022-12-31 17:00:59 -0330 |
commit | bdf2f38066bc394584bc977f8ac1ad9d66aa1130 (patch) | |
tree | 59e8b98ca411d2665f3c5efcdc5f672d351b9c7e | |
parent | 210970cb51febf19f754842b8407cd85f9767d83 (diff) |
Integrate search thread and handling
-rw-r--r-- | src/core/src/application.rs | 29 | ||||
-rw-r--r-- | src/core/src/events/meta_event.rs | 2 | ||||
-rw-r--r-- | src/core/src/lib.rs | 4 | ||||
-rw-r--r-- | src/core/src/process/artifact.rs | 56 | ||||
-rw-r--r-- | src/core/src/process/mod.rs | 23 | ||||
-rw-r--r-- | src/core/src/process/results.rs | 40 | ||||
-rw-r--r-- | src/core/src/process/tests.rs | 70 | ||||
-rw-r--r-- | src/core/src/search/mod.rs | 3 | ||||
-rw-r--r-- | src/core/src/search/thread.rs | 7 | ||||
-rw-r--r-- | src/core/src/search/update_handler.rs | 4 | ||||
-rw-r--r-- | src/core/src/testutil/assert_results.rs | 3 | ||||
-rw-r--r-- | src/core/src/testutil/mocked_searchable.rs | 17 | ||||
-rw-r--r-- | src/core/src/testutil/mod.rs | 4 | ||||
-rw-r--r-- | src/core/src/testutil/process_test.rs | 39 | ||||
-rw-r--r-- | src/core/src/testutil/with_search.rs | 10 |
15 files changed, 282 insertions, 29 deletions
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))] @@ -292,6 +304,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::<TestModuleProvider<DefaultTestModule>>::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() { let git_dir = set_git_directory("fixtures/simple"); let rebase_todo = format!("{git_dir}/rebase-todo"); 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<State>), + Event(Event), ExitStatus(ExitStatus), ExternalCommand((String, Vec<String>)), - EnqueueResize, + SearchCancel, + SearchTerm(String), + Searchable(Box<dyn Searchable>), +} + +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<ModuleProvider: module::ModuleProvider> { @@ -39,6 +40,7 @@ pub(crate) struct Process<ModuleProvider: module::ModuleProvider> { thread_statuses: ThreadStatuses, todo_file: Arc<Mutex<TodoFile>>, view_state: view::State, + search_state: search::State, } impl<ModuleProvider: module::ModuleProvider> Clone for Process<ModuleProvider> { @@ -54,6 +56,7 @@ impl<ModuleProvider: module::ModuleProvider> Clone for Process<ModuleProvider> { 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<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> { module_handler: ModuleHandler<ModuleProvider>, input_state: events::State, view_state: view::State, + search_state: search::State, thread_statuses: ThreadStatuses, ) -> Self { Self { @@ -77,6 +81,7 @@ impl<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> { 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<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> { 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<dyn Searchable>) -> 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<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> { 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<String>) { self.artifacts .push_back(Artifact::ExternalCommand((command, arguments))); @@ -90,11 +99,20 @@ impl From<State> for Results { } } +impl From<Box<dyn Searchable>> for Results { + fn from(searchable: Box<dyn Searchable>) -> 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() { @@ -147,6 +165,28 @@ mod tests { } #[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<dyn Searchable> = 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(); results.external_command(String::from("editor"), vec![String::from("arg1"), String::from("arg2")]); 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<dyn Searchable> = 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<UpdateHandler: Fn() + Sync + Send> { +pub(crate) struct Thread<UpdateHandler: UpdateHandlerFn> { state: State, search_update_handler: Arc<UpdateHandler>, } impl<UpdateHandler> Threadable for Thread<UpdateHandler> -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<UpdateHandler> Thread<UpdateHandler> -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<FN: Fn() + Sync + Send> 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::<Vec<String>>() 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<ModuleProvider: module::ModuleProvider + Send + 'static> { pub(crate) event_handler_context: EventHandlerTestContext, pub(crate) process: Process<ModuleProvider>, + 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<C, ModuleProvider: module::ModuleProvider + Send + 's with_event_handler(&[Event::from('a')], |event_handler_context| { with_view_state(|view_context| { with_todo_file(&[], |todo_file_context| { - let (todo_file_tmp_path, todo_file) = todo_file_context.to_owned(); - let view_state = view_context.state.clone(); - let input_state = event_handler_context.state.clone(); - let todo_file_path = PathBuf::from(todo_file_tmp_path.path()); + with_search(|search_context| { + let (todo_file_tmp_path, todo_file) = todo_file_context.to_owned(); + let view_state = view_context.state.clone(); + let input_state = event_handler_context.state.clone(); + let todo_file_path = PathBuf::from(todo_file_tmp_path.path()); - callback(TestContext { - event_handler_context, - process: Process::new( - Size::new(300, 120), - Arc::new(Mutex::new(todo_file)), - module_handler, - input_state, - view_state, - ThreadStatuses::new(), - ), - todo_file_path, - view_context, + callback(TestContext { + event_handler_context, + process: Process::new( + Size::new(300, 120), + Arc::new(Mutex::new(todo_file)), + module_handler, + input_state, + view_state, + search_context.state.clone(), + ThreadStatuses::new(), + ), + search_context, + todo_file_path, + view_context, + }); }); }); }); diff --git a/src/core/src/testutil/with_search.rs b/src/core/src/testutil/with_search.rs new file mode 100644 index 0000000..d640147 --- /dev/null +++ b/src/core/src/testutil/with_search.rs @@ -0,0 +1,10 @@ +use crate::search::State; + +pub(crate) struct TestContext { + pub(crate) state: State, +} + +pub(crate) fn with_search<C>(callback: C) +where C: FnOnce(TestContext) { + callback(TestContext { state: State::new() }); +} |