summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Oram <dev@mitmaro.ca>2022-12-21 14:33:13 -0330
committerTim Oram <dev@mitmaro.ca>2022-12-31 17:00:59 -0330
commitbdf2f38066bc394584bc977f8ac1ad9d66aa1130 (patch)
tree59e8b98ca411d2665f3c5efcdc5f672d351b9c7e
parent210970cb51febf19f754842b8407cd85f9767d83 (diff)
Integrate search thread and handling
-rw-r--r--src/core/src/application.rs29
-rw-r--r--src/core/src/events/meta_event.rs2
-rw-r--r--src/core/src/lib.rs4
-rw-r--r--src/core/src/process/artifact.rs56
-rw-r--r--src/core/src/process/mod.rs23
-rw-r--r--src/core/src/process/results.rs40
-rw-r--r--src/core/src/process/tests.rs70
-rw-r--r--src/core/src/search/mod.rs3
-rw-r--r--src/core/src/search/thread.rs7
-rw-r--r--src/core/src/search/update_handler.rs4
-rw-r--r--src/core/src/testutil/assert_results.rs3
-rw-r--r--src/core/src/testutil/mocked_searchable.rs17
-rw-r--r--src/core/src/testutil/mod.rs4
-rw-r--r--src/core/src/testutil/process_test.rs39
-rw-r--r--src/core/src/testutil/with_search.rs10
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() });
+}