diff --git a/src/exercise.rs b/src/exercise.rs index 84908284..5061ad2c 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 eeb1883e..cedcc9aa 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 3a56b4b6..5cba1ef3 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 5263bc57..13791356 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 48411db0..3c32323d 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, };