use crate::ui::constants::{
color_dim, color_failure, color_success, ICON_CRITICAL, ICON_PASS, SPINNER_FRAMES,
};
use crate::ui::utils::is_tty;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::{self, JoinHandle};
pub struct Spinner {
handle: Option<JoinHandle<()>>,
stop: Arc<AtomicBool>,
}
impl Spinner {
pub fn start(message: &str) -> Self {
let stop = Arc::new(AtomicBool::new(false));
let stop_clone = Arc::clone(&stop);
let message = message.to_string();
let handle = if is_tty() {
Some(thread::spawn(move || {
let mut frame = 0;
while !stop_clone.load(Ordering::Relaxed) {
let spinner_char = SPINNER_FRAMES[frame % SPINNER_FRAMES.len()];
eprint!("\r {} {}", spinner_char, message);
frame += 1;
}
eprint!("\r{}", " ".repeat(100));
eprint!("\r");
}))
} else {
eprintln!("{}", message);
None
};
Spinner { handle, stop }
}
pub fn stop_with_success(mut self, message: &str) {
self.stop.store(true, Ordering::Relaxed);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
if is_tty() {
eprintln!(" {} {}", color_success(ICON_PASS), message);
}
}
#[allow(dead_code)]
pub fn stop_with_failure(mut self, message: &str) {
self.stop.store(true, Ordering::Relaxed);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
if is_tty() {
eprintln!(" {} {}", color_failure(ICON_CRITICAL), message);
}
}
#[allow(dead_code)]
pub fn stop_with_message(mut self, message: &str) {
self.stop.store(true, Ordering::Relaxed);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
if is_tty() {
eprintln!(" {}", color_dim(message));
}
}
}
impl Drop for Spinner {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spinner_create() {
let spinner = Spinner::start("Testing...");
spinner.stop_with_message("Done");
}
#[test]
fn test_spinner_success() {
let spinner = Spinner::start("Processing...");
spinner.stop_with_success("Completed successfully");
}
#[test]
fn test_spinner_failure() {
let spinner = Spinner::start("Analyzing...");
spinner.stop_with_failure("Analysis failed");
}
}