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)
|
||||
- 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 `--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`
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -26,6 +26,10 @@ pub struct Args {
|
||||
/// Only use this if Rustlings fails to detect exercise file changes
|
||||
#[arg(long)]
|
||||
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)]
|
||||
|
||||
@ -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()?;
|
||||
}
|
||||
Some(Command::Run { name }) => {
|
||||
|
||||
11
src/watch.rs
11
src/watch.rs
@ -59,6 +59,7 @@ enum WatchExit {
|
||||
fn run_watch(
|
||||
app_state: &mut AppState,
|
||||
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
||||
auto_next: bool,
|
||||
) -> Result<WatchExit> {
|
||||
let (watch_event_sender, watch_event_receiver) = channel();
|
||||
|
||||
@ -87,7 +88,7 @@ fn run_watch(
|
||||
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();
|
||||
|
||||
watch_state.run_current_exercise(&mut stdout)?;
|
||||
@ -133,9 +134,10 @@ fn run_watch(
|
||||
fn watch_list_loop(
|
||||
app_state: &mut AppState,
|
||||
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
||||
auto_next: bool,
|
||||
) -> Result<()> {
|
||||
loop {
|
||||
match run_watch(app_state, notify_exercise_names)? {
|
||||
match run_watch(app_state, notify_exercise_names, auto_next)? {
|
||||
WatchExit::Shutdown => break Ok(()),
|
||||
// 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
|
||||
@ -149,6 +151,7 @@ fn watch_list_loop(
|
||||
pub fn watch(
|
||||
app_state: &mut AppState,
|
||||
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
||||
auto_next: bool,
|
||||
) -> Result<()> {
|
||||
// TODO: Use cfg_select! after bumping MSRV to at least 1.95
|
||||
#[cfg(not(windows))]
|
||||
@ -161,7 +164,7 @@ pub fn watch(
|
||||
rustix::termios::LocalModes::ICANON | rustix::termios::LocalModes::ECHO;
|
||||
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;
|
||||
rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?;
|
||||
@ -170,7 +173,7 @@ pub fn watch(
|
||||
}
|
||||
|
||||
#[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
|
||||
|
||||
@ -10,6 +10,7 @@ use std::{
|
||||
io::{self, Read, StdoutLock, Write},
|
||||
sync::mpsc::{Sender, SyncSender, sync_channel},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -24,6 +25,9 @@ const HEADING_ATTRIBUTES: Attributes = Attributes::none()
|
||||
.with(Attribute::Bold)
|
||||
.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)]
|
||||
enum DoneStatus {
|
||||
DoneWithSolution(String),
|
||||
@ -37,6 +41,7 @@ pub struct WatchState<'a> {
|
||||
show_hint: bool,
|
||||
done_status: DoneStatus,
|
||||
manual_run: bool,
|
||||
auto_next: bool,
|
||||
term_width: u16,
|
||||
terminal_event_unpause_sender: SyncSender<()>,
|
||||
}
|
||||
@ -46,6 +51,7 @@ impl<'a> WatchState<'a> {
|
||||
app_state: &'a mut AppState,
|
||||
watch_event_sender: Sender<WatchEvent>,
|
||||
manual_run: bool,
|
||||
auto_next: bool,
|
||||
) -> Result<Self> {
|
||||
let term_width = terminal::size()
|
||||
.context("Failed to get the terminal size")?
|
||||
@ -69,6 +75,7 @@ impl<'a> WatchState<'a> {
|
||||
show_hint: false,
|
||||
done_status: DoneStatus::Pending,
|
||||
manual_run,
|
||||
auto_next,
|
||||
term_width,
|
||||
terminal_event_unpause_sender,
|
||||
})
|
||||
@ -110,8 +117,21 @@ impl<'a> WatchState<'a> {
|
||||
self.app_state.join_editor_handle(editor_handle)?;
|
||||
self.render(stdout)?;
|
||||
|
||||
// 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<()> {
|
||||
clear_terminal(stdout)?;
|
||||
@ -175,7 +195,7 @@ impl<'a> WatchState<'a> {
|
||||
}
|
||||
|
||||
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.write_all(b"n")?;
|
||||
stdout.queue(ResetColor)?;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user