//! Terminal presentation: colour, emphasis, and a live spinner while a //! command works. Everything degrades to plain text when stdout is not a //! terminal, when `NO_COLOR` is set, or with `--plain`. use console::{style, StyledObject, Term}; use indicatif::{ProgressBar, ProgressStyle}; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; static PLAIN: AtomicBool = AtomicBool::new(false); static INIT: std::sync::Once = std::sync::Once::new(); /// Decide once whether to use colour and animation. pub fn init(plain_flag: bool) { INIT.call_once(|| { let no_color = std::env::var_os("NO_COLOR").is_some(); let tty = Term::stdout().features().is_attended(); let plain = plain_flag || no_color || !tty; PLAIN.store(plain, Ordering::Relaxed); console::set_colors_enabled(!plain); }); } pub fn plain() -> bool { PLAIN.load(Ordering::Relaxed) } fn s(v: T) -> StyledObject { style(v) } /// A ChangeId prefix. pub fn id(v: T) -> String { s(v).cyan().bold().to_string() } /// A hash prefix. pub fn hash(v: T) -> String { s(v).dim().to_string() } pub fn path(v: T) -> String { s(v).bold().to_string() } pub fn ok(v: T) -> String { s(v).green().to_string() } pub fn warn(v: T) -> String { s(v).yellow().to_string() } pub fn err(v: T) -> String { s(v).red().bold().to_string() } pub fn dim(v: T) -> String { s(v).dim().to_string() } pub fn heading(v: T) -> String { s(v).bold().underlined().to_string() } /// A status tag: green for additions, yellow for modifications, red for removals and conflicts. pub fn tag(t: &str) -> String { match t { "A" => s(t).green().bold().to_string(), "M" | "X" => s(t).yellow().bold().to_string(), "D" | "C!" => s(t).red().bold().to_string(), "R" => s(t).cyan().bold().to_string(), other => other.to_string(), } } /// A live spinner with a message, shown only on a terminal. Drop or call /// `done` to clear it; `done` prints the final line in its place. pub struct Spinner { bar: Option, } pub fn spinner(msg: &str) -> Spinner { if plain() { return Spinner { bar: None }; } let bar = ProgressBar::new_spinner(); bar.set_style(ProgressStyle::with_template("{spinner:.cyan} {msg}").unwrap().tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏", "✔"])); bar.set_message(msg.to_string()); bar.enable_steady_tick(Duration::from_millis(80)); Spinner { bar: Some(bar) } } impl Spinner { /// Replace the spinner with a final line (or print it plainly). pub fn done(self, msg: &str) { match &self.bar { Some(b) => b.finish_with_message(msg.to_string()), None => println!("{msg}"), } } /// Clear the spinner without printing. pub fn clear(self) { if let Some(b) = &self.bar { b.finish_and_clear(); } } }