composition_cli/display/
spinner.rs1use std::{
2 io::{self, Write},
3 sync::{
4 Arc, Mutex,
5 atomic::{AtomicBool, Ordering},
6 },
7 thread::{self, JoinHandle},
8 time::Duration,
9};
10
11static SPINNER: Mutex<Option<SpinnerState>> = Mutex::new(None);
12
13struct SpinnerState {
14 running: Arc<AtomicBool>,
15 handle: Option<JoinHandle<()>>,
16}
17
18pub fn start(message: &str) {
19 let mut spinner_guard = SPINNER.lock().unwrap();
20
21 if spinner_guard.is_some() {
22 drop(spinner_guard);
23 end();
24 spinner_guard = SPINNER.lock().unwrap();
25 }
26
27 let running = Arc::new(AtomicBool::new(true));
28 let running_clone = running.clone();
29 let message = message.to_string();
30
31 let handle = thread::spawn(move || {
32 let spinner_chars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
33 let mut index = 0;
34
35 while running_clone.load(Ordering::SeqCst) {
36 print!("\r{} {}", spinner_chars[index], message);
37 io::stdout().flush().unwrap();
38 index = (index + 1) % spinner_chars.len();
39 thread::sleep(Duration::from_millis(100));
40 }
41
42 print!("\r{} \r", " ".repeat(message.len() + 2));
43 io::stdout().flush().unwrap();
44 });
45
46 *spinner_guard = Some(SpinnerState {
47 running,
48 handle: Some(handle),
49 });
50}
51
52pub fn end() {
53 let mut spinner_guard = SPINNER.lock().unwrap();
54
55 if let Some(state) = spinner_guard.take() {
56 state.running.store(false, Ordering::SeqCst);
57
58 if let Some(handle) = state.handle {
59 drop(spinner_guard); let _ = handle.join();
61 }
62 }
63}