1
0
mirror of https://github.com/rust-lang/rustlings.git synced 2024-11-08 09:09:17 +01:00

Add the manual-run option

This commit is contained in:
mo8it 2024-04-14 17:10:53 +02:00
parent bd10b154fe
commit 1cbabc3d28
4 changed files with 66 additions and 25 deletions

@ -36,6 +36,10 @@ use self::{
struct Args { struct Args {
#[command(subcommand)] #[command(subcommand)]
command: Option<Subcommands>, command: Option<Subcommands>,
/// Manually run the current exercise using `r` or `run` in the watch mode.
/// Only use this if Rustlings fails to detect exercise file changes.
#[arg(long)]
manual_run: bool,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -101,17 +105,23 @@ fn main() -> Result<()> {
match args.command { match args.command {
None => { None => {
// For the the notify event handler thread. let notify_exercise_paths: Option<&'static [&'static str]> = if args.manual_run {
// Leaking is not a problem because the slice lives until the end of the program. None
let exercise_paths = app_state } else {
.exercises() // For the the notify event handler thread.
.iter() // Leaking is not a problem because the slice lives until the end of the program.
.map(|exercise| exercise.path) Some(
.collect::<Vec<_>>() app_state
.leak(); .exercises()
.iter()
.map(|exercise| exercise.path)
.collect::<Vec<_>>()
.leak(),
)
};
loop { loop {
match watch(&mut app_state, exercise_paths)? { match watch(&mut app_state, notify_exercise_paths)? {
WatchExit::Shutdown => break, WatchExit::Shutdown => break,
// It is much easier to exit the watch mode, launch the list mode and then restart // 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 // the watch mode instead of trying to pause the watch threads and correct the

@ -42,25 +42,38 @@ pub enum WatchExit {
pub fn watch( pub fn watch(
app_state: &mut AppState, app_state: &mut AppState,
exercise_paths: &'static [&'static str], notify_exercise_paths: Option<&'static [&'static str]>,
) -> Result<WatchExit> { ) -> Result<WatchExit> {
let (tx, rx) = channel(); let (tx, rx) = channel();
let mut debouncer = new_debouncer(
Duration::from_secs(1),
DebounceEventHandler {
tx: tx.clone(),
exercise_paths,
},
)?;
debouncer
.watcher()
.watch(Path::new("exercises"), RecursiveMode::Recursive)?;
let mut watch_state = WatchState::new(app_state); 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()?; watch_state.run_current_exercise()?;
thread::spawn(move || terminal_event_handler(tx)); thread::spawn(move || terminal_event_handler(tx, manual_run));
while let Ok(event) = rx.recv() { while let Ok(event) = rx.recv() {
match event { match event {
@ -78,6 +91,7 @@ pub fn watch(
watch_state.into_writer().write_all(QUIT_MSG)?; watch_state.into_writer().write_all(QUIT_MSG)?;
break; break;
} }
WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise()?,
WatchEvent::Input(InputEvent::Unrecognized(cmd)) => { WatchEvent::Input(InputEvent::Unrecognized(cmd)) => {
watch_state.handle_invalid_cmd(&cmd)?; watch_state.handle_invalid_cmd(&cmd)?;
} }
@ -88,7 +102,8 @@ pub fn watch(
watch_state.render()?; watch_state.render()?;
} }
WatchEvent::NotifyErr(e) => { WatchEvent::NotifyErr(e) => {
return Err(Error::from(e).context("Exercise file watcher failed")); watch_state.into_writer().write_all(NOTIFY_ERR.as_bytes())?;
return Err(Error::from(e));
} }
WatchEvent::TerminalEventErr(e) => { WatchEvent::TerminalEventErr(e) => {
return Err(Error::from(e).context("Terminal event listener failed")); return Err(Error::from(e).context("Terminal event listener failed"));
@ -103,3 +118,11 @@ const QUIT_MSG: &[u8] = b"
We hope you're enjoying learning Rust! 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. 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.
";

@ -18,10 +18,11 @@ pub struct WatchState<'a> {
stderr: Option<Vec<u8>>, stderr: Option<Vec<u8>>,
show_hint: bool, show_hint: bool,
show_done: bool, show_done: bool,
manual_run: bool,
} }
impl<'a> WatchState<'a> { impl<'a> WatchState<'a> {
pub fn new(app_state: &'a mut AppState) -> Self { pub fn new(app_state: &'a mut AppState, manual_run: bool) -> Self {
let writer = io::stdout().lock(); let writer = io::stdout().lock();
Self { Self {
@ -31,6 +32,7 @@ impl<'a> WatchState<'a> {
stderr: None, stderr: None,
show_hint: false, show_hint: false,
show_done: false, show_done: false,
manual_run,
} }
} }
@ -78,6 +80,10 @@ impl<'a> WatchState<'a> {
fn show_prompt(&mut self) -> io::Result<()> { fn show_prompt(&mut self) -> io::Result<()> {
self.writer.write_all(b"\n")?; self.writer.write_all(b"\n")?;
if self.manual_run {
self.writer.write_fmt(format_args!("{}un/", 'r'.bold()))?;
}
if self.show_done { if self.show_done {
self.writer.write_fmt(format_args!("{}ext/", 'n'.bold()))?; self.writer.write_fmt(format_args!("{}ext/", 'n'.bold()))?;
} }

@ -4,6 +4,7 @@ use std::sync::mpsc::Sender;
use super::WatchEvent; use super::WatchEvent;
pub enum InputEvent { pub enum InputEvent {
Run,
Next, Next,
Hint, Hint,
List, List,
@ -11,7 +12,7 @@ pub enum InputEvent {
Unrecognized(String), Unrecognized(String),
} }
pub fn terminal_event_handler(tx: Sender<WatchEvent>) { pub fn terminal_event_handler(tx: Sender<WatchEvent>, manual_run: bool) {
let mut input = String::with_capacity(8); let mut input = String::with_capacity(8);
let last_input_event = loop { let last_input_event = loop {
@ -43,6 +44,7 @@ pub fn terminal_event_handler(tx: Sender<WatchEvent>) {
"h" | "hint" => InputEvent::Hint, "h" | "hint" => InputEvent::Hint,
"l" | "list" => break InputEvent::List, "l" | "list" => break InputEvent::List,
"q" | "quit" => break InputEvent::Quit, "q" | "quit" => break InputEvent::Quit,
"r" | "run" if manual_run => InputEvent::Run,
_ => InputEvent::Unrecognized(input.clone()), _ => InputEvent::Unrecognized(input.clone()),
}; };