use indicatif::{ProgressBar, ProgressStyle};
use std::io::Write;
use std::time::{Duration, Instant};
const SPINNER_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
pub struct ToolSpinner {
bar: ProgressBar,
created_at: Instant,
finished: bool, }
const MIN_DISPLAY_MS: u64 = 100;
impl ToolSpinner {
pub fn new(msg: &str) -> Self {
let bar = ProgressBar::new_spinner();
bar.set_style(
ProgressStyle::with_template("{spinner:.cyan} {msg}")
.unwrap_or_else(|_| ProgressStyle::default_spinner())
.tick_strings(SPINNER_FRAMES),
);
bar.set_message(msg.to_string());
bar.enable_steady_tick(Duration::from_millis(80));
bar.tick();
let _ = std::io::stdout().flush();
Self { bar, created_at: Instant::now(), finished: false }
}
pub fn finish(&mut self, msg: &str) {
let elapsed = self.created_at.elapsed();
if elapsed < Duration::from_millis(MIN_DISPLAY_MS) {
std::thread::sleep(Duration::from_millis(MIN_DISPLAY_MS) - elapsed);
}
self.bar.finish_and_clear();
self.finished = true;
println!(" ✓ {}", msg);
}
pub fn finish_success(&mut self, msg: &str) {
self.finish(msg);
}
pub fn finish_error(&mut self, msg: &str) {
let elapsed = self.created_at.elapsed();
if elapsed < Duration::from_millis(MIN_DISPLAY_MS) {
std::thread::sleep(Duration::from_millis(MIN_DISPLAY_MS) - elapsed);
}
self.bar.finish_and_clear();
self.finished = true;
println!(" ✗ {}", msg);
}
pub fn finish_clear(&mut self) {
let elapsed = self.created_at.elapsed();
if elapsed < Duration::from_millis(MIN_DISPLAY_MS) {
std::thread::sleep(Duration::from_millis(MIN_DISPLAY_MS) - elapsed);
}
self.bar.finish_and_clear();
self.finished = true;
}
pub fn finish_clear_immediate(&mut self) {
self.bar.finish_and_clear();
self.finished = true;
}
pub fn set_message(&self, msg: &str) {
self.bar.set_message(msg.to_string());
}
pub fn bar(&self) -> &ProgressBar {
&self.bar
}
}
impl Drop for ToolSpinner {
fn drop(&mut self) {
if !self.finished {
self.bar.finish_and_clear();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spinner_can_be_created_and_finished() {
let mut spinner = ToolSpinner::new("test operation");
spinner.finish("done");
}
#[test]
fn spinner_success_format() {
let mut spinner = ToolSpinner::new("test");
spinner.finish_success("completed");
}
#[test]
fn spinner_error_format() {
let mut spinner = ToolSpinner::new("test");
spinner.finish_error("failed");
}
}