diff --git a/Cargo.lock b/Cargo.lock index 85a42bec..edbd8005 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -570,6 +570,7 @@ dependencies = [ "regex", "serde", "serde_json", + "shlex", "toml_edit", "which", ] @@ -629,6 +630,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "strsim" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index 3e65e00f..8f52ad32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ notify-debouncer-mini = "0.4.1" regex = "1.10.3" serde_json = "1.0.114" serde = { version = "1.0.197", features = ["derive"] } +shlex = "1.3.0" toml_edit = { version = "0.22.9", default-features = false, features = ["parse", "serde"] } which = "6.0.1" diff --git a/src/main.rs b/src/main.rs index 9f45f335..80e5277e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use clap::{Parser, Subcommand}; use console::Emoji; use notify_debouncer_mini::notify::{self, RecursiveMode}; use notify_debouncer_mini::{new_debouncer, DebouncedEventKind}; +use shlex::Shlex; use std::ffi::OsStr; use std::fs; use std::io::{self, prelude::*}; @@ -244,47 +245,49 @@ fn main() { } fn spawn_watch_shell( - failed_exercise_hint: &Arc>>, + failed_exercise_hint: Arc>>, should_quit: Arc, ) { - let failed_exercise_hint = Arc::clone(failed_exercise_hint); println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here."); - thread::spawn(move || loop { - let mut input = String::new(); - match io::stdin().read_line(&mut input) { - Ok(_) => { - let input = input.trim(); - if input == "hint" { - if let Some(hint) = &*failed_exercise_hint.lock().unwrap() { - println!("{hint}"); - } - } else if input == "clear" { - println!("\x1B[2J\x1B[1;1H"); - } else if input.eq("quit") { - should_quit.store(true, Ordering::SeqCst); - println!("Bye!"); - } else if input.eq("help") { - println!("Commands available to you in watch mode:"); - println!(" hint - prints the current exercise's hint"); - println!(" clear - clears the screen"); - println!(" quit - quits watch mode"); - println!(" ! - executes a command, like `!rustc --explain E0381`"); - println!(" help - displays this help message"); - println!(); - println!("Watch mode automatically re-evaluates the current exercise"); - println!("when you edit a file's contents.") - } else if let Some(cmd) = input.strip_prefix('!') { - let parts: Vec<&str> = cmd.split_whitespace().collect(); - if parts.is_empty() { - println!("no command provided"); - } else if let Err(e) = Command::new(parts[0]).args(&parts[1..]).status() { - println!("failed to execute command `{cmd}`: {e}"); - } - } else { - println!("unknown command: {input}"); + + thread::spawn(move || { + let mut input = String::with_capacity(32); + let mut stdin = io::stdin().lock(); + + loop { + // Recycle input buffer. + input.clear(); + + if let Err(e) = stdin.read_line(&mut input) { + println!("error reading command: {e}"); + } + + let input = input.trim(); + if input == "hint" { + if let Some(hint) = &*failed_exercise_hint.lock().unwrap() { + println!("{hint}"); + } + } else if input == "clear" { + println!("\x1B[2J\x1B[1;1H"); + } else if input == "quit" { + should_quit.store(true, Ordering::SeqCst); + println!("Bye!"); + } else if input == "help" { + println!("{WATCH_MODE_HELP_MESSAGE}"); + } else if let Some(cmd) = input.strip_prefix('!') { + let mut parts = Shlex::new(cmd); + + let Some(program) = parts.next() else { + println!("no command provided"); + continue; + }; + + if let Err(e) = Command::new(program).args(parts).status() { + println!("failed to execute command `{cmd}`: {e}"); } + } else { + println!("unknown command: {input}\n{WATCH_MODE_HELP_MESSAGE}"); } - Err(error) => println!("error reading command: {error}"), } }); } @@ -345,7 +348,7 @@ fn watch( Ok(_) => return Ok(WatchStatus::Finished), Err(exercise) => Arc::new(Mutex::new(Some(exercise.hint.clone()))), }; - spawn_watch_shell(&failed_exercise_hint, Arc::clone(&should_quit)); + spawn_watch_shell(Arc::clone(&failed_exercise_hint), Arc::clone(&should_quit)); loop { match rx.recv_timeout(Duration::from_secs(1)) { Ok(event) => match event { @@ -462,3 +465,13 @@ const WELCOME: &str = r" welcome to... | | | |_| \__ \ |_| | | | | | (_| \__ \ |_| \__,_|___/\__|_|_|_| |_|\__, |___/ |___/"; + +const WATCH_MODE_HELP_MESSAGE: &str = "Commands available to you in watch mode: + hint - prints the current exercise's hint + clear - clears the screen + quit - quits watch mode + ! - executes a command, like `!rustc --explain E0381` + help - displays this help message + +Watch mode automatically re-evaluates the current exercise +when you edit a file's contents.";