summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/watch.rs23
-rw-r--r--src/watch/notify_event.rs4
-rw-r--r--src/watch/state.rs24
-rw-r--r--src/watch/terminal_event.rs63
4 files changed, 72 insertions, 42 deletions
diff --git a/src/watch.rs b/src/watch.rs
index e910fb7..c937bfb 100644
--- a/src/watch.rs
+++ b/src/watch.rs
@@ -1,4 +1,4 @@
-use anyhow::{Context, Error, Result};
+use anyhow::{Error, Result};
use notify_debouncer_mini::{
new_debouncer,
notify::{self, RecursiveMode},
@@ -7,7 +7,6 @@ use std::{
io::{self, Write},
path::Path,
sync::mpsc::channel,
- thread,
time::Duration,
};
@@ -16,11 +15,7 @@ use crate::{
list,
};
-use self::{
- notify_event::NotifyEventHandler,
- state::WatchState,
- terminal_event::{terminal_event_handler, InputEvent},
-};
+use self::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent};
mod notify_event;
mod state;
@@ -47,7 +42,7 @@ fn run_watch(
app_state: &mut AppState,
notify_exercise_names: Option<&'static [&'static [u8]]>,
) -> Result<WatchExit> {
- let (tx, rx) = channel();
+ let (watch_event_sender, watch_event_receiver) = channel();
let mut manual_run = false;
// Prevent dropping the guard until the end of the function.
@@ -56,7 +51,7 @@ fn run_watch(
let mut debouncer = new_debouncer(
Duration::from_millis(200),
NotifyEventHandler {
- tx: tx.clone(),
+ sender: watch_event_sender.clone(),
exercise_names,
},
)
@@ -72,16 +67,12 @@ fn run_watch(
None
};
- let mut watch_state = WatchState::build(app_state, manual_run)?;
-
+ let mut watch_state = WatchState::build(app_state, watch_event_sender, manual_run)?;
let mut stdout = io::stdout().lock();
- watch_state.run_current_exercise(&mut stdout)?;
- thread::Builder::new()
- .spawn(move || terminal_event_handler(tx, manual_run))
- .context("Failed to spawn a thread to handle terminal events")?;
+ watch_state.run_current_exercise(&mut stdout)?;
- while let Ok(event) = rx.recv() {
+ while let Ok(event) = watch_event_receiver.recv() {
match event {
WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise(&mut stdout)? {
ExercisesProgress::AllDone => break,
diff --git a/src/watch/notify_event.rs b/src/watch/notify_event.rs
index 7471640..9b23525 100644
--- a/src/watch/notify_event.rs
+++ b/src/watch/notify_event.rs
@@ -4,7 +4,7 @@ use std::sync::mpsc::Sender;
use super::WatchEvent;
pub struct NotifyEventHandler {
- pub tx: Sender<WatchEvent>,
+ pub sender: Sender<WatchEvent>,
/// Used to report which exercise was modified.
pub exercise_names: &'static [&'static [u8]],
}
@@ -47,6 +47,6 @@ impl notify_debouncer_mini::DebounceEventHandler for NotifyEventHandler {
// An error occurs when the receiver is dropped.
// After dropping the receiver, the debouncer guard should also be dropped.
- let _ = self.tx.send(output_event);
+ let _ = self.sender.send(output_event);
}
}
diff --git a/src/watch/state.rs b/src/watch/state.rs
index e66cbee..6e76001 100644
--- a/src/watch/state.rs
+++ b/src/watch/state.rs
@@ -5,7 +5,11 @@ use crossterm::{
},
terminal, QueueableCommand,
};
-use std::io::{self, StdoutLock, Write};
+use std::{
+ io::{self, StdoutLock, Write},
+ sync::mpsc::Sender,
+ thread,
+};
use crate::{
app_state::{AppState, ExercisesProgress},
@@ -14,6 +18,11 @@ use crate::{
term::progress_bar,
};
+use super::{
+ terminal_event::{terminal_event_handler, InputPauseGuard},
+ WatchEvent,
+};
+
#[derive(PartialEq, Eq)]
enum DoneStatus {
DoneWithSolution(String),
@@ -31,11 +40,19 @@ pub struct WatchState<'a> {
}
impl<'a> WatchState<'a> {
- pub fn build(app_state: &'a mut AppState, manual_run: bool) -> Result<Self> {
+ pub fn build(
+ app_state: &'a mut AppState,
+ watch_event_sender: Sender<WatchEvent>,
+ manual_run: bool,
+ ) -> Result<Self> {
let term_width = terminal::size()
.context("Failed to get the terminal size")?
.0;
+ thread::Builder::new()
+ .spawn(move || terminal_event_handler(watch_event_sender, manual_run))
+ .context("Failed to spawn a thread to handle terminal events")?;
+
Ok(Self {
app_state,
output: Vec::with_capacity(OUTPUT_CAPACITY),
@@ -47,6 +64,9 @@ impl<'a> WatchState<'a> {
}
pub fn run_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> {
+ // Ignore any input until running the exercise is done.
+ let _input_pause_guard = InputPauseGuard::scoped_pause();
+
self.show_hint = false;
writeln!(
diff --git a/src/watch/terminal_event.rs b/src/watch/terminal_event.rs
index ca3a846..2a1dfdc 100644
--- a/src/watch/terminal_event.rs
+++ b/src/watch/terminal_event.rs
@@ -1,8 +1,32 @@
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
-use std::sync::mpsc::Sender;
+use std::sync::{
+ atomic::{AtomicBool, Ordering::Relaxed},
+ mpsc::Sender,
+};
use super::WatchEvent;
+static INPUT_PAUSED: AtomicBool = AtomicBool::new(false);
+
+// Private unit type to force using the constructor function.
+#[must_use = "When the guard is dropped, the input is unpaused"]
+pub struct InputPauseGuard(());
+
+impl InputPauseGuard {
+ #[inline]
+ pub fn scoped_pause() -> Self {
+ INPUT_PAUSED.store(true, Relaxed);
+ Self(())
+ }
+}
+
+impl Drop for InputPauseGuard {
+ #[inline]
+ fn drop(&mut self) {
+ INPUT_PAUSED.store(false, Relaxed);
+ }
+}
+
pub enum InputEvent {
Run,
Next,
@@ -11,46 +35,41 @@ pub enum InputEvent {
Quit,
}
-pub fn terminal_event_handler(tx: Sender<WatchEvent>, manual_run: bool) {
- let last_input_event = loop {
- let terminal_event = match event::read() {
- Ok(v) => v,
- Err(e) => {
- // If `send` returns an error, then the receiver is dropped and
- // a shutdown has been already initialized.
- let _ = tx.send(WatchEvent::TerminalEventErr(e));
- return;
- }
- };
-
- match terminal_event {
- Event::Key(key) => {
+pub fn terminal_event_handler(sender: Sender<WatchEvent>, manual_run: bool) {
+ let last_watch_event = loop {
+ match event::read() {
+ Ok(Event::Key(key)) => {
match key.kind {
KeyEventKind::Release | KeyEventKind::Repeat => continue,
KeyEventKind::Press => (),
}
+ if INPUT_PAUSED.load(Relaxed) {
+ continue;
+ }
+
let input_event = match key.code {
KeyCode::Char('n') => InputEvent::Next,
KeyCode::Char('h') => InputEvent::Hint,
- KeyCode::Char('l') => break InputEvent::List,
- KeyCode::Char('q') => break InputEvent::Quit,
+ KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List),
+ KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit),
KeyCode::Char('r') if manual_run => InputEvent::Run,
_ => continue,
};
- if tx.send(WatchEvent::Input(input_event)).is_err() {
+ if sender.send(WatchEvent::Input(input_event)).is_err() {
return;
}
}
- Event::Resize(width, _) => {
- if tx.send(WatchEvent::TerminalResize { width }).is_err() {
+ Ok(Event::Resize(width, _)) => {
+ if sender.send(WatchEvent::TerminalResize { width }).is_err() {
return;
}
}
- Event::FocusGained | Event::FocusLost | Event::Mouse(_) => continue,
+ Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue,
+ Err(e) => break WatchEvent::TerminalEventErr(e),
}
};
- let _ = tx.send(WatchEvent::Input(last_input_event));
+ let _ = sender.send(last_watch_event);
}