prune,ui: show the removal pass, prefix the sizing spinner
CI / build (push) Successful in 6m23s
CI / test (push) Skipped
CI / publish (push) Skipped
CI / snap (push) Successful in 11m45s

Confirming a 500 GiB prune went silent under "Removing:" for minutes:
drive an indicatif bar from the plan's sizes through a new
apply_with_progress observer, in the deb live view's layout — the
running count behind the spinner on the first line, the byte bar with
its counters on the second. The removed path is not rendered: it
fights the bar on a narrow terminal, and the failed-removal errors
name the paths that matter. --force plans no sizes and gets the count
line only.

The dry-run size spinner lacked the "> " prompt prefix the other
one-line waits carry; wait_spinner renders it too now.
This commit is contained in:
2026-10-03 01:34:47 +02:00
parent f6f2b882a8
commit 486856da60
4 changed files with 129 additions and 14 deletions
+47 -3
View File
@@ -7,6 +7,7 @@ pub mod deb;
/// yes/no confirmation
pub mod prompt;
use crossterm::terminal;
#[cfg(test)]
use indicatif::ProgressDrawTarget;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
@@ -41,16 +42,59 @@ pub(crate) fn transfer_style() -> ProgressStyle {
}
/// A standalone spinner for one-line waits outside a build view (the
/// prune size pass): braille frames over the message, drawn only on
/// terminals. Callers finish it with [`ProgressBar::finish_and_clear`].
/// prune size pass): braille frames over the message behind the `> `
/// prompt prefix, drawn only on terminals. Callers finish it with
/// [`ProgressBar::finish_and_clear`].
pub fn wait_spinner(msg: &str) -> ProgressBar {
let pb = ProgressBar::new_spinner();
pb.set_style(ProgressStyle::with_template("{spinner:.blue} {msg}").expect("spinner template"));
pb.set_style(
ProgressStyle::with_template("> {spinner:.blue} {msg}").expect("spinner template"),
);
pb.set_message(msg.to_string());
pb.enable_steady_tick(Duration::from_millis(120));
pb
}
/// Width of the terminal in columns, or `None` when it cannot be determined
pub(crate) fn terminal_width() -> Option<usize> {
terminal::size().ok().map(|(cols, _)| cols as usize)
}
/// Removal progress bar of `pkh prune`, in the deb live view's layout:
/// the running count behind the spinner on the first line, the byte bar
/// with its counters on the second when the plan carries sizes (`--force`
/// skips the sizing and gets the count line only). The removed path is
/// deliberately not rendered — it fights the bar on a narrow terminal,
/// and the failed-removal errors name the paths that matter. Drive it
/// with [`removal_progress`]; finish it with
/// [`ProgressBar::finish_and_clear`].
pub fn removal_bar(multi: &MultiProgress, total_bytes: u64) -> ProgressBar {
let pb = multi.add(ProgressBar::new(total_bytes));
let template = if total_bytes > 0 {
"> {spinner:.blue} {prefix}\n [{bar:24.cyan/blue}] {bytes}/{total_bytes}"
} else {
"> {spinner:.blue} {prefix}"
};
pb.set_style(
ProgressStyle::with_template(template)
.expect("removal template")
.progress_chars("=> "),
);
pb.enable_steady_tick(Duration::from_millis(120));
pb
}
/// Advance the removal bar to the `idx`-th of `count` artifact;
/// `done` bytes already freed
pub fn removal_progress(bar: &ProgressBar, idx: usize, count: usize, done: u64) {
bar.set_prefix(format!("Removing {}/{}", idx + 1, count));
if let Some(total) = bar.length()
&& total > 0
{
bar.set_position(done.min(total));
}
}
/// Create a spinner-style progress bar attached to `multi`, returning the bar
/// and a callback compatible with [`crate::ProgressCallback`]
pub fn create_progress_bar(