5. Progress and live output¶
Progress bars¶
use rich::{Console, Progress, ProgressColumn};
let mut progress = Progress::new().columns(vec![
ProgressColumn::Description,
ProgressColumn::Bar,
ProgressColumn::Percentage,
]);
progress.add_task("Downloading", 100.0, 60.0); // description, total, completed
progress.add_task("Extracting", 100.0, 30.0);
console.print(&progress);
Progress renders a snapshot — it draws the bars at the values you give it.
To animate, update the values and redraw, which is what Live is for.
Progress::new() uses upstream's default columns: description, bar, percentage
and time remaining.
Time, rate and spinner columns¶
add_task returns a TaskId; advance and update move a task along and record
speed samples, exactly as upstream's Progress does. Time-based columns read the
progress clock, which you can replace — handy in tests, where a fixed clock makes
every frame reproducible:
use rich::{Progress, ProgressColumn, TaskUpdate};
use std::sync::{Arc, Mutex};
let now = Arc::new(Mutex::new(0.0_f64));
let clock = now.clone();
let mut progress = Progress::new()
.clock(move || *clock.lock().unwrap())
.columns(vec![
ProgressColumn::spinner(),
ProgressColumn::Description,
ProgressColumn::Bar,
ProgressColumn::Download,
ProgressColumn::TransferSpeed,
ProgressColumn::TimeElapsed,
ProgressColumn::time_remaining(),
]);
let iso = progress.add_task("ubuntu.iso", 4_700_000_000.0, 0.0);
*now.lock().unwrap() = 4.0;
progress.advance(iso, 400_000_000.0);
*now.lock().unwrap() = 8.0;
progress.update(iso, TaskUpdate::default().advance(500_000_000.0));
console.print(&progress); // ⠋ ubuntu.iso ━━━━╸━━━ 0.9/4.7 GB 125.0 MB/s 0:00:08 0:00:31
Columns: Description, Bar, Percentage, TaskProgress { show_speed },
MofN, Download, BinaryDownload, TimeElapsed, TimeRemaining
(ProgressColumn::time_remaining() or TimeRemainingColumn::new(compact,
elapsed_when_finished)), TransferSpeed, FileSize, TotalFileSize,
Spinner (ProgressColumn::spinner() or SpinnerColumn::new(name,
finished_text)), Text(String, Style) for a fixed cell, TextFormat for a
cell formatted from the task, Renderable (upstream's RenderableColumn) for
the same renderable in every row, BarWith(BarColumn) for a bar with its own
width and styles, and WithTableColumn (via ProgressColumn::with_table_column)
for a column with explicit table options. A task with a total of None is
indeterminate and its bar pulses; add_unstarted_task adds one whose clock has
not started.
To redraw on its own, progress.start(console, writer, refresh_per_second)
starts a Live-driven display on a background thread (upstream's
with progress:); .transient(true) clears it when stopped and
.disable(true) draws nothing. For the common case, rich::track(iter,
"Working") wraps an iterator with a live bar on stdout, like upstream's
track(). The progress guide covers these.
Spinners¶
use rich::Spinner;
let spinner = Spinner::new("dots");
let frame = spinner.render(elapsed_seconds);
console.print(&frame);
render takes a time in seconds and returns the frame for that moment. As in
upstream rich, the first render marks the start of the animation, so keep one
spinner for the whole animation rather than building a new one per frame (a new
spinner always starts on its first frame). spinner.update(text, style, speed)
changes the text, style or speed mid-animation; a new speed continues from the
current frame. Spinner::text is console markup.
Live displays¶
Live redraws a renderable in place, moving the cursor back over its previous
output rather than scrolling:
fn live_display() {
// Live redraws a renderable in place. Manual mode: you call update/refresh.
let spinner = Spinner::new("dots").text("counting…");
let mut live = Live::new(Box::new(Text::new("")), Console::new(), std::io::stdout());
live.start();
let started = Instant::now();
for n in 0..20 {
let frame = spinner.render(started.elapsed().as_secs_f64());
live.update(Box::new(frame.append_text(&Text::new(format!(" {n}")))));
std::thread::sleep(Duration::from_millis(50));
}
live.stop(); // leaves the last frame on screen
}
Live::new takes the renderable to show, the console and the writer; start
begins drawing, update swaps in a new renderable, and stop draws the last
frame. Progress and live displays covers
the automatic-refresh mode and Progress on top of it.
One live display at a time
Live owns the cursor while it runs. Printing to the same console from
elsewhere — including from another thread — will interleave with its
redraws and corrupt the display.
A second display started on a clone of the same console is nested
instead, and drawn inside the first one, as in Python rich.
Status¶
Status is a Live with a spinner and a message, for the common
"working on it" case:
use rich::Status;
let mut status = Status::new("Fetching [b]index[/]…");
let frame = status.renderable().render(elapsed_seconds);
status.update(Some("Unpacking…"), None, None, None);
Status::update follows upstream: a new spinner name replaces the spinner and
restarts its animation, while a new message, style or speed updates it in
place.
Next: The CLI →