mod artifact;
mod results;
#[cfg(test)]
mod tests;
pub(crate) mod thread;
use std::{
io::ErrorKind,
process::Command,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use anyhow::{anyhow, Error, Result};
use display::Size;
use input::StandardEvent;
use parking_lot::Mutex;
use runtime::ThreadStatuses;
use todo_file::TodoFile;
use view::RenderContext;
pub(crate) use self::{artifact::Artifact, results::Results, thread::Thread};
use crate::{
events,
events::{Event, MetaEvent},
module::{self, ExitStatus, ModuleHandler, State},
search::{self, Action, Searchable},
};
pub(crate) struct Process<ModuleProvider: module::ModuleProvider> {
ended: Arc<AtomicBool>,
exit_status: Arc<Mutex<ExitStatus>>,
input_state: events::State,
module_handler: Arc<Mutex<ModuleHandler<ModuleProvider>>>,
paused: Arc<AtomicBool>,
render_context: Arc<Mutex<RenderContext>>,
state: Arc<Mutex<State>>,
thread_statuses: ThreadStatuses,
todo_file: Arc<Mutex<TodoFile>>,
view_state: view::State,
search_state: search::State,
}
impl<ModuleProvider: module::ModuleProvider> Clone for Process<ModuleProvider> {
fn clone(&self) -> Self {
Self {
ended: Arc::clone(&self.ended),
exit_status: Arc::clone(&self.exit_status),
input_state: self.input_state.clone(),
module_handler: Arc::clone(&self.module_handler),
paused: Arc::clone(&self.paused),
render_context: Arc::clone(&self.render_context),
state: Arc::clone(&self.state),
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(),
}
}
}
impl<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> {
pub(crate) fn new(
initial_display_size: Size,
todo_file: Arc<Mutex<TodoFile>>,
module_handler: ModuleHandler<ModuleProvider>,
input_state: events::State,
view_state: view::State,
search_state: search::State,
thread_statuses: ThreadStatuses,
) -> Self {
Self {
ended: Arc::new(AtomicBool::from(false)),
exit_status: Arc::new(Mutex::new(ExitStatus::None)),
input_state,
module_handler: Arc::new(Mutex::new(module_handler)),
paused: Arc::new(AtomicBool::from(false)),
render_context: Arc::new(Mutex::new(RenderContext::new(
initial_display_size.width(),
initial_display_size.height(),
))),
search_state,
state: Arc::new(Mutex::new(State::WindowSizeError)),
thread_statuses,
todo_file,
view_state,
}
}
pub(crate) fn is_ended(&self) -> bool {
self.ended.load(Ordering::Acquire)
}
pub(crate) fn end(&self) {
self.ended.store(true, Ordering::Release);
}
pub(crate) fn state(&self) -> State {
*self.state.lock()
}
pub(crate) fn set_state(&self, state: State) {
*self.state.lock() = state;
}
pub(crate) fn exit_status(&self) -> ExitStatus {
*self.exit_status.lock()
}
pub(crate) fn set_exit_status(&self, exit_status: ExitStatus) {
*self.exit_status.lock() = exit_status;
}
pub(crate) fn should_exit(&self) -> bool {
self.exit_status() != ExitStatus::None || self.is_ended()
}
pub(crate) fn is_exit_status_kill(&self) -> bool {
self.exit_status() == ExitStatus::Kill
}
fn activate(&self, previous_state: State) -> Results {
let mut module_handler = self.module_handler.lock();
let mut results = module_handler.activate(self.state(), previous_state);
results.enqueue_resize();
results
}
pub(crate) fn render(&self) {
let render_context = *self.render_context.lock();
let mut module_handler = self.module_handler.lock();
let view_data = module_handler.build_view_data(self.state(), &render_context);
self.view_state.render(view_data);
}
pub(crate) fn write_todo_file(&self) -> Result<()> {
self.todo_file.lock().write_file().map_err(Error::from)
}
fn deactivate(&self, state: State) -> Results {
let mut module_handler = self.module_handler.lock();
module_handler.deactivate(state)
}
pub(crate) fn handle_event(&self) -> Option<Results> {
self.module_handler
.lock()
.handle_event(self.state(), &self.input_state.clone(), &self.view_state.clone())
}
fn handle_event_artifact(&self, event: Event) -> Results {
let mut results = Results::new();
match event {
Event::Standard(StandardEvent::Exit) => {
results.exit_status(ExitStatus::Abort);
},
Event::Standard(StandardEvent::Kill) => {
results.exit_status(ExitStatus::Kill);
},
Event::Resize(width, height) => {
self.view_state.resize(width, height);
let mut render_context = self.render_context.lock();
render_context.update(width, height);
if self.state() != State::WindowSizeError && render_context.is_window_too_small() {
results.state(State::WindowSizeError);
}
},
_ => {},
};
results
}
fn handle_state(&self, state: State) -> Results {
let mut results = Results::new();
let previous_state = self.state();
if previous_state != state {
self.set_state(state);
results.append(self.deactivate(previous_state));
results.append(self.activate(previous_state));
}
results
}
fn handle_error(&self, error: &Error, previous_state: Option<State>) -> Results {
let mut results = Results::new();
let return_state = previous_state.unwrap_or_else(|| self.state());
self.set_state(State::Error);
results.append(self.activate(return_state));
let mut module_handler = self.module_handler.lock();
results.append(module_handler.error(State::Error, error));
results
}
fn handle_exit_status(&self, exit_status: ExitStatus) -> Results {
self.set_exit_status(exit_status);
Results::new()
}
fn handle_enqueue_resize(&self) -> Results {
let render_context = self.render_context.lock();
self.input_state.enqueue_event(Event::Resize(
render_context.width() as u16,
render_context.height() as u16,
));
Results::new()
}
fn handle_external_command(&self, external_command: &(String, Vec<String>)) -> Results {
let mut results = Results::new();
match self.run_command(external_command) {
Ok(meta_event) => {
self.input_state.enqueue_event(Event::from(meta_event));
},
Err(err) => {
results.error_with_return(
err.context(format!(
"Unable to run {} {}",
external_command.0,
external_command.1.join(" ")
)),
State::List,
);
},
}
results
}
fn run_command(&self, external_command: &(String, Vec<String>)) -> Result<MetaEvent> {
self.view_state.stop();
self.input_state.pause();
self.thread_statuses
.wait_for_status(view::REFRESH_THREAD_NAME, &runtime::Status::Waiting)?;
self.thread_statuses
.wait_for_status(input::THREAD_NAME, &runtime::Status::Waiting)?;
let mut cmd = Command::new(external_command.0.clone());
_ = cmd.args(external_command.1.clone());
let result = cmd
.status()
.map(|status| {
if status.success() {
MetaEvent::ExternalCommandSuccess
}
else {
MetaEvent::ExternalCommandError
}
})
.map_err(|err| {
match err.kind() {
ErrorKind::NotFound => {
anyhow!("File does not exist: {}", external_command.0)
},
ErrorKind::PermissionDenied => {
anyhow!("File not executable: {}", external_command.0)
},
_ => Error::from(err),
}
});
self.input_state.resume();
self.view_state.start();
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 {
Artifact::ChangeState(state) => self.handle_state(state),
Artifact::EnqueueResize => self.handle_enqueue_resize(),
Artifact::Error(err, previous_state) => self.handle_error(&err, previous_state),
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),
});
}
}
}