Start a spinner with `label`. Drop it (or call `done`) to stop.
(label: &str)
| 115 | |
| 116 | /// Start a spinner with `label`. Drop it (or call `done`) to stop. |
| 117 | pub fn spinner(label: &str) -> Spinner { |
| 118 | let stop = Arc::new(AtomicBool::new(false)); |
| 119 | // No animation when colors are off or stderr is redirected; just announce the step. |
| 120 | if plain() || !std::io::stderr().is_terminal() { |
| 121 | eprintln!(" {label}..."); |
| 122 | return Spinner { stop, handle: None }; |
| 123 | } |
| 124 | let stop_thread = stop.clone(); |
| 125 | let label = label.to_string(); |
| 126 | let handle = thread::spawn(move || { |
| 127 | let mut i = 0usize; |
| 128 | while !stop_thread.load(Ordering::Relaxed) { |
| 129 | let frame = SPIN_FRAMES[i % SPIN_FRAMES.len()]; |
| 130 | eprint!("\r {} {}", frame, label.dimmed()); |
| 131 | let _ = std::io::stderr().flush(); |
| 132 | thread::sleep(Duration::from_millis(120)); |
| 133 | i += 1; |
| 134 | } |
| 135 | // ANSI: carriage return + erase-to-end-of-line so the next print starts clean. |
| 136 | eprint!("\r\x1b[2K"); |
| 137 | let _ = std::io::stderr().flush(); |
| 138 | }); |
| 139 | Spinner { stop, handle: Some(handle) } |
| 140 | } |
| 141 | |
| 142 | impl Spinner { |
| 143 | /// Stop the spinner and print a `(successful) {msg}` line in green. |