scx_forge_agent/
progress.rs1use std::io::{IsTerminal, Write};
6use std::sync::{
7 atomic::{AtomicBool, Ordering},
8 Arc,
9};
10use std::thread::{self, JoinHandle};
11use std::time::Duration;
12
13use crate::color::Style;
14
15pub struct ProgressSpinner {
16 running: Option<Arc<AtomicBool>>,
17 handle: Option<JoinHandle<()>>,
18}
19
20impl ProgressSpinner {
21 pub fn stdout(label: impl Into<String>, color: Style) -> Self {
22 if !std::io::stdout().is_terminal() {
23 return Self {
24 running: None,
25 handle: None,
26 };
27 }
28
29 let label = label.into();
30 let running = Arc::new(AtomicBool::new(true));
31 let thread_running = running.clone();
32 let handle = thread::spawn(move || {
33 let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
34 let mut i = 0usize;
35 while thread_running.load(Ordering::Relaxed) {
36 print!(
37 "\r{} {}",
38 color.dim(frames[i % frames.len()]),
39 color.dim(&label)
40 );
41 let _ = std::io::stdout().flush();
42 i += 1;
43 thread::sleep(Duration::from_millis(80));
44 }
45 });
46
47 Self {
48 running: Some(running),
49 handle: Some(handle),
50 }
51 }
52
53 pub fn stop(&mut self) {
54 if let Some(running) = self.running.take() {
55 running.store(false, Ordering::Relaxed);
56 }
57 if let Some(handle) = self.handle.take() {
58 let _ = handle.join();
59 print!("\r\x1b[K");
60 let _ = std::io::stdout().flush();
61 }
62 }
63}
64
65impl Drop for ProgressSpinner {
66 fn drop(&mut self) {
67 self.stop();
68 }
69}