diff --git a/src/app_state.rs b/src/app_state.rs index 76a4c45..57ffea8 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -20,7 +20,7 @@ use crate::{ embedded::EMBEDDED_FILES, exercise::{Exercise, RunnableExercise}, info_file::ExerciseInfo, - term::{self, show_exercises_check_progress}, + term::{self, ExercisesCheckProgressVisualizer}, }; const STATE_FILE_NAME: &str = ".rustlings-state.txt"; @@ -409,13 +409,12 @@ impl AppState { } fn check_all_exercises_impl(&mut self, stdout: &mut StdoutLock) -> Result> { - stdout.write_all("Checking all exercises…\n".as_bytes())?; - let next_exercise_ind = AtomicUsize::new(0); let term_width = terminal::size() .context("Failed to get the terminal size")? .0; - clear_terminal(stdout)?; + let mut progress_visualizer = ExercisesCheckProgressVisualizer::build(stdout, term_width)?; + let next_exercise_ind = AtomicUsize::new(0); let mut progresses = vec![ExerciseCheckProgress::None; self.exercises.len()]; thread::scope(|s| { @@ -464,7 +463,7 @@ impl AppState { while let Ok((exercise_ind, progress)) = exercise_progress_receiver.recv() { progresses[exercise_ind] = progress; - show_exercises_check_progress(stdout, &progresses, term_width)?; + progress_visualizer.update(&progresses)?; } Ok::<_, Error>(()) @@ -487,7 +486,7 @@ impl AppState { // it could be because we exceeded the limit of open file descriptors. // Therefore, try running exercises with errors sequentially. progresses[exercise_ind] = ExerciseCheckProgress::Checking; - show_exercises_check_progress(stdout, &progresses, term_width)?; + progress_visualizer.update(&progresses)?; let exercise = &self.exercises[exercise_ind]; let success = exercise.run_exercise(None, &self.cmd_runner)?; @@ -501,7 +500,7 @@ impl AppState { } self.set_status(exercise_ind, success)?; - show_exercises_check_progress(stdout, &progresses, term_width)?; + progress_visualizer.update(&progresses)?; } } } diff --git a/src/term.rs b/src/term.rs index 8a2f8c5..13d5657 100644 --- a/src/term.rs +++ b/src/term.rs @@ -87,6 +87,74 @@ impl<'a> CountedWrite<'a> for StdoutLock<'a> { } } +pub struct ExercisesCheckProgressVisualizer<'a, 'b> { + stdout: &'a mut StdoutLock<'b>, + n_cols: usize, +} + +impl<'a, 'b> ExercisesCheckProgressVisualizer<'a, 'b> { + pub fn build(stdout: &'a mut StdoutLock<'b>, term_width: u16) -> io::Result { + clear_terminal(stdout)?; + stdout.write_all("Checking all exercises…\n".as_bytes())?; + + // Legend + stdout.write_all(b"Color of exercise number: ")?; + stdout.queue(SetForegroundColor(Color::Blue))?; + stdout.write_all(b"Checking")?; + stdout.queue(ResetColor)?; + stdout.write_all(b" - ")?; + stdout.queue(SetForegroundColor(Color::Green))?; + stdout.write_all(b"Done")?; + stdout.queue(ResetColor)?; + stdout.write_all(b" - ")?; + stdout.queue(SetForegroundColor(Color::Red))?; + stdout.write_all(b"Pending")?; + stdout.queue(ResetColor)?; + stdout.write_all(b"\n")?; + + // Exercise numbers with up to 3 digits. + // +1 because the last column doesn't end with a whitespace. + let n_cols = usize::from(term_width + 1) / 4; + + Ok(Self { stdout, n_cols }) + } + + pub fn update(&mut self, progresses: &[ExerciseCheckProgress]) -> io::Result<()> { + self.stdout.queue(MoveTo(0, 2))?; + + let mut exercise_num = 1; + for exercise_progress in progresses { + match exercise_progress { + ExerciseCheckProgress::None => (), + ExerciseCheckProgress::Checking => { + self.stdout.queue(SetForegroundColor(Color::Blue))?; + } + ExerciseCheckProgress::Done => { + self.stdout.queue(SetForegroundColor(Color::Green))?; + } + ExerciseCheckProgress::Pending => { + self.stdout.queue(SetForegroundColor(Color::Red))?; + } + } + + write!(self.stdout, "{exercise_num:<3}")?; + self.stdout.queue(ResetColor)?; + + if exercise_num != progresses.len() { + if exercise_num % self.n_cols == 0 { + self.stdout.write_all(b"\n")?; + } else { + self.stdout.write_all(b" ")?; + } + + exercise_num += 1; + } + } + + self.stdout.flush() + } +} + pub fn progress_bar<'a>( writer: &mut impl CountedWrite<'a>, progress: u16, @@ -137,63 +205,6 @@ pub fn progress_bar<'a>( write!(stdout, "] {progress:>3}/{total}") } -pub fn show_exercises_check_progress( - stdout: &mut StdoutLock, - progresses: &[ExerciseCheckProgress], - term_width: u16, -) -> io::Result<()> { - stdout.queue(MoveTo(0, 0))?; - - // Legend - stdout.write_all(b"Color of exercise number: ")?; - stdout.queue(SetForegroundColor(Color::Blue))?; - stdout.write_all(b"Checking")?; - stdout.queue(ResetColor)?; - stdout.write_all(b" - ")?; - stdout.queue(SetForegroundColor(Color::Green))?; - stdout.write_all(b"Done")?; - stdout.queue(ResetColor)?; - stdout.write_all(b" - ")?; - stdout.queue(SetForegroundColor(Color::Red))?; - stdout.write_all(b"Pending")?; - stdout.queue(ResetColor)?; - stdout.write_all(b"\n")?; - - // Exercise numbers with up to 3 digits. - let n_cols = usize::from(term_width + 1) / 4; - - let mut exercise_num = 1; - for exercise_progress in progresses { - match exercise_progress { - ExerciseCheckProgress::None => (), - ExerciseCheckProgress::Checking => { - stdout.queue(SetForegroundColor(Color::Blue))?; - } - ExerciseCheckProgress::Done => { - stdout.queue(SetForegroundColor(Color::Green))?; - } - ExerciseCheckProgress::Pending => { - stdout.queue(SetForegroundColor(Color::Red))?; - } - } - - write!(stdout, "{exercise_num:<3}")?; - stdout.queue(ResetColor)?; - - if exercise_num != progresses.len() { - if exercise_num % n_cols == 0 { - stdout.write_all(b"\n")?; - } else { - stdout.write_all(b" ")?; - } - - exercise_num += 1; - } - } - - stdout.flush() -} - pub fn clear_terminal(stdout: &mut StdoutLock) -> io::Result<()> { stdout .queue(MoveTo(0, 0))?