use std::io::Write;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use crossterm::style::{Stylize, style};
const WAIT_LABELS: &[&str] = &[
"working",
"running",
"figuring",
"searching",
"assembling",
"parsing",
"resolving",
"mapping",
"tracing",
"scanning",
];
const DOT_FRAMES: &[&str] = &[" ", ". ", ".. ", "..."];
pub struct ActivityIndicator {
active: Arc<AtomicBool>,
handle: Option<tokio::task::JoinHandle<()>>,
}
impl ActivityIndicator {
pub fn start(label: &str) -> Self {
let active = Arc::new(AtomicBool::new(true));
let active_clone = active.clone();
let label = label.to_string();
let handle = tokio::spawn(async move {
let mut frame = 0usize;
let mut phrase_idx = 0usize;
while active_clone.load(Ordering::Relaxed) {
let dots = DOT_FRAMES[frame % DOT_FRAMES.len()];
let phrase = WAIT_LABELS[phrase_idx % WAIT_LABELS.len()];
let status = if label.is_empty() {
format!("{phrase}{dots}")
} else {
format!("{label}{dots}")
};
let color = super::theme::current().muted;
print!("\r{}", style(status).with(color));
let _ = std::io::stdout().flush();
tokio::time::sleep(Duration::from_millis(400)).await;
frame += 1;
if frame.is_multiple_of(DOT_FRAMES.len() * 2) {
phrase_idx += 1;
}
}
print!("\r{}\r", " ".repeat(60));
let _ = std::io::stdout().flush();
});
Self {
active,
handle: Some(handle),
}
}
pub fn thinking() -> Self {
Self::start("")
}
pub fn tool(tool_name: &str) -> Self {
Self::start(&format!("running {tool_name}"))
}
pub fn stop(&self) {
self.active.store(false, Ordering::Relaxed);
}
}
impl Drop for ActivityIndicator {
fn drop(&mut self) {
self.active.store(false, Ordering::Relaxed);
}
}