Skip to content

Commit 396ee4d

Browse files
committed
Show progress with exercise numbers
1 parent 326169a commit 396ee4d

File tree

3 files changed

+114
-159
lines changed

3 files changed

+114
-159
lines changed

src/app_state.rs

Lines changed: 47 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
use anyhow::{bail, Context, Error, Result};
2-
use crossterm::{
3-
style::{ResetColor, SetForegroundColor},
4-
terminal, QueueableCommand,
5-
};
2+
use crossterm::{cursor, terminal, QueueableCommand};
63
use std::{
74
env,
85
fs::{File, OpenOptions},
@@ -23,7 +20,7 @@ use crate::{
2320
embedded::EMBEDDED_FILES,
2421
exercise::{Exercise, RunnableExercise},
2522
info_file::ExerciseInfo,
26-
term::{self, progress_bar_with_success},
23+
term::{self, show_exercises_check_progress},
2724
};
2825

2926
const STATE_FILE_NAME: &str = ".rustlings-state.txt";
@@ -44,18 +41,12 @@ pub enum StateFileStatus {
4441
NotRead,
4542
}
4643

47-
enum ExerciseCheckProgress {
48-
Checking,
49-
Done,
50-
Pending,
51-
Error,
52-
}
53-
5444
#[derive(Clone, Copy)]
55-
enum ExerciseCheckResult {
45+
pub enum ExerciseCheckProgress {
46+
None,
47+
Checking,
5648
Done,
5749
Pending,
58-
Error,
5950
}
6051

6152
pub struct AppState {
@@ -417,27 +408,25 @@ impl AppState {
417408
}
418409
}
419410

420-
// Return the exercise index of the first pending exercise found.
421-
pub fn check_all_exercises(&mut self, stdout: &mut StdoutLock) -> Result<Option<usize>> {
411+
fn check_all_exercises_impl(&mut self, stdout: &mut StdoutLock) -> Result<Option<usize>> {
422412
stdout.write_all("Checking all exercises…\n".as_bytes())?;
423-
let n_exercises = self.exercises.len() as u16;
424413
let next_exercise_ind = AtomicUsize::new(0);
425414
let term_width = terminal::size()
426415
.context("Failed to get the terminal size")?
427416
.0;
417+
clear_terminal(stdout)?;
428418

429-
let mut results = vec![ExerciseCheckResult::Error; self.exercises.len()];
419+
let mut progresses = vec![ExerciseCheckProgress::None; self.exercises.len()];
430420
let mut done = 0;
431421
let mut pending = 0;
432422

433423
thread::scope(|s| {
434-
let mut checking = 0;
435-
let (exercise_result_sender, exercise_result_receiver) = mpsc::channel();
424+
let (exercise_progress_sender, exercise_progress_receiver) = mpsc::channel();
436425
let n_threads = thread::available_parallelism()
437426
.map_or(DEFAULT_CHECK_PARALLELISM, |count| count.get());
438427

439428
for _ in 0..n_threads {
440-
let exercise_result_sender = exercise_result_sender.clone();
429+
let exercise_progress_sender = exercise_progress_sender.clone();
441430
let next_exercise_ind = &next_exercise_ind;
442431
let slf = &self;
443432
thread::Builder::new()
@@ -449,125 +438,102 @@ impl AppState {
449438
};
450439

451440
// Notify the progress bar that this exercise is pending.
452-
if exercise_result_sender
441+
if exercise_progress_sender
453442
.send((exercise_ind, ExerciseCheckProgress::Checking))
454443
.is_err()
455444
{
456445
break;
457446
};
458447

459448
let success = exercise.run_exercise(None, &slf.cmd_runner);
460-
let result = match success {
449+
let progress = match success {
461450
Ok(true) => ExerciseCheckProgress::Done,
462451
Ok(false) => ExerciseCheckProgress::Pending,
463-
Err(_) => ExerciseCheckProgress::Error,
452+
Err(_) => ExerciseCheckProgress::None,
464453
};
465454

466455
// Notify the progress bar that this exercise is done.
467-
if exercise_result_sender.send((exercise_ind, result)).is_err() {
456+
if exercise_progress_sender
457+
.send((exercise_ind, progress))
458+
.is_err()
459+
{
468460
break;
469461
}
470462
})
471463
.context("Failed to spawn a thread to check all exercises")?;
472464
}
473465

474466
// Drop this sender to detect when the last thread is done.
475-
drop(exercise_result_sender);
476-
477-
// Print the legend.
478-
stdout.write_all(b"Color legend: ")?;
479-
stdout.queue(SetForegroundColor(term::PROGRESS_FAILED_COLOR))?;
480-
stdout.write_all(b"Pending")?;
481-
stdout.queue(ResetColor)?;
482-
stdout.write_all(b" - ")?;
483-
stdout.queue(SetForegroundColor(term::PROGRESS_SUCCESS_COLOR))?;
484-
stdout.write_all(b"Done")?;
485-
stdout.queue(ResetColor)?;
486-
stdout.write_all(b" - ")?;
487-
stdout.queue(SetForegroundColor(term::PROGRESS_PENDING_COLOR))?;
488-
stdout.write_all(b"Checking")?;
489-
stdout.queue(ResetColor)?;
490-
stdout.write_all(b"\n")?;
467+
drop(exercise_progress_sender);
491468

492-
while let Ok((exercise_ind, result)) = exercise_result_receiver.recv() {
493-
match result {
494-
ExerciseCheckProgress::Checking => checking += 1,
495-
ExerciseCheckProgress::Done => {
496-
results[exercise_ind] = ExerciseCheckResult::Done;
497-
checking -= 1;
498-
done += 1;
499-
}
500-
ExerciseCheckProgress::Pending => {
501-
results[exercise_ind] = ExerciseCheckResult::Pending;
502-
checking -= 1;
503-
pending += 1;
504-
}
505-
ExerciseCheckProgress::Error => checking -= 1,
469+
while let Ok((exercise_ind, progress)) = exercise_progress_receiver.recv() {
470+
progresses[exercise_ind] = progress;
471+
472+
match progress {
473+
ExerciseCheckProgress::None | ExerciseCheckProgress::Checking => (),
474+
ExerciseCheckProgress::Done => done += 1,
475+
ExerciseCheckProgress::Pending => pending += 1,
506476
}
507477

508-
stdout.write_all(b"\r")?;
509-
progress_bar_with_success(
510-
stdout,
511-
checking,
512-
pending,
513-
done,
514-
n_exercises,
515-
term_width,
516-
)?;
517-
stdout.flush()?;
478+
show_exercises_check_progress(stdout, &progresses, term_width)?;
518479
}
519480

520481
Ok::<_, Error>(())
521482
})?;
522483

523484
let mut first_pending_exercise_ind = None;
524-
for (exercise_ind, result) in results.into_iter().enumerate() {
525-
match result {
526-
ExerciseCheckResult::Done => {
485+
for exercise_ind in 0..progresses.len() {
486+
match progresses[exercise_ind] {
487+
ExerciseCheckProgress::Done => {
527488
self.set_status(exercise_ind, true)?;
528489
}
529-
ExerciseCheckResult::Pending => {
490+
ExerciseCheckProgress::Pending => {
530491
self.set_status(exercise_ind, false)?;
531492
if first_pending_exercise_ind.is_none() {
532493
first_pending_exercise_ind = Some(exercise_ind);
533494
}
534495
}
535-
ExerciseCheckResult::Error => {
496+
ExerciseCheckProgress::None | ExerciseCheckProgress::Checking => {
536497
// If we got an error while checking all exercises in parallel,
537498
// it could be because we exceeded the limit of open file descriptors.
538499
// Therefore, try running exercises with errors sequentially.
500+
progresses[exercise_ind] = ExerciseCheckProgress::Checking;
501+
show_exercises_check_progress(stdout, &progresses, term_width)?;
502+
539503
let exercise = &self.exercises[exercise_ind];
540504
let success = exercise.run_exercise(None, &self.cmd_runner)?;
541505
if success {
542506
done += 1;
507+
progresses[exercise_ind] = ExerciseCheckProgress::Done;
543508
} else {
544509
pending += 1;
545510
if first_pending_exercise_ind.is_none() {
546511
first_pending_exercise_ind = Some(exercise_ind);
547512
}
513+
progresses[exercise_ind] = ExerciseCheckProgress::Pending;
548514
}
549515
self.set_status(exercise_ind, success)?;
550516

551-
stdout.write_all(b"\r")?;
552-
progress_bar_with_success(
553-
stdout,
554-
u16::from(pending + done < n_exercises),
555-
pending,
556-
done,
557-
n_exercises,
558-
term_width,
559-
)?;
560-
stdout.flush()?;
517+
show_exercises_check_progress(stdout, &progresses, term_width)?;
561518
}
562519
}
563520
}
564521

565522
self.write()?;
566-
stdout.write_all(b"\n\n")?;
523+
stdout.write_all(b"\n")?;
567524

568525
Ok(first_pending_exercise_ind)
569526
}
570527

528+
// Return the exercise index of the first pending exercise found.
529+
pub fn check_all_exercises(&mut self, stdout: &mut StdoutLock) -> Result<Option<usize>> {
530+
stdout.queue(cursor::Hide)?;
531+
let res = self.check_all_exercises_impl(stdout);
532+
stdout.queue(cursor::Show)?;
533+
534+
res
535+
}
536+
571537
/// Mark the current exercise as done and move on to the next pending exercise if one exists.
572538
/// If all exercises are marked as done, run all of them to make sure that they are actually
573539
/// done. If an exercise which is marked as done fails, mark it as pending and continue on it.

src/main.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
use anyhow::{bail, Context, Result};
22
use app_state::StateFileStatus;
33
use clap::{Parser, Subcommand};
4-
use crossterm::{
5-
style::{Color, Print, ResetColor, SetForegroundColor},
6-
QueueableCommand,
7-
};
84
use std::{
95
io::{self, IsTerminal, Write},
106
path::Path,
@@ -157,12 +153,13 @@ fn main() -> Result<ExitCode> {
157153

158154
let pending = app_state.n_pending();
159155
if pending == 1 {
160-
stdout.queue(Print("One exercise pending: "))?;
156+
stdout.write_all(b"One exercise pending: ")?;
161157
} else {
162-
stdout.queue(SetForegroundColor(Color::Red))?;
163-
write!(stdout, "{pending}")?;
164-
stdout.queue(ResetColor)?;
165-
stdout.queue(Print(" exercises are pending. The first: "))?;
158+
write!(
159+
stdout,
160+
"{pending}/{} exercises are pending. The first: ",
161+
app_state.exercises().len(),
162+
)?;
166163
}
167164
app_state
168165
.current_exercise()

0 commit comments

Comments
 (0)