1use clap::builder::styling::Style as AnsiStyle;
8use std::io::{self, IsTerminal, Write};
9
10pub(crate) struct Spinner {
12 chars: [char; 10],
13 index: usize,
14 message: &'static str,
15 started: bool,
16 enabled: bool,
17}
18
19impl Spinner {
20 #[must_use]
22 pub fn new(message: &'static str) -> Self {
23 Self {
24 chars: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
25 index: 0,
26 message,
27 started: false,
28 enabled: io::stderr().is_terminal(),
29 }
30 }
31
32 fn start(&mut self) {
35 if self.enabled && !self.started {
36 let mut stderr = io::stderr();
37 let _ = write!(stderr, "\x1B[?25l");
38 let _ = stderr.flush();
39 self.started = true;
40 self.tick_internal();
41 }
42 }
43
44 pub fn tick(&mut self) {
47 if !self.enabled {
48 return;
49 }
50 if self.started {
51 self.index += 1;
52 self.tick_internal();
53 } else {
54 self.start();
55 }
56 }
57
58 fn tick_internal(&self) {
59 if !self.enabled || !self.started {
60 return;
61 }
62 let mut stderr = io::stderr();
63 let green = AnsiStyle::new().bold();
64 let spinner_char = self.chars[self.index % self.chars.len()];
65 let _ = write!(stderr, "\r{green}{spinner_char}{green:#} {}", self.message);
66 let _ = stderr.flush();
67 }
68
69 pub fn finish(&self) {
71 if self.enabled && self.started {
72 let mut stderr = io::stderr();
73 let len_to_clear = 1 + 1 + self.message.len();
74 let _ = write!(stderr, "\r{:len$}\r\x1B[?25h", " ", len = len_to_clear);
75 let _ = stderr.flush();
76 }
77 }
78}
79
80impl Drop for Spinner {
81 fn drop(&mut self) {
82 self.finish();
83 }
84}