Skip to main content

rskit_cli/
progress.rs

1//! Progress bar abstractions over `indicatif`.
2//!
3//! Provides preset styles for common CLI progress patterns.
4
5use indicatif::{
6    MultiProgress as IndicatifMultiProgress, ProgressBar as IndicatifBar,
7    ProgressStyle as IndicatifStyle,
8};
9use std::time::Duration;
10
11/// Re-export raw type so binaries can create shared instances for writer integration.
12pub use indicatif::MultiProgress as RawMultiProgress;
13
14/// Preset progress bar styles.
15pub enum ProgressStyle {
16    /// Standard bar with count and percentage.
17    Bar,
18    /// Spinner for indeterminate operations.
19    Spinner,
20    /// Download-style with bytes.
21    Download,
22    /// Finished bar — just shows prefix + message, no bar/timer.
23    Finished,
24}
25
26impl ProgressStyle {
27    fn to_indicatif(&self) -> IndicatifStyle {
28        match self {
29            ProgressStyle::Bar => style_or_default(
30                "{prefix:.bold} {wide_bar:.cyan/dim} {pos}/{len} {percent}% {elapsed}",
31                IndicatifStyle::default_bar,
32            )
33            .progress_chars("━╸─"),
34
35            ProgressStyle::Spinner => style_or_default(
36                "{spinner:.green} {prefix} {wide_msg}",
37                IndicatifStyle::default_spinner,
38            ),
39
40            ProgressStyle::Download => style_or_default(
41                "{prefix:.bold} {wide_bar:.green/dim} {bytes}/{total_bytes} {bytes_per_sec} {eta}",
42                IndicatifStyle::default_bar,
43            )
44            .progress_chars("━╸─"),
45
46            ProgressStyle::Finished => {
47                style_or_default("{prefix} {wide_msg}", IndicatifStyle::default_spinner)
48            }
49        }
50    }
51}
52
53fn style_or_default(template: &str, fallback: impl FnOnce() -> IndicatifStyle) -> IndicatifStyle {
54    IndicatifStyle::with_template(template).unwrap_or_else(|_| fallback())
55}
56
57/// A single progress bar wrapping `indicatif`.
58pub struct ProgressBar {
59    inner: IndicatifBar,
60}
61
62impl ProgressBar {
63    /// Create a new progress bar with the given total and style.
64    pub fn new(total: u64, style: ProgressStyle) -> Self {
65        let bar = IndicatifBar::new(total);
66        bar.set_style(style.to_indicatif());
67        bar.enable_steady_tick(Duration::from_millis(100));
68        Self { inner: bar }
69    }
70
71    /// Create a spinner (indeterminate progress).
72    pub fn spinner() -> Self {
73        let bar = IndicatifBar::new_spinner();
74        bar.set_style(ProgressStyle::Spinner.to_indicatif());
75        bar.enable_steady_tick(Duration::from_millis(80));
76        Self { inner: bar }
77    }
78
79    /// Set the prefix text (typically appears before the bar).
80    pub fn set_prefix(&self, prefix: impl Into<String>) {
81        self.inner.set_prefix(prefix.into());
82    }
83
84    /// Change the bar style.
85    pub fn set_style(&self, style: ProgressStyle) {
86        self.inner.set_style(style.to_indicatif());
87    }
88
89    /// Set the message text.
90    pub fn set_message(&self, msg: impl Into<String>) {
91        self.inner.set_message(msg.into());
92    }
93
94    /// Enable steady tick (auto-redraw) at the given interval.
95    pub fn enable_steady_tick(&self, interval: Duration) {
96        self.inner.enable_steady_tick(interval);
97    }
98
99    /// Trigger a redraw without changing position.
100    pub fn tick(&self) {
101        self.inner.tick();
102    }
103
104    /// Set current position.
105    pub fn set_position(&self, pos: u64) {
106        self.inner.set_position(pos);
107    }
108
109    /// Increment position by delta.
110    pub fn inc(&self, delta: u64) {
111        self.inner.inc(delta);
112    }
113
114    /// Set total length.
115    pub fn set_length(&self, len: u64) {
116        self.inner.set_length(len);
117    }
118
119    /// Mark as finished.
120    pub fn finish(&self) {
121        self.inner.finish();
122    }
123
124    /// Mark as finished with a final message (bar stays visible).
125    pub fn finish_with_message(&self, msg: impl Into<std::borrow::Cow<'static, str>>) {
126        self.inner.finish_with_message(msg);
127    }
128
129    /// Reset the bar to its initial state (position 0, not finished).
130    pub fn reset(&self) {
131        self.inner.reset();
132    }
133
134    /// Finish and clear from display.
135    pub fn finish_and_clear(&self) {
136        self.inner.finish_and_clear();
137    }
138
139    /// Access the underlying indicatif bar.
140    pub fn inner(&self) -> &IndicatifBar {
141        &self.inner
142    }
143}
144
145/// Multi-progress bar manager for concurrent operations.
146pub struct MultiProgress {
147    inner: IndicatifMultiProgress,
148}
149
150impl MultiProgress {
151    #[must_use]
152    pub fn new() -> Self {
153        Self {
154            inner: IndicatifMultiProgress::new(),
155        }
156    }
157
158    /// Wrap an existing `indicatif::MultiProgress`.
159    #[must_use]
160    pub fn from_raw(mp: IndicatifMultiProgress) -> Self {
161        Self { inner: mp }
162    }
163
164    /// Access the underlying `indicatif::MultiProgress`.
165    pub fn raw(&self) -> &IndicatifMultiProgress {
166        &self.inner
167    }
168
169    /// Add a bar-style progress with the given prefix and total.
170    pub fn add_bar(&self, prefix: impl Into<String>, total: u64) -> ProgressBar {
171        let bar = IndicatifBar::new(total);
172        bar.set_style(ProgressStyle::Bar.to_indicatif());
173        bar.set_prefix(prefix.into());
174        let bar = self.inner.add(bar);
175        ProgressBar { inner: bar }
176    }
177
178    /// Add a bar inserted after another bar (keeps ordering stable).
179    pub fn insert_bar_after(
180        &self,
181        after: &ProgressBar,
182        prefix: impl Into<String>,
183        total: u64,
184    ) -> ProgressBar {
185        let bar = IndicatifBar::new(total);
186        bar.set_style(ProgressStyle::Bar.to_indicatif());
187        bar.set_prefix(prefix.into());
188        let bar = self.inner.insert_after(after.inner(), bar);
189        ProgressBar { inner: bar }
190    }
191
192    /// Add a spinner-style progress.
193    pub fn add_spinner(&self, prefix: impl Into<String>) -> ProgressBar {
194        let bar = IndicatifBar::new_spinner();
195        bar.set_style(ProgressStyle::Spinner.to_indicatif());
196        bar.set_prefix(prefix.into());
197        bar.enable_steady_tick(Duration::from_millis(120));
198        let bar = self.inner.add(bar);
199        ProgressBar { inner: bar }
200    }
201
202    /// Add a static text line (no animation, no bar).
203    pub fn add_static_line(
204        &self,
205        prefix: impl Into<String>,
206        msg: impl Into<std::borrow::Cow<'static, str>>,
207    ) -> ProgressBar {
208        let bar = IndicatifBar::new(0);
209        bar.set_style(ProgressStyle::Finished.to_indicatif());
210        bar.set_prefix(prefix.into());
211        bar.finish_with_message(msg);
212        let bar = self.inner.add(bar);
213        ProgressBar { inner: bar }
214    }
215
216    /// Print a line above the progress bars (for completed items).
217    pub fn println(&self, msg: impl AsRef<str>) -> std::io::Result<()> {
218        self.inner.println(msg)
219    }
220
221    /// Remove a progress bar from the display.
222    pub fn remove(&self, bar: &ProgressBar) {
223        self.inner.remove(bar.inner());
224    }
225
226    /// Clear all bars.
227    pub fn clear(&self) -> std::io::Result<()> {
228        self.inner.clear()
229    }
230}
231
232impl Default for MultiProgress {
233    fn default() -> Self {
234        Self::new()
235    }
236}