mirror of
https://github.com/rust-lang/rustlings.git
synced 2026-06-30 00:08:45 +00:00
Add --auto-next flag to skip the post-success prompt in watch mode
After passing an exercise, the watch loop currently waits for `n` before advancing. Add an opt-in `--auto-next` flag that renders the "Exercise done" screen for ~1.5s, then auto-advances to the next pending exercise and runs it. Auto-advance only fires on success; a failing exercise still stops at the prompt. The `n:next` key is hidden from the prompt when --auto-next is on, but the user can still press `n` to skip ahead manually. --auto-next is compatible with --manual-run: pressing `r` triggers a check, and on success the loop auto-advances the same way.
This commit is contained in:
parent
5e7a5d1721
commit
513e28f064
@ -8,6 +8,7 @@
|
|||||||
- Automatically open the current file with `$EDITOR` in a new pane if Rustlings is running in [Zellij](https://zellij.dev)
|
- Automatically open the current file with `$EDITOR` in a new pane if Rustlings is running in [Zellij](https://zellij.dev)
|
||||||
- New argument `--no-editor` to disable automatic opening of the current file in VS Code or Zellij
|
- New argument `--no-editor` to disable automatic opening of the current file in VS Code or Zellij
|
||||||
- New argument `--edit-cmd` to communicate with an editor running in a different process to open the current exercise
|
- New argument `--edit-cmd` to communicate with an editor running in a different process to open the current exercise
|
||||||
|
- New argument `--auto-next` to automatically move on to the next pending exercise after it passes, without waiting for `n`
|
||||||
- Show the file link of the current exercise when running `rustlings hint` and `rustlings reset`
|
- Show the file link of the current exercise when running `rustlings hint` and `rustlings reset`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -26,6 +26,10 @@ pub struct Args {
|
|||||||
/// Only use this if Rustlings fails to detect exercise file changes
|
/// Only use this if Rustlings fails to detect exercise file changes
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub manual_run: bool,
|
pub manual_run: bool,
|
||||||
|
/// Automatically move on to the next pending exercise after it passes,
|
||||||
|
/// without waiting for `n`. Useful for learners who want a continuous flow.
|
||||||
|
#[arg(long)]
|
||||||
|
pub auto_next: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
|
|||||||
@ -118,7 +118,7 @@ fn main() -> Result<ExitCode> {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
watch::watch(&mut app_state, notify_exercise_names)?;
|
watch::watch(&mut app_state, notify_exercise_names, args.auto_next)?;
|
||||||
app_state.close_editor()?;
|
app_state.close_editor()?;
|
||||||
}
|
}
|
||||||
Some(Command::Run { name }) => {
|
Some(Command::Run { name }) => {
|
||||||
|
|||||||
11
src/watch.rs
11
src/watch.rs
@ -59,6 +59,7 @@ enum WatchExit {
|
|||||||
fn run_watch(
|
fn run_watch(
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
||||||
|
auto_next: bool,
|
||||||
) -> Result<WatchExit> {
|
) -> Result<WatchExit> {
|
||||||
let (watch_event_sender, watch_event_receiver) = channel();
|
let (watch_event_sender, watch_event_receiver) = channel();
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ fn run_watch(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut watch_state = WatchState::build(app_state, watch_event_sender, manual_run)?;
|
let mut watch_state = WatchState::build(app_state, watch_event_sender, manual_run, auto_next)?;
|
||||||
let mut stdout = io::stdout().lock();
|
let mut stdout = io::stdout().lock();
|
||||||
|
|
||||||
watch_state.run_current_exercise(&mut stdout)?;
|
watch_state.run_current_exercise(&mut stdout)?;
|
||||||
@ -133,9 +134,10 @@ fn run_watch(
|
|||||||
fn watch_list_loop(
|
fn watch_list_loop(
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
||||||
|
auto_next: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
match run_watch(app_state, notify_exercise_names)? {
|
match run_watch(app_state, notify_exercise_names, auto_next)? {
|
||||||
WatchExit::Shutdown => break Ok(()),
|
WatchExit::Shutdown => break Ok(()),
|
||||||
// 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
|
||||||
@ -149,6 +151,7 @@ fn watch_list_loop(
|
|||||||
pub fn watch(
|
pub fn watch(
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
||||||
|
auto_next: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// TODO: Use cfg_select! after bumping MSRV to at least 1.95
|
// TODO: Use cfg_select! after bumping MSRV to at least 1.95
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
@ -161,7 +164,7 @@ pub fn watch(
|
|||||||
rustix::termios::LocalModes::ICANON | rustix::termios::LocalModes::ECHO;
|
rustix::termios::LocalModes::ICANON | rustix::termios::LocalModes::ECHO;
|
||||||
rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?;
|
rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?;
|
||||||
|
|
||||||
let res = watch_list_loop(app_state, notify_exercise_names);
|
let res = watch_list_loop(app_state, notify_exercise_names, auto_next);
|
||||||
|
|
||||||
termios.local_modes = original_local_modes;
|
termios.local_modes = original_local_modes;
|
||||||
rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?;
|
rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?;
|
||||||
@ -170,7 +173,7 @@ pub fn watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
watch_list_loop(app_state, notify_exercise_names)
|
watch_list_loop(app_state, notify_exercise_names, auto_next)
|
||||||
}
|
}
|
||||||
|
|
||||||
const QUIT_MSG: &[u8] = b"q\n
|
const QUIT_MSG: &[u8] = b"q\n
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use std::{
|
|||||||
io::{self, Read, StdoutLock, Write},
|
io::{self, Read, StdoutLock, Write},
|
||||||
sync::mpsc::{Sender, SyncSender, sync_channel},
|
sync::mpsc::{Sender, SyncSender, sync_channel},
|
||||||
thread,
|
thread,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -24,6 +25,9 @@ const HEADING_ATTRIBUTES: Attributes = Attributes::none()
|
|||||||
.with(Attribute::Bold)
|
.with(Attribute::Bold)
|
||||||
.with(Attribute::Underlined);
|
.with(Attribute::Underlined);
|
||||||
|
|
||||||
|
// How long to show the "Exercise done" screen before auto-advancing.
|
||||||
|
const AUTO_NEXT_DELAY: Duration = Duration::from_millis(1500);
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
enum DoneStatus {
|
enum DoneStatus {
|
||||||
DoneWithSolution(String),
|
DoneWithSolution(String),
|
||||||
@ -37,6 +41,7 @@ pub struct WatchState<'a> {
|
|||||||
show_hint: bool,
|
show_hint: bool,
|
||||||
done_status: DoneStatus,
|
done_status: DoneStatus,
|
||||||
manual_run: bool,
|
manual_run: bool,
|
||||||
|
auto_next: bool,
|
||||||
term_width: u16,
|
term_width: u16,
|
||||||
terminal_event_unpause_sender: SyncSender<()>,
|
terminal_event_unpause_sender: SyncSender<()>,
|
||||||
}
|
}
|
||||||
@ -46,6 +51,7 @@ impl<'a> WatchState<'a> {
|
|||||||
app_state: &'a mut AppState,
|
app_state: &'a mut AppState,
|
||||||
watch_event_sender: Sender<WatchEvent>,
|
watch_event_sender: Sender<WatchEvent>,
|
||||||
manual_run: bool,
|
manual_run: bool,
|
||||||
|
auto_next: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let term_width = terminal::size()
|
let term_width = terminal::size()
|
||||||
.context("Failed to get the terminal size")?
|
.context("Failed to get the terminal size")?
|
||||||
@ -69,6 +75,7 @@ impl<'a> WatchState<'a> {
|
|||||||
show_hint: false,
|
show_hint: false,
|
||||||
done_status: DoneStatus::Pending,
|
done_status: DoneStatus::Pending,
|
||||||
manual_run,
|
manual_run,
|
||||||
|
auto_next,
|
||||||
term_width,
|
term_width,
|
||||||
terminal_event_unpause_sender,
|
terminal_event_unpause_sender,
|
||||||
})
|
})
|
||||||
@ -110,7 +117,20 @@ impl<'a> WatchState<'a> {
|
|||||||
self.app_state.join_editor_handle(editor_handle)?;
|
self.app_state.join_editor_handle(editor_handle)?;
|
||||||
self.render(stdout)?;
|
self.render(stdout)?;
|
||||||
|
|
||||||
Ok(())
|
// If `--auto-next` is on and the exercise passed, briefly show the
|
||||||
|
// "done" screen, then advance to the next pending exercise and run it.
|
||||||
|
// The recursion bottoms out when we hit a failing exercise
|
||||||
|
// (rendering leaves the user at the prompt) or when everything is done.
|
||||||
|
if self.auto_next && self.done_status != DoneStatus::Pending {
|
||||||
|
thread::sleep(AUTO_NEXT_DELAY);
|
||||||
|
match self.next_exercise(stdout)? {
|
||||||
|
ExercisesProgress::AllDone => Ok(()),
|
||||||
|
ExercisesProgress::NewPending => self.run_current_exercise(stdout),
|
||||||
|
ExercisesProgress::CurrentPending => Ok(()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> {
|
pub fn reset_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> {
|
||||||
@ -175,7 +195,7 @@ impl<'a> WatchState<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show_prompt(&self, stdout: &mut StdoutLock) -> io::Result<()> {
|
fn show_prompt(&self, stdout: &mut StdoutLock) -> io::Result<()> {
|
||||||
if self.done_status != DoneStatus::Pending {
|
if !self.auto_next && self.done_status != DoneStatus::Pending {
|
||||||
stdout.queue(SetAttribute(Attribute::Bold))?;
|
stdout.queue(SetAttribute(Attribute::Bold))?;
|
||||||
stdout.write_all(b"n")?;
|
stdout.write_all(b"n")?;
|
||||||
stdout.queue(ResetColor)?;
|
stdout.queue(ResetColor)?;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user