canic_host/terminal/
activity.rs1use super::TerminalStyle;
8use std::{
9 io::{self, Write},
10 sync::{
11 Arc,
12 atomic::{AtomicBool, Ordering},
13 },
14 thread::{self, JoinHandle},
15 time::{Duration, Instant},
16};
17
18const FRAMES: [&str; 4] = ["|", "/", "-", "\\"];
19const FRAME_INTERVAL: Duration = Duration::from_millis(120);
20
21pub struct TerminalActivity {
22 stop: Arc<AtomicBool>,
23 handle: Option<JoinHandle<()>>,
24 interactive: bool,
25}
26
27impl TerminalActivity {
28 #[must_use]
29 pub fn start(message: impl Into<String>) -> Self {
30 let message = message.into();
31 let style = TerminalStyle::detected();
32 if !style.interactive() {
33 println!(" .. {message}");
34 return Self {
35 stop: Arc::new(AtomicBool::new(true)),
36 handle: None,
37 interactive: false,
38 };
39 }
40
41 let stop = Arc::new(AtomicBool::new(false));
42 let worker_stop = Arc::clone(&stop);
43 let handle = thread::spawn(move || {
44 let started_at = Instant::now();
45 let mut frame = 0;
46 while !worker_stop.load(Ordering::Acquire) {
47 let elapsed = started_at.elapsed().as_secs();
48 print!(
49 "\r\u{1b}[2K {} {} {}",
50 style.warning(FRAMES[frame % FRAMES.len()]),
51 message,
52 style.muted(&format!("{elapsed}s"))
53 );
54 let _ = io::stdout().flush();
55 frame += 1;
56 thread::park_timeout(FRAME_INTERVAL);
57 }
58 });
59
60 Self {
61 stop,
62 handle: Some(handle),
63 interactive: true,
64 }
65 }
66
67 pub fn finish(mut self) {
68 self.stop_and_clear();
69 }
70
71 fn stop_and_clear(&mut self) {
72 self.stop.store(true, Ordering::Release);
73 if let Some(handle) = self.handle.take() {
74 handle.thread().unpark();
75 let _ = handle.join();
76 }
77 if self.interactive {
78 print!("\r\u{1b}[2K");
79 let _ = io::stdout().flush();
80 }
81 }
82}
83
84impl Drop for TerminalActivity {
85 fn drop(&mut self) {
86 self.stop_and_clear();
87 }
88}