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);
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));
}

View file

@ -22,7 +22,6 @@ enum DoneStatus {
}
pub struct WatchState<'a> {
stdout: StdoutLock<'a>,
app_state: &'a mut AppState,
output: Vec<u8>,
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<ExercisesProgress> {
pub fn next_exercise(&mut self, stdout: &mut StdoutLock) -> Result<ExercisesProgress> {
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)
}
}