mirror of
https://github.com/rust-lang/rustlings.git
synced 2026-06-30 00:08:45 +00:00
feature to allow rustlings to automatically move on to the next exercise
This commit is contained in:
parent
4bab596677
commit
d754784ac0
@ -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 exercise after a correct solution.
|
||||
/// Can be toggled at runtime with `a` in the watch mode
|
||||
#[arg(long)]
|
||||
pub auto_move: 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_move)?;
|
||||
app_state.close_editor()?;
|
||||
}
|
||||
Some(Command::Run { name }) => {
|
||||
|
||||
14
src/watch.rs
14
src/watch.rs
@ -59,6 +59,7 @@ enum WatchExit {
|
||||
fn run_watch(
|
||||
app_state: &mut AppState,
|
||||
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
||||
auto_move: 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_move)?;
|
||||
let mut stdout = io::stdout().lock();
|
||||
|
||||
watch_state.run_current_exercise(&mut stdout)?;
|
||||
@ -110,6 +111,9 @@ fn run_watch(
|
||||
ExercisesProgress::CurrentPending => watch_state.render(&mut stdout)?,
|
||||
},
|
||||
WatchEvent::Input(InputEvent::Reset) => watch_state.reset_exercise(&mut stdout)?,
|
||||
WatchEvent::Input(InputEvent::ToggleAutoMove) => {
|
||||
watch_state.toggle_auto_move(&mut stdout)?;
|
||||
}
|
||||
WatchEvent::Input(InputEvent::Quit) => {
|
||||
stdout.write_all(QUIT_MSG)?;
|
||||
break;
|
||||
@ -133,9 +137,10 @@ fn run_watch(
|
||||
fn watch_list_loop(
|
||||
app_state: &mut AppState,
|
||||
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
||||
auto_move: bool,
|
||||
) -> Result<()> {
|
||||
loop {
|
||||
match run_watch(app_state, notify_exercise_names)? {
|
||||
match run_watch(app_state, notify_exercise_names, auto_move)? {
|
||||
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 +154,7 @@ fn watch_list_loop(
|
||||
pub fn watch(
|
||||
app_state: &mut AppState,
|
||||
notify_exercise_names: Option<&'static [&'static [u8]]>,
|
||||
auto_move: bool,
|
||||
) -> Result<()> {
|
||||
// TODO: Use cfg_select! after bumping MSRV to at least 1.95
|
||||
#[cfg(not(windows))]
|
||||
@ -161,7 +167,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_move);
|
||||
|
||||
termios.local_modes = original_local_modes;
|
||||
rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?;
|
||||
@ -170,7 +176,7 @@ pub fn watch(
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
watch_list_loop(app_state, notify_exercise_names)
|
||||
watch_list_loop(app_state, notify_exercise_names, auto_move)
|
||||
}
|
||||
|
||||
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::{
|
||||
@ -17,7 +18,7 @@ use crate::{
|
||||
clear_terminal,
|
||||
exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
|
||||
term::progress_bar,
|
||||
watch::{InputPauseGuard, WatchEvent, terminal_event::terminal_event_handler},
|
||||
watch::{InputPauseGuard, WatchEvent, terminal_event::InputEvent, terminal_event::terminal_event_handler},
|
||||
};
|
||||
|
||||
const HEADING_ATTRIBUTES: Attributes = Attributes::none()
|
||||
@ -37,8 +38,10 @@ pub struct WatchState<'a> {
|
||||
show_hint: bool,
|
||||
done_status: DoneStatus,
|
||||
manual_run: bool,
|
||||
auto_move: bool,
|
||||
term_width: u16,
|
||||
terminal_event_unpause_sender: SyncSender<()>,
|
||||
watch_event_sender: Sender<WatchEvent>,
|
||||
}
|
||||
|
||||
impl<'a> WatchState<'a> {
|
||||
@ -46,6 +49,7 @@ impl<'a> WatchState<'a> {
|
||||
app_state: &'a mut AppState,
|
||||
watch_event_sender: Sender<WatchEvent>,
|
||||
manual_run: bool,
|
||||
auto_move: bool,
|
||||
) -> Result<Self> {
|
||||
let term_width = terminal::size()
|
||||
.context("Failed to get the terminal size")?
|
||||
@ -53,10 +57,12 @@ impl<'a> WatchState<'a> {
|
||||
|
||||
let (terminal_event_unpause_sender, terminal_event_unpause_receiver) = sync_channel(0);
|
||||
|
||||
let event_sender_for_handler = watch_event_sender.clone();
|
||||
|
||||
thread::Builder::new()
|
||||
.spawn(move || {
|
||||
terminal_event_handler(
|
||||
watch_event_sender,
|
||||
event_sender_for_handler,
|
||||
terminal_event_unpause_receiver,
|
||||
manual_run,
|
||||
);
|
||||
@ -69,8 +75,10 @@ impl<'a> WatchState<'a> {
|
||||
show_hint: false,
|
||||
done_status: DoneStatus::Pending,
|
||||
manual_run,
|
||||
auto_move,
|
||||
term_width,
|
||||
terminal_event_unpause_sender,
|
||||
watch_event_sender,
|
||||
})
|
||||
}
|
||||
|
||||
@ -110,6 +118,18 @@ impl<'a> WatchState<'a> {
|
||||
self.app_state.join_editor_handle(editor_handle)?;
|
||||
self.render(stdout)?;
|
||||
|
||||
// Auto-move: if the exercise is done and auto_move is enabled,
|
||||
// spawn a thread that sends a Next event after 2 seconds.
|
||||
if self.auto_move && self.done_status != DoneStatus::Pending {
|
||||
let sender = self.watch_event_sender.clone();
|
||||
thread::Builder::new()
|
||||
.spawn(move || {
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
let _ = sender.send(WatchEvent::Input(InputEvent::Next));
|
||||
})
|
||||
.context("Failed to spawn auto-move thread")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -204,6 +224,7 @@ impl<'a> WatchState<'a> {
|
||||
show_key(b'l', b":list / ")?;
|
||||
show_key(b'c', b":check all / ")?;
|
||||
show_key(b'x', b":reset / ")?;
|
||||
show_key(b'a', b":auto move / ")?;
|
||||
show_key(b'q', b":quit ? ")?;
|
||||
|
||||
stdout.flush()
|
||||
@ -240,11 +261,18 @@ impl<'a> WatchState<'a> {
|
||||
solution_link_line(stdout, solution_path, self.app_state.emit_file_links())?;
|
||||
}
|
||||
|
||||
if self.auto_move {
|
||||
stdout.write_all(
|
||||
"Auto-moving to the next exercise in 2 seconds…\n\n"
|
||||
.as_bytes(),
|
||||
)?;
|
||||
} else {
|
||||
stdout.write_all(
|
||||
"When done experimenting, enter `n` to move on to the next exercise 🦀\n\n"
|
||||
.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
progress_bar(
|
||||
stdout,
|
||||
@ -259,6 +287,14 @@ impl<'a> WatchState<'a> {
|
||||
.terminal_file_link(stdout, self.app_state.emit_file_links())?;
|
||||
stdout.write_all(b"\n\n")?;
|
||||
|
||||
// Show auto-move status
|
||||
if self.auto_move {
|
||||
stdout.queue(SetForegroundColor(Color::Cyan))?;
|
||||
stdout.write_all(b"Auto-move: ON")?;
|
||||
stdout.queue(ResetColor)?;
|
||||
stdout.write_all(b"\n\n")?;
|
||||
}
|
||||
|
||||
self.show_prompt(stdout)?;
|
||||
|
||||
Ok(())
|
||||
@ -300,4 +336,9 @@ impl<'a> WatchState<'a> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn toggle_auto_move(&mut self, stdout: &mut StdoutLock) -> io::Result<()> {
|
||||
self.auto_move = !self.auto_move;
|
||||
self.render(stdout)
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ pub enum InputEvent {
|
||||
CheckAll,
|
||||
Reset,
|
||||
Quit,
|
||||
ToggleAutoMove,
|
||||
}
|
||||
|
||||
pub fn terminal_event_handler(
|
||||
@ -39,6 +40,7 @@ pub fn terminal_event_handler(
|
||||
KeyCode::Char('h') => InputEvent::Hint,
|
||||
KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List),
|
||||
KeyCode::Char('c') => InputEvent::CheckAll,
|
||||
KeyCode::Char('a') => InputEvent::ToggleAutoMove,
|
||||
KeyCode::Char('x') => {
|
||||
if sender.send(WatchEvent::Input(InputEvent::Reset)).is_err() {
|
||||
return;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user