Async-first terminal UI spinners and progress bars
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102//! Per-row rendering helper.//!//! A [`Line`] holds the theme bits used to render one terminal row (bar, bar width, layout) and a//! reusable buffer. It only fills the buffer — callers (standalone wrappers, [`Group`]s) own the//! actual stdout writes and cursor management.
use std::io::Write;use std::time::Duration;
use owo_colors::Style;
use crate::bar::Bar;use crate::layout::{Layout, RenderContext};use crate::progressive::Progressive;use crate::term::{self, clear_line};use crate::Theme;
/// Per-frame rendering inputs that are not part of [`Progressive`]. The spinner character, the/// elapsed time, and style overrides.pub(crate) struct FrameContext { pub spinner_char: Option<char>, pub elapsed: Duration, pub show_elapsed: bool, pub spinner_style: Style, pub annotation_style: Style,}
pub(crate) struct Line<'a> { bar: Bar<'a>, bar_width: usize, layout: Layout, render_buf: String,}
impl<'a> Line<'a> { pub(crate) fn new(theme: &Theme<'a>) -> Self { Self { bar: theme.bar.clone(), bar_width: theme.effective_bar_width(), layout: theme.layout.clone(), render_buf: String::new(), } }
/// Render `item` together with `frame` into this line's internal buffer and return a borrowed /// view of the rendered bytes. The buffer is cleared before each render. pub(crate) fn render_into<'b, P: Progressive<'b> + ?Sized>( &mut self, item: &P, frame: &FrameContext, ) -> &str { let ctx = RenderContext { spinner: frame.spinner_char, elapsed: frame.elapsed, show_elapsed: frame.show_elapsed, bar: &self.bar, bar_width: self.bar_width, progress: item.progress(), bytes_done: item.bytes_done(), bytes_total: item.bytes_total(), rate: item.rate(), label: item.label(), message: item.message(), spinner_style: frame.spinner_style, annotation_style: frame.annotation_style, }; self.render_buf.clear(); self.layout.render(&ctx, &mut self.render_buf); &self.render_buf }
/// Render `item` and write the result to stdout as the single line of a standalone wrapper: /// hide cursor, clear current line, write content, flush. No-op when `is_tty` is false. pub(crate) fn standalone_render<'b, P: Progressive<'b> + ?Sized>( &mut self, item: &P, frame: &FrameContext, is_tty: bool, ) { if !is_tty { return; } let _ = self.render_into(item, frame); let mut stdout = std::io::stdout().lock(); let _ = stdout.write_all(term::HIDE_CURSOR); let _ = clear_line(&mut stdout); let _ = stdout.write_all(self.render_buf.as_bytes()); let _ = stdout.flush(); }
/// Clear the current line. Used by a standalone wrapper when its work completes; cursor /// restoration happens via the wrapper's [`CursorGuard`](crate::term::CursorGuard). pub(crate) fn standalone_clear(is_tty: bool) { if !is_tty { return; } let mut stdout = std::io::stdout().lock(); let _ = clear_line(&mut stdout); let _ = stdout.flush(); }}