diff options
| author | mo8it <mo8it@proton.me> | 2024-04-09 21:07:53 +0200 |
|---|---|---|
| committer | mo8it <mo8it@proton.me> | 2024-04-09 21:07:53 +0200 |
| commit | f0ce2c1afa21fdaa34aed8f21c1ef4d3c47cebdd (patch) | |
| tree | bf428b63104f597cb3ab5c4fd60b49c5b47ee809 /src/watch.rs | |
| parent | 850c1d0234b2c1ae09a8f1c8f669e23a324fd644 (diff) | |
Improve event handling in the watch mode
Diffstat (limited to 'src/watch.rs')
| -rw-r--r-- | src/watch.rs | 150 |
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(()) } |
