1use crate::console::{Console, ConsoleOptions};
14use crate::measure::Measurement;
15use crate::protocol::Renderable;
16use crate::segment::Segment;
17use crate::spinner::Spinner;
18use crate::style::StyleType;
19
20pub struct Status {
22 status: String,
23 spinner_style: StyleType,
24 speed: f64,
25 spinner: Spinner,
26}
27
28impl Status {
29 pub fn new(message: impl Into<String>) -> Self {
32 let status = message.into();
33 let spinner_style = StyleType::Name("status.spinner".to_string());
34 Status {
35 spinner: Spinner::new("dots")
36 .text(status.clone())
37 .style(spinner_style.clone()),
38 status,
39 spinner_style,
40 speed: 1.0,
41 }
42 }
43
44 fn rebuild(mut self, name: &str) -> Self {
45 self.spinner = Spinner::new(name)
46 .text(self.status.clone())
47 .style(self.spinner_style.clone())
48 .speed(self.speed);
49 self
50 }
51
52 pub fn spinner(self, name: &str) -> Self {
54 self.rebuild(name)
55 }
56
57 pub fn spinner_style(mut self, style: impl Into<StyleType>) -> Self {
59 self.spinner_style = style.into();
60 self.spinner = self.spinner.style(self.spinner_style.clone());
61 self
62 }
63
64 pub fn speed(mut self, speed: f64) -> Self {
66 self.speed = speed;
67 self.spinner = self.spinner.speed(speed);
68 self
69 }
70
71 pub fn update(
76 &mut self,
77 status: Option<&str>,
78 spinner: Option<&str>,
79 spinner_style: Option<StyleType>,
80 speed: Option<f64>,
81 ) {
82 if let Some(status) = status {
83 self.status = status.to_string();
84 }
85 if let Some(style) = spinner_style {
86 self.spinner_style = style;
87 }
88 if let Some(speed) = speed.filter(|s| *s != 0.0) {
89 self.speed = speed;
90 }
91 if let Some(name) = spinner {
92 self.spinner = Spinner::new(name)
93 .text(self.status.clone())
94 .style(self.spinner_style.clone())
95 .speed(self.speed);
96 } else {
97 self.spinner.update(
98 Some(&self.status),
99 Some(self.spinner_style.clone()),
100 Some(self.speed),
101 );
102 }
103 }
104
105 pub fn renderable(&self) -> &Spinner {
107 &self.spinner
108 }
109}
110
111impl Renderable for Status {
114 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
115 self.spinner.rich_render(console, options)
116 }
117
118 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
119 self.spinner.measure(console, options)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::color::ColorSystem;
127
128 fn render(status: &Status) -> String {
129 Console::builder()
130 .force_terminal(true)
131 .color_system(Some(ColorSystem::Truecolor))
132 .width(30)
133 .no_color(false)
134 .build()
135 .render_to_string(status)
136 }
137
138 #[test]
139 fn default_status_frame() {
140 assert_eq!(
142 render(&Status::new("Loading data")),
143 "\x1b[32m⠋\x1b[0m Loading data"
144 );
145 }
146
147 #[test]
148 fn custom_spinner() {
149 assert_eq!(
150 render(&Status::new("Building").spinner("line")),
151 "\x1b[32m-\x1b[0m Building"
152 );
153 }
154}