mirror of
https://github.com/notohh/rustlings.git
synced 2024-11-21 21:42:23 -05:00
Add the manual-run option
This commit is contained in:
parent
bd10b154fe
commit
1cbabc3d28
4 changed files with 66 additions and 25 deletions
28
src/main.rs
28
src/main.rs
|
@ -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
|
||||||
|
|
51
src/watch.rs
51
src/watch.rs
|
@ -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()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue