summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo8it <mo8it@proton.me>2024-04-14 17:10:53 +0200
committermo8it <mo8it@proton.me>2024-04-14 17:10:53 +0200
commit1cbabc3d28a29a01caeffba969ed640e00e5f0be (patch)
tree4bed65819c4a05e1d9cb088d9b3fd969398143d8
parentbd10b154fe558af693e9f8f57dbb3e43f0bd0ec8 (diff)
Add the manual-run option
-rw-r--r--src/main.rs28
-rw-r--r--src/watch.rs53
-rw-r--r--src/watch/state.rs8
-rw-r--r--src/watch/terminal_event.rs4
4 files changed, 67 insertions, 26 deletions
diff --git a/src/main.rs b/src/main.rs
index 6796921..28a426b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -36,6 +36,10 @@ use self::{
struct Args {
#[command(subcommand)]
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)]
@@ -101,17 +105,23 @@ fn main() -> Result<()> {
match args.command {
None => {
- // For the the notify event handler thread.
- // Leaking is not a problem because the slice lives until the end of the program.
- let exercise_paths = app_state
- .exercises()
- .iter()
- .map(|exercise| exercise.path)
- .collect::<Vec<_>>()
- .leak();
+ let notify_exercise_paths: Option<&'static [&'static str]> = if args.manual_run {
+ None
+ } else {
+ // For the the notify event handler thread.
+ // Leaking is not a problem because the slice lives until the end of the program.
+ Some(
+ app_state
+ .exercises()
+ .iter()
+ .map(|exercise| exercise.path)
+ .collect::<Vec<_>>()
+ .leak(),
+ )
+ };
loop {
- match watch(&mut app_state, exercise_paths)? {
+ match watch(&mut app_state, notify_exercise_paths)? {
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
diff --git a/src/watch.rs b/src/watch.rs
index bab64ae..d20e552 100644
--- a/src/watch.rs
+++ b/src/watch.rs
@@ -42,25 +42,38 @@ pub enum WatchExit {
pub fn watch(
app_state: &mut AppState,
- exercise_paths: &'static [&'static str],
+ notify_exercise_paths: Option<&'static [&'static str]>,
) -> Result<WatchExit> {
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()?;
- thread::spawn(move || terminal_event_handler(tx));
+ thread::spawn(move || terminal_event_handler(tx, manual_run));
while let Ok(event) = rx.recv() {
match event {
@@ -78,6 +91,7 @@ pub fn watch(
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)?;
}
@@ -88,7 +102,8 @@ pub fn watch(
watch_state.render()?;
}
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) => {
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!
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.
+";
diff --git a/src/watch/state.rs b/src/watch/state.rs
index 1a79573..c0f6c53 100644
--- a/src/watch/state.rs
+++ b/src/watch/state.rs
@@ -18,10 +18,11 @@ pub struct WatchState<'a> {
stderr: Option<Vec<u8>>,
show_hint: bool,
show_done: bool,
+ manual_run: bool,
}
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();
Self {
@@ -31,6 +32,7 @@ impl<'a> WatchState<'a> {
stderr: None,
show_hint: false,
show_done: false,
+ manual_run,
}
}
@@ -78,6 +80,10 @@ impl<'a> WatchState<'a> {
fn show_prompt(&mut self) -> io::Result<()> {
self.writer.write_all(b"\n")?;
+ if self.manual_run {
+ self.writer.write_fmt(format_args!("{}un/", 'r'.bold()))?;
+ }
+
if self.show_done {
self.writer.write_fmt(format_args!("{}ext/", 'n'.bold()))?;
}
diff --git a/src/watch/terminal_event.rs b/src/watch/terminal_event.rs
index 7f7ebe0..6d790b7 100644
--- a/src/watch/terminal_event.rs
+++ b/src/watch/terminal_event.rs
@@ -4,6 +4,7 @@ use std::sync::mpsc::Sender;
use super::WatchEvent;
pub enum InputEvent {
+ Run,
Next,
Hint,
List,
@@ -11,7 +12,7 @@ pub enum InputEvent {
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 last_input_event = loop {
@@ -43,6 +44,7 @@ pub fn terminal_event_handler(tx: Sender<WatchEvent>) {
"h" | "hint" => InputEvent::Hint,
"l" | "list" => break InputEvent::List,
"q" | "quit" => break InputEvent::Quit,
+ "r" | "run" if manual_run => InputEvent::Run,
_ => InputEvent::Unrecognized(input.clone()),
};