From 159273e53291cd72d27795cd2cfe0820587e3009 Mon Sep 17 00:00:00 2001 From: mo8it Date: Mon, 26 Aug 2024 00:09:04 +0200 Subject: [PATCH] Take stdout as argument in watch mode --- src/watch.rs | 25 ++++----- src/watch/state.rs | 127 +++++++++++++++++++++------------------------ 2 files changed, 69 insertions(+), 83 deletions(-) diff --git a/src/watch.rs b/src/watch.rs index c6690304..e14d3c57 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -72,35 +72,32 @@ pub fn watch( let mut watch_state = WatchState::new(app_state, manual_run); - watch_state.run_current_exercise()?; + let mut stdout = io::stdout().lock(); + watch_state.run_current_exercise(&mut stdout)?; thread::spawn(move || terminal_event_handler(tx, manual_run)); while let Ok(event) = rx.recv() { match event { - WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise()? { + WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise(&mut stdout)? { ExercisesProgress::AllDone => break, - ExercisesProgress::CurrentPending => watch_state.render()?, - ExercisesProgress::NewPending => watch_state.run_current_exercise()?, + ExercisesProgress::CurrentPending => watch_state.render(&mut stdout)?, + ExercisesProgress::NewPending => watch_state.run_current_exercise(&mut stdout)?, }, - WatchEvent::Input(InputEvent::Hint) => { - watch_state.show_hint()?; - } + WatchEvent::Input(InputEvent::Hint) => watch_state.show_hint(&mut stdout)?, WatchEvent::Input(InputEvent::List) => { return Ok(WatchExit::List); } WatchEvent::Input(InputEvent::Quit) => { - watch_state.into_writer().write_all(QUIT_MSG)?; + stdout.write_all(QUIT_MSG)?; break; } - WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise()?, - WatchEvent::Input(InputEvent::Unrecognized) => watch_state.render()?, + WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise(&mut stdout)?, + WatchEvent::Input(InputEvent::Unrecognized) => watch_state.render(&mut stdout)?, WatchEvent::FileChange { exercise_ind } => { - watch_state.handle_file_change(exercise_ind)?; - } - WatchEvent::TerminalResize => { - watch_state.render()?; + watch_state.handle_file_change(exercise_ind, &mut stdout)?; } + WatchEvent::TerminalResize => watch_state.render(&mut stdout)?, WatchEvent::NotifyErr(e) => { return Err(Error::from(e).context(NOTIFY_ERR)); } diff --git a/src/watch/state.rs b/src/watch/state.rs index f9fd1389..47af9193 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -22,7 +22,6 @@ enum DoneStatus { } pub struct WatchState<'a> { - stdout: StdoutLock<'a>, app_state: &'a mut AppState, output: Vec, show_hint: bool, @@ -32,11 +31,7 @@ pub struct WatchState<'a> { impl<'a> WatchState<'a> { pub fn new(app_state: &'a mut AppState, manual_run: bool) -> Self { - // TODO: Take stdout as arg. - let stdout = io::stdout().lock(); - Self { - stdout, app_state, output: Vec::with_capacity(OUTPUT_CAPACITY), show_hint: false, @@ -45,16 +40,11 @@ impl<'a> WatchState<'a> { } } - #[inline] - pub fn into_writer(self) -> StdoutLock<'a> { - self.stdout - } - - pub fn run_current_exercise(&mut self) -> Result<()> { + pub fn run_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> { self.show_hint = false; writeln!( - self.stdout, + stdout, "\nChecking the exercise `{}`. Please wait…", self.app_state.current_exercise().name, )?; @@ -78,11 +68,15 @@ impl<'a> WatchState<'a> { self.done_status = DoneStatus::Pending; } - self.render()?; + self.render(stdout)?; Ok(()) } - pub fn handle_file_change(&mut self, exercise_ind: usize) -> Result<()> { + pub fn handle_file_change( + &mut self, + exercise_ind: usize, + stdout: &mut StdoutLock, + ) -> Result<()> { // Don't skip exercises on file changes to avoid confusion from missing exercises. // Skipping exercises must be explicit in the interactive list. // But going back to an earlier exercise on file change is fine. @@ -91,118 +85,113 @@ impl<'a> WatchState<'a> { } self.app_state.set_current_exercise_ind(exercise_ind)?; - self.run_current_exercise() + self.run_current_exercise(stdout) } /// Move on to the next exercise if the current one is done. - pub fn next_exercise(&mut self) -> Result { + pub fn next_exercise(&mut self, stdout: &mut StdoutLock) -> Result { if self.done_status == DoneStatus::Pending { return Ok(ExercisesProgress::CurrentPending); } - self.app_state.done_current_exercise(&mut self.stdout) + self.app_state.done_current_exercise(stdout) } - fn show_prompt(&mut self) -> io::Result<()> { + fn show_prompt(&self, stdout: &mut StdoutLock) -> io::Result<()> { if self.manual_run { - self.stdout.queue(SetAttribute(Attribute::Bold))?; - self.stdout.write_all(b"r")?; - self.stdout.queue(ResetColor)?; - self.stdout.write_all(b":run / ")?; + stdout.queue(SetAttribute(Attribute::Bold))?; + stdout.write_all(b"r")?; + stdout.queue(ResetColor)?; + stdout.write_all(b":run / ")?; } if self.done_status != DoneStatus::Pending { - self.stdout.queue(SetAttribute(Attribute::Bold))?; - self.stdout.write_all(b"n")?; - self.stdout.queue(ResetColor)?; - self.stdout.write_all(b":")?; - self.stdout.queue(SetAttribute(Attribute::Underlined))?; - self.stdout.write_all(b"next")?; - self.stdout.queue(ResetColor)?; - self.stdout.write_all(b" / ")?; + stdout.queue(SetAttribute(Attribute::Bold))?; + stdout.write_all(b"n")?; + stdout.queue(ResetColor)?; + stdout.write_all(b":")?; + stdout.queue(SetAttribute(Attribute::Underlined))?; + stdout.write_all(b"next")?; + stdout.queue(ResetColor)?; + stdout.write_all(b" / ")?; } if !self.show_hint { - self.stdout.queue(SetAttribute(Attribute::Bold))?; - self.stdout.write_all(b"h")?; - self.stdout.queue(ResetColor)?; - self.stdout.write_all(b":hint / ")?; + stdout.queue(SetAttribute(Attribute::Bold))?; + stdout.write_all(b"h")?; + stdout.queue(ResetColor)?; + stdout.write_all(b":hint / ")?; } - self.stdout.queue(SetAttribute(Attribute::Bold))?; - self.stdout.write_all(b"l")?; - self.stdout.queue(ResetColor)?; - self.stdout.write_all(b":list / ")?; + stdout.queue(SetAttribute(Attribute::Bold))?; + stdout.write_all(b"l")?; + stdout.queue(ResetColor)?; + stdout.write_all(b":list / ")?; - self.stdout.queue(SetAttribute(Attribute::Bold))?; - self.stdout.write_all(b"q")?; - self.stdout.queue(ResetColor)?; - self.stdout.write_all(b":quit ? ")?; + stdout.queue(SetAttribute(Attribute::Bold))?; + stdout.write_all(b"q")?; + stdout.queue(ResetColor)?; + stdout.write_all(b":quit ? ")?; - self.stdout.flush() + stdout.flush() } - pub fn render(&mut self) -> io::Result<()> { + pub fn render(&self, stdout: &mut StdoutLock) -> io::Result<()> { // Prevent having the first line shifted if clearing wasn't successful. - self.stdout.write_all(b"\n")?; - clear_terminal(&mut self.stdout)?; + stdout.write_all(b"\n")?; + clear_terminal(stdout)?; - self.stdout.write_all(&self.output)?; + stdout.write_all(&self.output)?; if self.show_hint { - self.stdout + stdout .queue(SetAttributes( Attributes::from(Attribute::Bold).with(Attribute::Underlined), ))? .queue(SetForegroundColor(Color::Cyan))?; - self.stdout.write_all(b"Hint\n")?; - self.stdout.queue(ResetColor)?; + stdout.write_all(b"Hint\n")?; + stdout.queue(ResetColor)?; - self.stdout - .write_all(self.app_state.current_exercise().hint.as_bytes())?; - self.stdout.write_all(b"\n\n")?; + stdout.write_all(self.app_state.current_exercise().hint.as_bytes())?; + stdout.write_all(b"\n\n")?; } if self.done_status != DoneStatus::Pending { - self.stdout + stdout .queue(SetAttribute(Attribute::Bold))? .queue(SetForegroundColor(Color::Green))?; - self.stdout.write_all("Exercise done ✓\n".as_bytes())?; - self.stdout.queue(ResetColor)?; + stdout.write_all("Exercise done ✓\n".as_bytes())?; + stdout.queue(ResetColor)?; if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status { - solution_link_line(&mut self.stdout, solution_path)?; + solution_link_line(stdout, solution_path)?; } writeln!( - self.stdout, + stdout, "When done experimenting, enter `n` to move on to the next exercise 🦀\n", )?; } let line_width = terminal::size()?.0; progress_bar( - &mut self.stdout, + stdout, self.app_state.n_done(), self.app_state.exercises().len() as u16, line_width, )?; - self.stdout.write_all(b"\nCurrent exercise: ")?; - terminal_file_link( - &mut self.stdout, - self.app_state.current_exercise().path, - Color::Blue, - )?; - self.stdout.write_all(b"\n\n")?; + stdout.write_all(b"\nCurrent exercise: ")?; + terminal_file_link(stdout, self.app_state.current_exercise().path, Color::Blue)?; + stdout.write_all(b"\n\n")?; - self.show_prompt()?; + self.show_prompt(stdout)?; Ok(()) } - pub fn show_hint(&mut self) -> io::Result<()> { + pub fn show_hint(&mut self, stdout: &mut StdoutLock) -> io::Result<()> { self.show_hint = true; - self.render() + self.render(stdout) } }