From d55d2adef712f80b55c765594983bc21e7b5dad7 Mon Sep 17 00:00:00 2001 From: pacexy Date: Wed, 29 Jan 2025 18:43:53 +0800 Subject: [PATCH 1/6] init --- src/exercise.rs | 9 +++++++++ src/main.rs | 9 ++++++++- src/watch.rs | 12 +++++++++--- src/watch/state.rs | 19 +++++++++++++++++++ src/watch/terminal_event.rs | 2 ++ 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/exercise.rs b/src/exercise.rs index 849082847a..5061ad2cfc 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -4,6 +4,7 @@ use crossterm::{ QueueableCommand, }; use std::io::{self, StdoutLock, Write}; +use std::process::Command; use crate::{ cmd::CmdRunner, @@ -79,6 +80,14 @@ impl Exercise { writer.write_str(self.path) } + + /// Open the exercise file in the specified editor + pub fn open_in_editor(&self, editor_cmd: &str) -> io::Result { + dbg!(editor_cmd); + dbg!(self.path); + let status = Command::new(editor_cmd).arg(self.path).status()?; + Ok(status.success()) + } } pub trait RunnableExercise { diff --git a/src/main.rs b/src/main.rs index eeb1883edd..cedcc9aa93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,9 @@ struct Args { /// Only use this if Rustlings fails to detect exercise file changes. #[arg(long)] manual_run: bool, + /// Command to open exercise files in an editor (e.g. "code" for VS Code) + #[arg(long)] + edit_cmd: Option, } #[derive(Subcommand)] @@ -135,7 +138,11 @@ fn main() -> Result { ) }; - watch::watch(&mut app_state, notify_exercise_names)?; + watch::watch( + &mut app_state, + notify_exercise_names, + args.edit_cmd.as_deref(), + )?; } Some(Subcommands::Run { name }) => { if let Some(name) = name { diff --git a/src/watch.rs b/src/watch.rs index 3a56b4b65b..5cba1ef39f 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -62,6 +62,7 @@ enum WatchExit { fn run_watch( app_state: &mut AppState, notify_exercise_names: Option<&'static [&'static [u8]]>, + edit_cmd: Option<&str>, ) -> Result { let (watch_event_sender, watch_event_receiver) = channel(); @@ -113,6 +114,9 @@ fn run_watch( ExercisesProgress::CurrentPending => watch_state.render(&mut stdout)?, }, WatchEvent::Input(InputEvent::Reset) => watch_state.reset_exercise(&mut stdout)?, + WatchEvent::Input(InputEvent::Edit) => { + watch_state.edit_exercise(&mut stdout, edit_cmd)? + } WatchEvent::Input(InputEvent::Quit) => { stdout.write_all(QUIT_MSG)?; break; @@ -136,9 +140,10 @@ fn run_watch( fn watch_list_loop( app_state: &mut AppState, notify_exercise_names: Option<&'static [&'static [u8]]>, + edit_cmd: Option<&str>, ) -> Result<()> { loop { - match run_watch(app_state, notify_exercise_names)? { + match run_watch(app_state, notify_exercise_names, edit_cmd)? { 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 @@ -152,6 +157,7 @@ fn watch_list_loop( pub fn watch( app_state: &mut AppState, notify_exercise_names: Option<&'static [&'static [u8]]>, + edit_cmd: Option<&str>, ) -> Result<()> { #[cfg(not(windows))] { @@ -163,7 +169,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, edit_cmd); termios.local_modes = original_local_modes; rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?; @@ -172,7 +178,7 @@ pub fn watch( } #[cfg(windows)] - watch_list_loop(app_state, notify_exercise_names) + watch_list_loop(app_state, notify_exercise_names, edit_cmd) } const QUIT_MSG: &[u8] = b" diff --git a/src/watch/state.rs b/src/watch/state.rs index 5263bc5788..1379135634 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -199,6 +199,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'e', b":edit / ")?; show_key(b'q', b":quit ? ")?; stdout.flush() @@ -268,6 +269,24 @@ impl<'a> WatchState<'a> { Ok(()) } + pub fn edit_exercise( + &mut self, + stdout: &mut StdoutLock, + editor_cmd: Option<&str>, + ) -> io::Result<()> { + if let Some(editor_cmd) = editor_cmd { + if let Err(e) = self.app_state.current_exercise().open_in_editor(editor_cmd) { + writeln!(stdout, "Failed to open editor: {}", e)?; + } + } else { + writeln!( + stdout, + "No editor command specified. Use --edit-cmd to specify an editor." + )?; + } + Ok(()) + } + pub fn check_all_exercises(&mut self, stdout: &mut StdoutLock) -> Result { // Ignore any input until checking all exercises is done. let _input_pause_guard = InputPauseGuard::scoped_pause(); diff --git a/src/watch/terminal_event.rs b/src/watch/terminal_event.rs index 48411db0f7..3c32323dc2 100644 --- a/src/watch/terminal_event.rs +++ b/src/watch/terminal_event.rs @@ -14,6 +14,7 @@ pub enum InputEvent { CheckAll, Reset, Quit, + Edit, } pub fn terminal_event_handler( @@ -51,6 +52,7 @@ pub fn terminal_event_handler( continue; } + KeyCode::Char('e') => InputEvent::Edit, KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit), _ => continue, }; From 6c99c00a51fc7a8e87167a39214ac5ff9711ad82 Mon Sep 17 00:00:00 2001 From: pacexy Date: Wed, 29 Jan 2025 18:53:33 +0800 Subject: [PATCH 2/6] rename to `--editor` --- src/exercise.rs | 6 +++--- src/main.rs | 4 ++-- src/watch.rs | 14 +++++++------- src/watch/state.rs | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/exercise.rs b/src/exercise.rs index 5061ad2cfc..f4da912321 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -82,10 +82,10 @@ impl Exercise { } /// Open the exercise file in the specified editor - pub fn open_in_editor(&self, editor_cmd: &str) -> io::Result { - dbg!(editor_cmd); + pub fn open_in_editor(&self, editor: &str) -> io::Result { + dbg!(editor); dbg!(self.path); - let status = Command::new(editor_cmd).arg(self.path).status()?; + let status = Command::new(editor).arg(self.path).status()?; Ok(status.success()) } } diff --git a/src/main.rs b/src/main.rs index cedcc9aa93..cd849bb659 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ struct Args { manual_run: bool, /// Command to open exercise files in an editor (e.g. "code" for VS Code) #[arg(long)] - edit_cmd: Option, + editor: Option, } #[derive(Subcommand)] @@ -141,7 +141,7 @@ fn main() -> Result { watch::watch( &mut app_state, notify_exercise_names, - args.edit_cmd.as_deref(), + args.editor.as_deref(), )?; } Some(Subcommands::Run { name }) => { diff --git a/src/watch.rs b/src/watch.rs index 5cba1ef39f..ad4a10f736 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -62,7 +62,7 @@ enum WatchExit { fn run_watch( app_state: &mut AppState, notify_exercise_names: Option<&'static [&'static [u8]]>, - edit_cmd: Option<&str>, + editor: Option<&str>, ) -> Result { let (watch_event_sender, watch_event_receiver) = channel(); @@ -115,7 +115,7 @@ fn run_watch( }, WatchEvent::Input(InputEvent::Reset) => watch_state.reset_exercise(&mut stdout)?, WatchEvent::Input(InputEvent::Edit) => { - watch_state.edit_exercise(&mut stdout, edit_cmd)? + watch_state.edit_exercise(&mut stdout, editor)? } WatchEvent::Input(InputEvent::Quit) => { stdout.write_all(QUIT_MSG)?; @@ -140,10 +140,10 @@ fn run_watch( fn watch_list_loop( app_state: &mut AppState, notify_exercise_names: Option<&'static [&'static [u8]]>, - edit_cmd: Option<&str>, + editor: Option<&str>, ) -> Result<()> { loop { - match run_watch(app_state, notify_exercise_names, edit_cmd)? { + match run_watch(app_state, notify_exercise_names, editor)? { 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 @@ -157,7 +157,7 @@ fn watch_list_loop( pub fn watch( app_state: &mut AppState, notify_exercise_names: Option<&'static [&'static [u8]]>, - edit_cmd: Option<&str>, + editor: Option<&str>, ) -> Result<()> { #[cfg(not(windows))] { @@ -169,7 +169,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, edit_cmd); + let res = watch_list_loop(app_state, notify_exercise_names, editor); termios.local_modes = original_local_modes; rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?; @@ -178,7 +178,7 @@ pub fn watch( } #[cfg(windows)] - watch_list_loop(app_state, notify_exercise_names, edit_cmd) + watch_list_loop(app_state, notify_exercise_names, editor) } const QUIT_MSG: &[u8] = b" diff --git a/src/watch/state.rs b/src/watch/state.rs index 1379135634..1ad07bce04 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -272,16 +272,16 @@ impl<'a> WatchState<'a> { pub fn edit_exercise( &mut self, stdout: &mut StdoutLock, - editor_cmd: Option<&str>, + editor: Option<&str>, ) -> io::Result<()> { - if let Some(editor_cmd) = editor_cmd { - if let Err(e) = self.app_state.current_exercise().open_in_editor(editor_cmd) { + if let Some(editor) = editor { + if let Err(e) = self.app_state.current_exercise().open_in_editor(editor) { writeln!(stdout, "Failed to open editor: {}", e)?; } } else { writeln!( stdout, - "No editor command specified. Use --edit-cmd to specify an editor." + "No editor command specified. Use --editor to specify an editor." )?; } Ok(()) From ef3d2da9827564e2333cca1cd1fa0388fcd48dd4 Mon Sep 17 00:00:00 2001 From: pacexy Date: Wed, 29 Jan 2025 19:14:37 +0800 Subject: [PATCH 3/6] update docs --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 3118451f80..e786420ee3 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,16 @@ After [initialization](#initialization), Rustlings can be launched by simply run This will start the _watch mode_ which walks you through the exercises in a predefined order (what we think is best for newcomers). It will rerun the current exercise automatically every time you change the exercise's file in the `exercises/` directory. +You can specify an editor command with the `--editor` option to open exercises directly from watch mode: + +```bash +rustlings --editor code # For VS Code +rustlings --editor vim # For Vim +rustlings --editor "code --wait" # For VS Code with wait option +``` + +Then press `e` in watch mode to open the current exercise in your editor. +
If detecting file changes in the exercises/ directory fails… (click to expand) From 17f01e6a58a8d1546124a58c158750ccbd3cc01f Mon Sep 17 00:00:00 2001 From: pacexy Date: Wed, 29 Jan 2025 19:15:34 +0800 Subject: [PATCH 4/6] remove dbg --- src/exercise.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/exercise.rs b/src/exercise.rs index f4da912321..e018c7f8a2 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -83,8 +83,6 @@ impl Exercise { /// Open the exercise file in the specified editor pub fn open_in_editor(&self, editor: &str) -> io::Result { - dbg!(editor); - dbg!(self.path); let status = Command::new(editor).arg(self.path).status()?; Ok(status.success()) } From 20a69abfd9e3592939f253cc95c2e38d8a9019ca Mon Sep 17 00:00:00 2001 From: pacexy Date: Wed, 29 Jan 2025 19:44:40 +0800 Subject: [PATCH 5/6] support editor arguments --- src/exercise.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/exercise.rs b/src/exercise.rs index e018c7f8a2..6d2cd7839e 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -83,7 +83,21 @@ impl Exercise { /// Open the exercise file in the specified editor pub fn open_in_editor(&self, editor: &str) -> io::Result { - let status = Command::new(editor).arg(self.path).status()?; + let parts: Vec<&str> = editor.split_whitespace().collect(); + if parts.is_empty() { + return Ok(false); + } + + let mut cmd = Command::new(parts[0]); + + // If the editor command has arguments, add them to the command + if parts.len() > 1 { + cmd.args(&parts[1..]); + } + + cmd.arg(self.path); + + let status = cmd.status()?; Ok(status.success()) } } From 653b4b0441ca4e90c27e747af5ae27282c6b1e39 Mon Sep 17 00:00:00 2001 From: pacexy Date: Wed, 29 Jan 2025 21:00:25 +0800 Subject: [PATCH 6/6] update docs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e786420ee3..90e37f404d 100644 --- a/README.md +++ b/README.md @@ -110,9 +110,9 @@ It will rerun the current exercise automatically every time you change the exerc You can specify an editor command with the `--editor` option to open exercises directly from watch mode: ```bash -rustlings --editor code # For VS Code -rustlings --editor vim # For Vim -rustlings --editor "code --wait" # For VS Code with wait option +rustlings --editor code # For VS Code +rustlings --editor vim # For Vim +rustlings --editor "code --wait" # For VS Code with wait argument ``` Then press `e` in watch mode to open the current exercise in your editor.