summaryrefslogtreecommitdiff
path: root/src/watch.rs
diff options
context:
space:
mode:
authorMo <76752051+mo8it@users.noreply.github.com>2024-04-14 17:13:32 +0200
committerGitHub <noreply@github.com>2024-04-14 17:13:32 +0200
commitdc02c38a945fcafacf6d2d35f5d3e317e7185cb0 (patch)
treebd3ad843a575650881b220c4b008fc7509917d24 /src/watch.rs
parent8c8f30d8ce3b732de649938d8945496bd769ac22 (diff)
parent7526c6b1f92626df6ab8b4853535b73711bfada4 (diff)
Merge pull request #1942 from rust-lang/tui
TUI
Diffstat (limited to 'src/watch.rs')
-rw-r--r--src/watch.rs128
1 files changed, 128 insertions, 0 deletions
diff --git a/src/watch.rs b/src/watch.rs
new file mode 100644
index 0000000..d20e552
--- /dev/null
+++ b/src/watch.rs
@@ -0,0 +1,128 @@
+use anyhow::{Error, Result};
+use notify_debouncer_mini::{
+ new_debouncer,
+ notify::{self, RecursiveMode},
+};
+use std::{
+ io::{self, Write},
+ path::Path,
+ sync::mpsc::channel,
+ thread,
+ time::Duration,
+};
+
+mod notify_event;
+mod state;
+mod terminal_event;
+
+use crate::app_state::{AppState, ExercisesProgress};
+
+use self::{
+ notify_event::DebounceEventHandler,
+ state::WatchState,
+ terminal_event::{terminal_event_handler, InputEvent},
+};
+
+enum WatchEvent {
+ Input(InputEvent),
+ FileChange { exercise_ind: usize },
+ TerminalResize,
+ NotifyErr(notify::Error),
+ TerminalEventErr(io::Error),
+}
+
+/// Returned by the watch mode to indicate what to do afterwards.
+#[must_use]
+pub enum WatchExit {
+ /// Exit the program.
+ Shutdown,
+ /// Enter the list mode and restart the watch mode afterwards.
+ List,
+}
+
+pub fn watch(
+ app_state: &mut AppState,
+ notify_exercise_paths: Option<&'static [&'static str]>,
+) -> Result<WatchExit> {
+ let (tx, rx) = channel();
+
+ let mut manual_run = false;
+ // Prevent dropping the guard until the end of the function.
+ // Otherwise, the file watcher exits.
+ let _debouncer_guard = if let Some(exercise_paths) = notify_exercise_paths {
+ let mut debouncer = new_debouncer(
+ Duration::from_secs(1),
+ DebounceEventHandler {
+ tx: tx.clone(),
+ exercise_paths,
+ },
+ )
+ .inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?;
+ debouncer
+ .watcher()
+ .watch(Path::new("exercises"), RecursiveMode::Recursive)
+ .inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?;
+
+ Some(debouncer)
+ } else {
+ manual_run = true;
+ None
+ };
+
+ let mut watch_state = WatchState::new(app_state, manual_run);
+
+ watch_state.run_current_exercise()?;
+
+ thread::spawn(move || terminal_event_handler(tx, manual_run));
+
+ while let Ok(event) = rx.recv() {
+ match event {
+ WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise()? {
+ ExercisesProgress::AllDone => break,
+ ExercisesProgress::Pending => watch_state.run_current_exercise()?,
+ },
+ WatchEvent::Input(InputEvent::Hint) => {
+ watch_state.show_hint()?;
+ }
+ WatchEvent::Input(InputEvent::List) => {
+ return Ok(WatchExit::List);
+ }
+ WatchEvent::Input(InputEvent::Quit) => {
+ watch_state.into_writer().write_all(QUIT_MSG)?;
+ break;
+ }
+ WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise()?,
+ WatchEvent::Input(InputEvent::Unrecognized(cmd)) => {
+ watch_state.handle_invalid_cmd(&cmd)?;
+ }
+ WatchEvent::FileChange { exercise_ind } => {
+ watch_state.run_exercise_with_ind(exercise_ind)?;
+ }
+ WatchEvent::TerminalResize => {
+ watch_state.render()?;
+ }
+ WatchEvent::NotifyErr(e) => {
+ watch_state.into_writer().write_all(NOTIFY_ERR.as_bytes())?;
+ return Err(Error::from(e));
+ }
+ WatchEvent::TerminalEventErr(e) => {
+ return Err(Error::from(e).context("Terminal event listener failed"));
+ }
+ }
+ }
+
+ Ok(WatchExit::Shutdown)
+}
+
+const QUIT_MSG: &[u8] = 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.
+";
+
+const NOTIFY_ERR: &str = "
+The automatic detection of exercise file changes failed :(
+Please try running `rustlings` again.
+
+If you keep getting this error, run `rustlings --manual-run` to deactivate the file watcher.
+You need to manually trigger running the current exercise using `r` or `run` then.
+";