Take stdout as argument in watch mode

This commit is contained in:
mo8it 2024-08-26 00:09:04 +02:00
parent 631f2db1a3
commit 159273e532
2 changed files with 69 additions and 83 deletions

View file

@ -72,35 +72,32 @@ pub fn watch(
let mut watch_state = WatchState::new(app_state, manual_run); 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)); thread::spawn(move || terminal_event_handler(tx, manual_run));
while let Ok(event) = rx.recv() { while let Ok(event) = rx.recv() {
match event { 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::AllDone => break,
ExercisesProgress::CurrentPending => watch_state.render()?, ExercisesProgress::CurrentPending => watch_state.render(&mut stdout)?,
ExercisesProgress::NewPending => watch_state.run_current_exercise()?, ExercisesProgress::NewPending => watch_state.run_current_exercise(&mut stdout)?,
}, },
WatchEvent::Input(InputEvent::Hint) => { WatchEvent::Input(InputEvent::Hint) => watch_state.show_hint(&mut stdout)?,
watch_state.show_hint()?;
}
WatchEvent::Input(InputEvent::List) => { WatchEvent::Input(InputEvent::List) => {
return Ok(WatchExit::List); return Ok(WatchExit::List);
} }
WatchEvent::Input(InputEvent::Quit) => { WatchEvent::Input(InputEvent::Quit) => {
watch_state.into_writer().write_all(QUIT_MSG)?; stdout.write_all(QUIT_MSG)?;
break; break;
} }
WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise()?, WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise(&mut stdout)?,
WatchEvent::Input(InputEvent::Unrecognized) => watch_state.render()?, WatchEvent::Input(InputEvent::Unrecognized) => watch_state.render(&mut stdout)?,
WatchEvent::FileChange { exercise_ind } => { WatchEvent::FileChange { exercise_ind } => {
watch_state.handle_file_change(exercise_ind)?; watch_state.handle_file_change(exercise_ind, &mut stdout)?;
}
WatchEvent::TerminalResize => {
watch_state.render()?;
} }
WatchEvent::TerminalResize => watch_state.render(&mut stdout)?,
WatchEvent::NotifyErr(e) => { WatchEvent::NotifyErr(e) => {
return Err(Error::from(e).context(NOTIFY_ERR)); return Err(Error::from(e).context(NOTIFY_ERR));
} }

View file

@ -22,7 +22,6 @@ enum DoneStatus {
} }
pub struct WatchState<'a> { pub struct WatchState<'a> {
stdout: StdoutLock<'a>,
app_state: &'a mut AppState, app_state: &'a mut AppState,
output: Vec<u8>, output: Vec<u8>,
show_hint: bool, show_hint: bool,
@ -32,11 +31,7 @@ pub struct WatchState<'a> {
impl<'a> WatchState<'a> { impl<'a> WatchState<'a> {
pub fn new(app_state: &'a mut AppState, manual_run: bool) -> Self { pub fn new(app_state: &'a mut AppState, manual_run: bool) -> Self {
// TODO: Take stdout as arg.
let stdout = io::stdout().lock();
Self { Self {
stdout,
app_state, app_state,
output: Vec::with_capacity(OUTPUT_CAPACITY), output: Vec::with_capacity(OUTPUT_CAPACITY),
show_hint: false, show_hint: false,
@ -45,16 +40,11 @@ impl<'a> WatchState<'a> {
} }
} }
#[inline] pub fn run_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> {
pub fn into_writer(self) -> StdoutLock<'a> {
self.stdout
}
pub fn run_current_exercise(&mut self) -> Result<()> {
self.show_hint = false; self.show_hint = false;
writeln!( writeln!(
self.stdout, stdout,
"\nChecking the exercise `{}`. Please wait…", "\nChecking the exercise `{}`. Please wait…",
self.app_state.current_exercise().name, self.app_state.current_exercise().name,
)?; )?;
@ -78,11 +68,15 @@ impl<'a> WatchState<'a> {
self.done_status = DoneStatus::Pending; self.done_status = DoneStatus::Pending;
} }
self.render()?; self.render(stdout)?;
Ok(()) 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. // Don't skip exercises on file changes to avoid confusion from missing exercises.
// Skipping exercises must be explicit in the interactive list. // Skipping exercises must be explicit in the interactive list.
// But going back to an earlier exercise on file change is fine. // 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.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. /// Move on to the next exercise if the current one is done.
pub fn next_exercise(&mut self) -> Result<ExercisesProgress> { pub fn next_exercise(&mut self, stdout: &mut StdoutLock) -> Result<ExercisesProgress> {
if self.done_status == DoneStatus::Pending { if self.done_status == DoneStatus::Pending {
return Ok(ExercisesProgress::CurrentPending); 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 { if self.manual_run {
self.stdout.queue(SetAttribute(Attribute::Bold))?; stdout.queue(SetAttribute(Attribute::Bold))?;
self.stdout.write_all(b"r")?; stdout.write_all(b"r")?;
self.stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
self.stdout.write_all(b":run / ")?; stdout.write_all(b":run / ")?;
} }
if self.done_status != DoneStatus::Pending { if self.done_status != DoneStatus::Pending {
self.stdout.queue(SetAttribute(Attribute::Bold))?; stdout.queue(SetAttribute(Attribute::Bold))?;
self.stdout.write_all(b"n")?; stdout.write_all(b"n")?;
self.stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
self.stdout.write_all(b":")?; stdout.write_all(b":")?;
self.stdout.queue(SetAttribute(Attribute::Underlined))?; stdout.queue(SetAttribute(Attribute::Underlined))?;
self.stdout.write_all(b"next")?; stdout.write_all(b"next")?;
self.stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
self.stdout.write_all(b" / ")?; stdout.write_all(b" / ")?;
} }
if !self.show_hint { if !self.show_hint {
self.stdout.queue(SetAttribute(Attribute::Bold))?; stdout.queue(SetAttribute(Attribute::Bold))?;
self.stdout.write_all(b"h")?; stdout.write_all(b"h")?;
self.stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
self.stdout.write_all(b":hint / ")?; stdout.write_all(b":hint / ")?;
} }
self.stdout.queue(SetAttribute(Attribute::Bold))?; stdout.queue(SetAttribute(Attribute::Bold))?;
self.stdout.write_all(b"l")?; stdout.write_all(b"l")?;
self.stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
self.stdout.write_all(b":list / ")?; stdout.write_all(b":list / ")?;
self.stdout.queue(SetAttribute(Attribute::Bold))?; stdout.queue(SetAttribute(Attribute::Bold))?;
self.stdout.write_all(b"q")?; stdout.write_all(b"q")?;
self.stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
self.stdout.write_all(b":quit ? ")?; 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. // Prevent having the first line shifted if clearing wasn't successful.
self.stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
clear_terminal(&mut self.stdout)?; clear_terminal(stdout)?;
self.stdout.write_all(&self.output)?; stdout.write_all(&self.output)?;
if self.show_hint { if self.show_hint {
self.stdout stdout
.queue(SetAttributes( .queue(SetAttributes(
Attributes::from(Attribute::Bold).with(Attribute::Underlined), Attributes::from(Attribute::Bold).with(Attribute::Underlined),
))? ))?
.queue(SetForegroundColor(Color::Cyan))?; .queue(SetForegroundColor(Color::Cyan))?;
self.stdout.write_all(b"Hint\n")?; stdout.write_all(b"Hint\n")?;
self.stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
self.stdout stdout.write_all(self.app_state.current_exercise().hint.as_bytes())?;
.write_all(self.app_state.current_exercise().hint.as_bytes())?; stdout.write_all(b"\n\n")?;
self.stdout.write_all(b"\n\n")?;
} }
if self.done_status != DoneStatus::Pending { if self.done_status != DoneStatus::Pending {
self.stdout stdout
.queue(SetAttribute(Attribute::Bold))? .queue(SetAttribute(Attribute::Bold))?
.queue(SetForegroundColor(Color::Green))?; .queue(SetForegroundColor(Color::Green))?;
self.stdout.write_all("Exercise done ✓\n".as_bytes())?; stdout.write_all("Exercise done ✓\n".as_bytes())?;
self.stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status { if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status {
solution_link_line(&mut self.stdout, solution_path)?; solution_link_line(stdout, solution_path)?;
} }
writeln!( writeln!(
self.stdout, stdout,
"When done experimenting, enter `n` to move on to the next exercise 🦀\n", "When done experimenting, enter `n` to move on to the next exercise 🦀\n",
)?; )?;
} }
let line_width = terminal::size()?.0; let line_width = terminal::size()?.0;
progress_bar( progress_bar(
&mut self.stdout, stdout,
self.app_state.n_done(), self.app_state.n_done(),
self.app_state.exercises().len() as u16, self.app_state.exercises().len() as u16,
line_width, line_width,
)?; )?;
self.stdout.write_all(b"\nCurrent exercise: ")?; stdout.write_all(b"\nCurrent exercise: ")?;
terminal_file_link( terminal_file_link(stdout, self.app_state.current_exercise().path, Color::Blue)?;
&mut self.stdout, stdout.write_all(b"\n\n")?;
self.app_state.current_exercise().path,
Color::Blue,
)?;
self.stdout.write_all(b"\n\n")?;
self.show_prompt()?; self.show_prompt(stdout)?;
Ok(()) 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.show_hint = true;
self.render() self.render(stdout)
} }
} }