diff --git a/Cargo.lock b/Cargo.lock index 6f78f3e..25949c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -490,6 +490,7 @@ dependencies = [ "crossterm", "notify-debouncer-mini", "os_pipe", + "rustix", "rustlings-macros", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 15e2d1c..da4fc4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,9 @@ serde_json = "1.0.128" serde.workspace = true toml_edit.workspace = true +[target.'cfg(not(windows))'.dependencies] +rustix = { version = "0.38.35", default-features = false, features = ["std", "stdio", "termios"] } + [dev-dependencies] tempfile = "3.12.0" diff --git a/src/main.rs b/src/main.rs index e53cd5a..fe4b3dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::{ }; use term::{clear_terminal, press_enter_prompt}; -use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::WatchExit}; +use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile}; mod app_state; mod cargo_toml; @@ -130,15 +130,7 @@ fn main() -> Result<()> { ) }; - loop { - match watch::watch(&mut app_state, notify_exercise_names)? { - WatchExit::Shutdown => break, - // It is much easier to exit the watch mode, launch the list mode and then restart - // the watch mode instead of trying to pause the watch threads and correct the - // watch state. - WatchExit::List => list::list(&mut app_state)?, - } - } + watch::watch(&mut app_state, notify_exercise_names)?; } Some(Subcommands::Run { name }) => { if let Some(name) = name { diff --git a/src/watch.rs b/src/watch.rs index e14d3c5..be8409f 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -11,7 +11,10 @@ use std::{ time::Duration, }; -use crate::app_state::{AppState, ExercisesProgress}; +use crate::{ + app_state::{AppState, ExercisesProgress}, + list, +}; use self::{ notify_event::NotifyEventHandler, @@ -33,15 +36,14 @@ enum WatchEvent { /// Returned by the watch mode to indicate what to do afterwards. #[must_use] -pub enum WatchExit { +enum WatchExit { /// Exit the program. Shutdown, /// Enter the list mode and restart the watch mode afterwards. List, } -/// `notify_exercise_names` as None activates the manual run mode. -pub fn watch( +fn run_watch( app_state: &mut AppState, notify_exercise_names: Option<&'static [&'static [u8]]>, ) -> Result { @@ -110,6 +112,48 @@ pub fn watch( Ok(WatchExit::Shutdown) } +fn watch_list_loop( + app_state: &mut AppState, + notify_exercise_names: Option<&'static [&'static [u8]]>, +) -> Result<()> { + loop { + match run_watch(app_state, notify_exercise_names)? { + WatchExit::Shutdown => break Ok(()), + // It is much easier to exit the watch mode, launch the list mode and then restart + // the watch mode instead of trying to pause the watch threads and correct the + // watch state. + WatchExit::List => list::list(app_state)?, + } + } +} + +/// `notify_exercise_names` as None activates the manual run mode. +pub fn watch( + app_state: &mut AppState, + notify_exercise_names: Option<&'static [&'static [u8]]>, +) -> Result<()> { + #[cfg(not(windows))] + { + let stdin_fd = rustix::stdio::stdin(); + let mut termios = rustix::termios::tcgetattr(stdin_fd)?; + let original_local_modes = termios.local_modes; + // Disable stdin line buffering and hide input. + termios.local_modes -= + rustix::termios::LocalModes::ICANON | rustix::termios::LocalModes::ECHO; + rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?; + + let res = watch_list_loop(app_state, notify_exercise_names); + + termios.local_modes = original_local_modes; + rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?; + + res + } + + #[cfg(windows)] + watch_list_loop(app_state, notify_exercise_names) +} + 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.