Skip to content

Commit bdf2f38

Browse files
committed
Integrate search thread and handling
1 parent 210970c commit bdf2f38

File tree

15 files changed

+282
-29
lines changed

15 files changed

+282
-29
lines changed

src/core/src/application.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ use anyhow::Result;
44
use config::Config;
55
use display::Display;
66
use git::Repository;
7-
use input::{EventHandler, EventReaderFn};
7+
use input::{Event, EventHandler, EventReaderFn};
88
use parking_lot::Mutex;
99
use runtime::{Runtime, Threadable};
1010
use todo_file::TodoFile;
1111
use view::View;
1212

1313
use crate::{
1414
events,
15-
events::KeyBindings,
15+
events::{KeyBindings, MetaEvent},
1616
help::build_help,
1717
module::{self, ExitStatus, ModuleHandler},
1818
process::{self, Process},
19+
search,
20+
search::UpdateHandlerFn,
1921
Args,
2022
Exit,
2123
};
@@ -71,12 +73,18 @@ where ModuleProvider: module::ModuleProvider + Send + 'static
7173
let view_state = view_threads.state();
7274
threads.push(Box::new(view_threads));
7375

76+
let search_update_handler = Self::create_search_update_handler(input_state.clone());
77+
let search_threads = search::Thread::new(search_update_handler);
78+
let search_state = search_threads.state();
79+
threads.push(Box::new(search_threads));
80+
7481
let process = Process::new(
7582
initial_display_size,
7683
todo_file,
7784
module_handler,
7885
input_state,
7986
view_state,
87+
search_state,
8088
runtime.statuses(),
8189
);
8290
let process_threads = process::Thread::new(process.clone());
@@ -160,6 +168,10 @@ where ModuleProvider: module::ModuleProvider + Send + 'static
160168

161169
Ok(todo_file)
162170
}
171+
172+
fn create_search_update_handler(input_state: events::State) -> impl Fn() + Send + Sync {
173+
move || input_state.push_event(Event::MetaEvent(MetaEvent::SearchUpdate))
174+
}
163175
}
164176

165177
#[cfg(all(unix, test))]
@@ -290,6 +302,19 @@ mod tests {
290302
);
291303
}
292304

305+
#[test]
306+
#[serial_test::serial]
307+
fn search_update_handler_handles_update() {
308+
let event_provider = create_event_reader(|| Ok(None));
309+
let input_threads = events::Thread::new(event_provider);
310+
let input_state = input_threads.state();
311+
let update_handler =
312+
Application::<TestModuleProvider<DefaultTestModule>>::create_search_update_handler(input_state.clone());
313+
update_handler();
314+
315+
assert_eq!(input_state.read_event(), Event::MetaEvent(MetaEvent::SearchUpdate));
316+
}
317+
293318
#[test]
294319
#[serial_test::serial]
295320
fn run_until_finished_success() {

src/core/src/events/meta_event.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ pub(crate) enum MetaEvent {
7070
ExternalCommandSuccess,
7171
/// The external command was an error meta event.
7272
ExternalCommandError,
73+
/// Search was updated
74+
SearchUpdate,
7375
}
7476

7577
impl input::CustomEvent for MetaEvent {}

src/core/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@
113113
clippy::redundant_closure_for_method_calls,
114114
clippy::wildcard_enum_match_arm,
115115
missing_docs,
116-
rustdoc::missing_crate_level_docs
116+
rustdoc::missing_crate_level_docs,
117+
unused
117118
)]
118119

119120
mod application;
@@ -127,7 +128,6 @@ mod license;
127128
mod module;
128129
mod modules;
129130
mod process;
130-
#[allow(dead_code)]
131131
mod search;
132132
#[cfg(test)]
133133
mod tests;

src/core/src/process/artifact.rs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,67 @@
1+
use std::fmt::{Debug, Formatter};
2+
13
use anyhow::Error;
24

35
use crate::{
46
events::Event,
57
module::{ExitStatus, State},
8+
search::Searchable,
69
};
710

8-
#[derive(Debug)]
911
#[allow(variant_size_differences)]
1012
pub(crate) enum Artifact {
11-
Event(Event),
1213
ChangeState(State),
14+
EnqueueResize,
1315
Error(Error, Option<State>),
16+
Event(Event),
1417
ExitStatus(ExitStatus),
1518
ExternalCommand((String, Vec<String>)),
16-
EnqueueResize,
19+
SearchCancel,
20+
SearchTerm(String),
21+
Searchable(Box<dyn Searchable>),
22+
}
23+
24+
impl Debug for Artifact {
25+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
26+
match *self {
27+
Self::ChangeState(state) => write!(f, "ChangeState({state:?})"),
28+
Self::EnqueueResize => write!(f, "EnqueueResize"),
29+
Self::Error(ref err, state) => write!(f, "Error({err:?}, {state:?})"),
30+
Self::Event(event) => write!(f, "Event({event:?})"),
31+
Self::ExitStatus(status) => write!(f, "ExitStatus({status:?})"),
32+
Self::ExternalCommand((ref command, ref args)) => write!(f, "ExternalCommand({command:?}, {args:?})"),
33+
Self::SearchCancel => write!(f, "SearchCancel"),
34+
Self::SearchTerm(ref term) => write!(f, "SearchTerm({term:?})"),
35+
Self::Searchable(_) => write!(f, "Searchable(dyn Searchable)"),
36+
}
37+
}
38+
}
39+
40+
#[cfg(test)]
41+
mod tests {
42+
use anyhow::anyhow;
43+
use rstest::rstest;
44+
45+
use super::*;
46+
use crate::{
47+
search::{Interrupter, SearchResult},
48+
testutil::MockedSearchable,
49+
};
50+
51+
#[rstest]
52+
#[case::change_state(Artifact::ChangeState(State::List), "ChangeState(List)")]
53+
#[case::enqueue_resize(Artifact::EnqueueResize, "EnqueueResize")]
54+
#[case::error(Artifact::Error(anyhow!("Error"), Some(State::List)), "Error(Error, Some(List))")]
55+
#[case::event(Artifact::Event(Event::None), "Event(None)")]
56+
#[case::exit_status(Artifact::ExitStatus(ExitStatus::Abort), "ExitStatus(Abort)")]
57+
#[case::external_command(Artifact::ExternalCommand((String::from("foo"), vec![])), "ExternalCommand(\"foo\", [])")]
58+
#[case::search_cancel(Artifact::SearchCancel, "SearchCancel")]
59+
#[case::search_term(Artifact::SearchTerm(String::from("foo")), "SearchTerm(\"foo\")")]
60+
#[case::searchable(
61+
Artifact::Searchable(Box::new(MockedSearchable::new())),
62+
"Searchable(dyn Searchable)"
63+
)]
64+
fn debug(#[case] artifact: Artifact, #[case] expected: &str) {
65+
assert_eq!(format!("{artifact:?}"), expected);
66+
}
1767
}

src/core/src/process/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::{
2626
events,
2727
events::{Event, MetaEvent},
2828
module::{self, ExitStatus, ModuleHandler, State},
29+
search::{self, Action, Searchable},
2930
};
3031

3132
pub(crate) struct Process<ModuleProvider: module::ModuleProvider> {
@@ -39,6 +40,7 @@ pub(crate) struct Process<ModuleProvider: module::ModuleProvider> {
3940
thread_statuses: ThreadStatuses,
4041
todo_file: Arc<Mutex<TodoFile>>,
4142
view_state: view::State,
43+
search_state: search::State,
4244
}
4345

4446
impl<ModuleProvider: module::ModuleProvider> Clone for Process<ModuleProvider> {
@@ -54,6 +56,7 @@ impl<ModuleProvider: module::ModuleProvider> Clone for Process<ModuleProvider> {
5456
thread_statuses: self.thread_statuses.clone(),
5557
todo_file: Arc::clone(&self.todo_file),
5658
view_state: self.view_state.clone(),
59+
search_state: self.search_state.clone(),
5760
}
5861
}
5962
}
@@ -65,6 +68,7 @@ impl<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> {
6568
module_handler: ModuleHandler<ModuleProvider>,
6669
input_state: events::State,
6770
view_state: view::State,
71+
search_state: search::State,
6872
thread_statuses: ThreadStatuses,
6973
) -> Self {
7074
Self {
@@ -77,6 +81,7 @@ impl<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> {
7781
initial_display_size.width(),
7882
initial_display_size.height(),
7983
))),
84+
search_state,
8085
state: Arc::new(Mutex::new(State::WindowSizeError)),
8186
thread_statuses,
8287
todo_file,
@@ -266,6 +271,21 @@ impl<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> {
266271
result
267272
}
268273

274+
fn handle_search_cancel(&self) -> Results {
275+
self.search_state.send_update(Action::Cancel);
276+
Results::new()
277+
}
278+
279+
fn handle_search_term(&self, term: String) -> Results {
280+
self.search_state.send_update(Action::Start(term));
281+
Results::new()
282+
}
283+
284+
fn handle_searchable(&self, searchable: Box<dyn Searchable>) -> Results {
285+
self.search_state.send_update(Action::SetSearchable(searchable));
286+
Results::new()
287+
}
288+
269289
fn handle_results(&self, mut results: Results) {
270290
while let Some(artifact) = results.artifact() {
271291
results.append(match artifact {
@@ -275,6 +295,9 @@ impl<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> {
275295
Artifact::Event(event) => self.handle_event_artifact(event),
276296
Artifact::ExitStatus(exit_status) => self.handle_exit_status(exit_status),
277297
Artifact::ExternalCommand(command) => self.handle_external_command(&command),
298+
Artifact::SearchCancel => self.handle_search_cancel(),
299+
Artifact::SearchTerm(search_term) => self.handle_search_term(search_term),
300+
Artifact::Searchable(searchable) => self.handle_searchable(searchable),
278301
});
279302
}
280303
}

src/core/src/process/results.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::{
66
events::Event,
77
module::{ExitStatus, State},
88
process::artifact::Artifact,
9+
search::Searchable,
910
};
1011

1112
#[derive(Debug)]
@@ -40,6 +41,14 @@ impl Results {
4041
self.artifacts.push_back(Artifact::ChangeState(new_state));
4142
}
4243

44+
pub(crate) fn search_cancel(&mut self) {
45+
self.artifacts.push_back(Artifact::SearchCancel);
46+
}
47+
48+
pub(crate) fn search_term(&mut self, term: &str) {
49+
self.artifacts.push_back(Artifact::SearchTerm(String::from(term)));
50+
}
51+
4352
pub(crate) fn external_command(&mut self, command: String, arguments: Vec<String>) {
4453
self.artifacts
4554
.push_back(Artifact::ExternalCommand((command, arguments)));
@@ -90,11 +99,20 @@ impl From<State> for Results {
9099
}
91100
}
92101

102+
impl From<Box<dyn Searchable>> for Results {
103+
fn from(searchable: Box<dyn Searchable>) -> Self {
104+
Self {
105+
artifacts: VecDeque::from(vec![Artifact::Searchable(searchable)]),
106+
}
107+
}
108+
}
109+
93110
#[cfg(test)]
94111
mod tests {
95112
use anyhow::anyhow;
96113

97114
use super::*;
115+
use crate::testutil::MockedSearchable;
98116

99117
#[test]
100118
fn empty() {
@@ -146,6 +164,28 @@ mod tests {
146164
assert!(matches!(results.artifact(), Some(Artifact::ChangeState(State::List))));
147165
}
148166

167+
#[test]
168+
fn search_cancel() {
169+
let mut results = Results::new();
170+
results.search_cancel();
171+
assert!(matches!(results.artifact(), Some(Artifact::SearchCancel)));
172+
}
173+
174+
#[test]
175+
fn search_term() {
176+
let mut results = Results::new();
177+
let search_term = String::from("foo");
178+
results.search_term(search_term.as_str());
179+
assert!(matches!(results.artifact(), Some(Artifact::SearchTerm(search_term))));
180+
}
181+
182+
#[test]
183+
fn searchable() {
184+
let mocked_searchable: Box<dyn Searchable> = Box::new(MockedSearchable::new());
185+
let mut results = Results::from(mocked_searchable);
186+
assert!(matches!(results.artifact(), Some(Artifact::Searchable(_))));
187+
}
188+
149189
#[test]
150190
fn external_command() {
151191
let mut results = Results::new();

src/core/src/process/tests.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ use crate::{
1111
assert_results,
1212
events::KeyBindings,
1313
module::{Module, DEFAULT_INPUT_OPTIONS, DEFAULT_VIEW_DATA},
14-
testutil::{create_default_test_module_handler, create_test_module_handler, process_test, ProcessTestContext},
14+
search::{Interrupter, SearchResult},
15+
testutil::{
16+
create_default_test_module_handler,
17+
create_test_module_handler,
18+
process_test,
19+
MockedSearchable,
20+
ProcessTestContext,
21+
},
1522
};
1623

1724
#[derive(Clone)]
@@ -583,3 +590,64 @@ fn handle_results_external_command_success() {
583590
},
584591
);
585592
}
593+
594+
#[test]
595+
fn handle_search_cancel() {
596+
let module = TestModule::new();
597+
process_test(
598+
create_test_module_handler(module),
599+
|ProcessTestContext {
600+
process,
601+
search_context,
602+
..
603+
}| {
604+
let mut results = Results::new();
605+
results.search_cancel();
606+
process.handle_results(results);
607+
assert!(matches!(search_context.state.receive_update(), Action::Cancel));
608+
},
609+
);
610+
}
611+
612+
#[test]
613+
fn handle_search_term() {
614+
let module = TestModule::new();
615+
process_test(
616+
create_test_module_handler(module),
617+
|ProcessTestContext {
618+
process,
619+
search_context,
620+
..
621+
}| {
622+
let mut results = Results::new();
623+
let search_term = String::from("foo");
624+
results.search_term(search_term.as_str());
625+
process.handle_results(results);
626+
assert!(matches!(
627+
search_context.state.receive_update(),
628+
Action::Start(search_term)
629+
));
630+
},
631+
);
632+
}
633+
634+
#[test]
635+
fn handle_searchable() {
636+
let module = TestModule::new();
637+
process_test(
638+
create_test_module_handler(module),
639+
|ProcessTestContext {
640+
process,
641+
search_context,
642+
..
643+
}| {
644+
let searchable: Box<dyn Searchable> = Box::new(MockedSearchable {});
645+
let results = Results::from(searchable);
646+
process.handle_results(results);
647+
assert!(matches!(
648+
search_context.state.receive_update(),
649+
Action::SetSearchable(_)
650+
));
651+
},
652+
);
653+
}

0 commit comments

Comments
 (0)