[feat] add TTY-aware progress management with indicatif and file-specific progress bars
This commit is contained in:
89
src/lib.rs
89
src/lib.rs
@@ -217,6 +217,95 @@ pub mod ui {
|
||||
io::stdin().read_line(&mut s)?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
// Progress manager built on indicatif MultiProgress for per-file and aggregate bars
|
||||
/// TTY-aware progress UI built on `indicatif` for per-file and aggregate progress bars.
|
||||
///
|
||||
/// This small helper encapsulates a `MultiProgress` with one aggregate (total) bar and
|
||||
/// one per-file bar. It is intentionally minimal to keep integration lightweight.
|
||||
pub mod progress {
|
||||
use atty::Stream;
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
|
||||
/// Manages a set of per-file progress bars plus a top aggregate bar.
|
||||
pub struct ProgressManager {
|
||||
enabled: bool,
|
||||
mp: Option<MultiProgress>,
|
||||
per: Vec<ProgressBar>,
|
||||
total: Option<ProgressBar>,
|
||||
total_n: usize,
|
||||
completed: usize,
|
||||
done: Vec<bool>,
|
||||
}
|
||||
|
||||
impl ProgressManager {
|
||||
/// Create a new manager with the given enabled flag.
|
||||
pub fn new(enabled: bool) -> Self {
|
||||
Self { enabled, mp: None, per: Vec::new(), total: None, total_n: 0, completed: 0, done: Vec::new() }
|
||||
}
|
||||
|
||||
/// Create a manager that enables bars when `n > 1`, stderr is a TTY, and not quiet.
|
||||
pub fn default_for_files(n: usize) -> Self {
|
||||
let enabled = n > 1 && atty::is(Stream::Stderr) && !crate::is_quiet();
|
||||
Self::new(enabled)
|
||||
}
|
||||
|
||||
/// Initialize bars for the given file labels. If disabled or single file, no-op.
|
||||
pub fn init_files(&mut self, labels: &[String]) {
|
||||
self.total_n = labels.len();
|
||||
if !self.enabled || self.total_n <= 1 {
|
||||
// No bars in single-file mode or when disabled
|
||||
self.enabled = false;
|
||||
return;
|
||||
}
|
||||
let mp = MultiProgress::new();
|
||||
// Aggregate bar at the top
|
||||
let total = mp.add(ProgressBar::new(labels.len() as u64));
|
||||
total.set_style(ProgressStyle::with_template("{prefix} [{bar:40.cyan/blue}] {pos}/{len}")
|
||||
.unwrap()
|
||||
.progress_chars("=>-"));
|
||||
total.set_prefix("Total");
|
||||
self.total = Some(total);
|
||||
// Per-file bars
|
||||
for label in labels {
|
||||
let pb = mp.add(ProgressBar::new(100));
|
||||
pb.set_style(ProgressStyle::with_template("{prefix} [{bar:40.green/black}] {pos}% {msg}")
|
||||
.unwrap()
|
||||
.progress_chars("=>-"));
|
||||
pb.set_position(0);
|
||||
pb.set_prefix(label.clone());
|
||||
self.per.push(pb);
|
||||
}
|
||||
self.mp = Some(mp);
|
||||
}
|
||||
|
||||
/// Returns true when bars are enabled (multi-file TTY mode).
|
||||
pub fn is_enabled(&self) -> bool { self.enabled }
|
||||
|
||||
/// Get a clone of the per-file progress bar at index, if enabled.
|
||||
pub fn per_bar(&self, idx: usize) -> Option<ProgressBar> {
|
||||
if !self.enabled { return None; }
|
||||
self.per.get(idx).cloned()
|
||||
}
|
||||
|
||||
/// Get a clone of the aggregate (total) progress bar, if enabled.
|
||||
pub fn total_bar(&self) -> Option<ProgressBar> {
|
||||
if !self.enabled { return None; }
|
||||
self.total.as_ref().cloned()
|
||||
}
|
||||
|
||||
/// Mark a file as finished (set to 100% and update total counter).
|
||||
pub fn mark_file_done(&mut self, idx: usize) {
|
||||
if !self.enabled { return; }
|
||||
if let Some(pb) = self.per.get(idx) {
|
||||
pb.set_position(100);
|
||||
pb.finish_with_message("done");
|
||||
}
|
||||
self.completed += 1;
|
||||
if let Some(total) = &self.total { total.set_position(self.completed as u64); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Logging macros and helpers
|
||||
|
||||
Reference in New Issue
Block a user