vtcode_core/ui/
spinner.rs1use indicatif::{ProgressBar, ProgressStyle};
4use std::time::Duration;
5
6pub struct Spinner {
8 pb: ProgressBar,
9}
10
11impl Spinner {
12 pub fn new(message: &str) -> Self {
14 let pb = ProgressBar::new_spinner();
15 pb.set_style(
16 ProgressStyle::with_template("{spinner:.green} {msg}")
17 .unwrap()
18 .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"),
19 );
20 pb.set_message(message.to_string());
21 pb.enable_steady_tick(Duration::from_millis(100));
22 pb.tick(); Self { pb }
25 }
26
27 pub fn new_download_style(message: &str, total_size: u64) -> Self {
29 let pb = ProgressBar::new(total_size);
30 pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})")
31 .unwrap()
32 .progress_chars("#>-"));
33 pb.set_message(message.to_string());
34
35 Self { pb }
36 }
37
38 pub fn set_message(&self, message: &str) {
40 self.pb.set_message(message.to_string());
41 }
42
43 pub fn set_position(&self, pos: u64) {
45 self.pb.set_position(pos);
46 }
47
48 pub fn set_length(&self, len: u64) {
50 self.pb.set_length(len);
51 }
52
53 pub fn finish_with_message(&self, message: &str) {
55 self.pb.finish_with_message(message.to_string());
56 }
57
58 pub fn finish_and_clear(&self) {
60 self.pb.finish_and_clear();
61 }
62
63 pub fn finish_with_error(&self, message: &str) {
65 self.pb.abandon_with_message(format!("❌ {}", message));
66 }
67
68 pub fn clone_inner(&self) -> ProgressBar {
70 self.pb.clone()
71 }
72
73 pub fn new_async(message: &str) -> Self {
75 let pb = ProgressBar::new_spinner();
76 pb.set_style(
77 ProgressStyle::with_template("{spinner:.green} {msg}")
78 .unwrap()
79 .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"),
80 );
81 pb.set_message(message.to_string());
82 pb.enable_steady_tick(Duration::from_millis(100));
83 pb.tick(); Self { pb }
86 }
87
88 pub fn spawn_async(self) -> tokio::task::JoinHandle<()> {
90 tokio::spawn(async move {
91 loop {
93 tokio::time::sleep(Duration::from_millis(100)).await;
94 }
96 })
97 }
98}
99
100pub fn show_loading_spinner(message: &str) -> Spinner {
103 let spinner = Spinner::new(message);
104 spinner.pb.tick();
105 spinner
106}
107
108pub fn start_loading_spinner(message: &str) -> Spinner {
111 let spinner = Spinner::new(message);
112 spinner.pb.tick();
113 spinner
114}
115
116pub fn start_download_spinner(message: &str, total_size: u64) -> Spinner {
119 let spinner = Spinner::new_download_style(message, total_size);
120 spinner.pb.tick();
121 spinner
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use std::thread;
128 use std::time::Duration;
129
130 #[test]
131 fn test_spinner_creation() {
132 let spinner = Spinner::new("Testing spinner");
133 assert!(spinner.clone_inner().length().is_none());
134 spinner.finish_and_clear();
135 }
136
137 #[test]
138 fn test_show_loading_spinner() {
139 let spinner = show_loading_spinner("Test message");
140 thread::sleep(Duration::from_millis(200));
141 spinner.finish_with_message("Done");
142 }
143
144 #[test]
145 fn test_download_spinner() {
146 let spinner = start_download_spinner("Downloading", 100);
147 spinner.set_position(50);
148 thread::sleep(Duration::from_millis(200));
149 spinner.finish_with_message("Download complete");
150 }
151}