summaryrefslogtreecommitdiff
path: root/src/watch.rs
diff options
context:
space:
mode:
authormo8it <mo8it@proton.me>2024-04-09 21:07:53 +0200
committermo8it <mo8it@proton.me>2024-04-09 21:07:53 +0200
commitf0ce2c1afa21fdaa34aed8f21c1ef4d3c47cebdd (patch)
treebf428b63104f597cb3ab5c4fd60b49c5b47ee809 /src/watch.rs
parent850c1d0234b2c1ae09a8f1c8f669e23a324fd644 (diff)
Improve event handling in the watch mode
Diffstat (limited to 'src/watch.rs')
-rw-r--r--src/watch.rs150
1 files changed, 106 insertions, 44 deletions
diff --git a/src/watch.rs b/src/watch.rs
index 967f98c..abf4002 100644
--- a/src/watch.rs
+++ b/src/watch.rs
@@ -1,9 +1,11 @@
-use anyhow::Result;
-use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode};
+use anyhow::{bail, Context, Result};
+use notify_debouncer_mini::{
+ new_debouncer, notify::RecursiveMode, DebounceEventResult, DebouncedEventKind,
+};
use std::{
io::{self, BufRead, Write},
path::Path,
- sync::mpsc::{channel, sync_channel},
+ sync::mpsc::{channel, Sender},
thread,
time::Duration,
};
@@ -14,70 +16,130 @@ use crate::{exercise::Exercise, state_file::StateFile};
use self::state::WatchState;
-enum Event {
+enum InputEvent {
Hint,
Clear,
Quit,
+ Unrecognized,
}
-pub fn watch(state_file: &StateFile, exercises: &[Exercise]) -> Result<()> {
- let (tx, rx) = channel();
- let mut debouncer = new_debouncer(Duration::from_secs(1), tx)?;
- debouncer
- .watcher()
- .watch(Path::new("exercises"), RecursiveMode::Recursive)?;
+enum WatchEvent {
+ Input(InputEvent),
+ FileChange { exercise_ind: usize },
+ TerminalResize,
+}
- let mut watch_state = WatchState::new(state_file, exercises, rx);
+struct DebouceEventHandler {
+ tx: Sender<WatchEvent>,
+ exercises: &'static [Exercise],
+}
- watch_state.run_exercise()?;
- watch_state.render()?;
+impl notify_debouncer_mini::DebounceEventHandler for DebouceEventHandler {
+ fn handle_event(&mut self, event: DebounceEventResult) {
+ let Ok(event) = event else {
+ // TODO
+ return;
+ };
+
+ let Some(exercise_ind) = event
+ .iter()
+ .filter_map(|event| {
+ if event.kind != DebouncedEventKind::Any
+ || !event.path.extension().is_some_and(|ext| ext == "rs")
+ {
+ return None;
+ }
- let (tx, rx) = sync_channel(0);
- thread::spawn(move || {
- let mut stdin = io::stdin().lock();
- let mut stdin_buf = String::with_capacity(8);
+ self.exercises
+ .iter()
+ .position(|exercise| event.path.ends_with(&exercise.path))
+ })
+ .min()
+ else {
+ return;
+ };
- loop {
- stdin.read_line(&mut stdin_buf).unwrap();
+ self.tx.send(WatchEvent::FileChange { exercise_ind });
+ }
+}
- let event = match stdin_buf.trim() {
- "h" | "hint" => Some(Event::Hint),
- "c" | "clear" => Some(Event::Clear),
- "q" | "quit" => Some(Event::Quit),
- _ => None,
- };
+fn input_handler(tx: Sender<WatchEvent>) -> Result<()> {
+ let mut stdin = io::stdin().lock();
+ let mut stdin_buf = String::with_capacity(8);
+
+ loop {
+ stdin
+ .read_line(&mut stdin_buf)
+ .context("Failed to read the user's input from stdin")?;
- stdin_buf.clear();
+ let event = match stdin_buf.trim() {
+ "h" | "hint" => InputEvent::Hint,
+ "c" | "clear" => InputEvent::Clear,
+ "q" | "quit" => InputEvent::Quit,
+ _ => InputEvent::Unrecognized,
+ };
- if tx.send(event).is_err() {
- break;
- };
+ stdin_buf.clear();
+
+ if tx.send(WatchEvent::Input(event)).is_err() {
+ return Ok(());
}
- });
+ }
+}
- loop {
- watch_state.try_recv_event()?;
+pub fn watch(state_file: &StateFile, exercises: &'static [Exercise]) -> Result<()> {
+ let (tx, rx) = channel();
+ let mut debouncer = new_debouncer(
+ Duration::from_secs(1),
+ DebouceEventHandler {
+ tx: tx.clone(),
+ exercises,
+ },
+ )?;
+ debouncer
+ .watcher()
+ .watch(Path::new("exercises"), RecursiveMode::Recursive)?;
- if let Ok(event) = rx.try_recv() {
- match event {
- Some(Event::Hint) => {
- watch_state.show_hint()?;
- }
- Some(Event::Clear) => {
- watch_state.render()?;
- }
- Some(Event::Quit) => break,
- None => {
- watch_state.handle_invalid_cmd()?;
- }
+ let mut watch_state = WatchState::new(state_file, exercises);
+
+ // TODO: bool
+ watch_state.run_exercise()?;
+ watch_state.render()?;
+
+ let input_thread = thread::spawn(move || input_handler(tx));
+
+ while let Ok(event) = rx.recv() {
+ match event {
+ WatchEvent::Input(InputEvent::Hint) => {
+ watch_state.show_hint()?;
+ }
+ WatchEvent::Input(InputEvent::Clear) | WatchEvent::TerminalResize => {
+ watch_state.render()?;
+ }
+ WatchEvent::Input(InputEvent::Quit) => break,
+ WatchEvent::Input(InputEvent::Unrecognized) => {
+ watch_state.handle_invalid_cmd()?;
+ }
+ WatchEvent::FileChange { exercise_ind } => {
+ // TODO: bool
+ watch_state.run_exercise_with_ind(exercise_ind)?;
+ watch_state.render()?;
}
}
}
+ // Drop the receiver for the sender threads to exit.
+ drop(rx);
+
watch_state.into_writer().write_all(b"
We hope you're enjoying learning Rust!
If you want to continue working on the exercises at a later point, you can simply run `rustlings` again.
")?;
+ match input_thread.join() {
+ Ok(res) => res?,
+ Err(_) => bail!("The input thread panicked"),
+ }
+
Ok(())
}