Skip to main content

cli_ui/prompt/
progress_bar.rs

1#![allow(missing_docs)]
2//! Progress bar — ports `@clack/prompts` `progress()`.
3//!
4//! Built on top of the spinner: the bar is rendered into the spinner's message
5//! line, so it animates the bullet character while the user calls
6//! [`Progress::advance`] from another thread or in a loop.
7
8use super::spinner::{spinner, Spinner};
9use crate::styles::{paint, DIM};
10
11#[derive(Clone, Copy)]
12pub enum Style {
13    Light,
14    Heavy,
15    Block,
16}
17
18impl Style {
19    fn ch(self) -> &'static str {
20        match self {
21            Style::Light => "─",
22            Style::Heavy => "━",
23            Style::Block => "█",
24        }
25    }
26}
27
28pub struct Progress {
29    spin: Spinner,
30    style: Style,
31    size: usize,
32    max: u64,
33    value: std::sync::Mutex<u64>,
34    msg: std::sync::Mutex<String>,
35}
36
37pub fn progress() -> Progress {
38    Progress {
39        spin: spinner(),
40        style: Style::Heavy,
41        size: 40,
42        max: 100,
43        value: std::sync::Mutex::new(0),
44        msg: std::sync::Mutex::new(String::new()),
45    }
46}
47
48impl Progress {
49    pub fn style(mut self, s: Style) -> Self {
50        self.style = s;
51        self
52    }
53    pub fn size(mut self, n: usize) -> Self {
54        self.size = n.max(1);
55        self
56    }
57    pub fn max(mut self, n: u64) -> Self {
58        self.max = n.max(1);
59        self
60    }
61
62    pub fn start(&self, msg: impl Into<String>) {
63        let m = msg.into();
64        *self.msg.lock().unwrap() = m.clone();
65        self.spin.start(self.render(&m));
66    }
67
68    pub fn advance(&self, step: u64, msg: Option<String>) {
69        let mut v = self.value.lock().unwrap();
70        *v = (*v + step).min(self.max);
71        let value = *v;
72        drop(v);
73        if let Some(m) = msg {
74            *self.msg.lock().unwrap() = m;
75        }
76        let m = self.msg.lock().unwrap().clone();
77        self.spin.message(self.render_with(&m, value));
78    }
79
80    pub fn message(&self, msg: impl Into<String>) {
81        self.advance(0, Some(msg.into()));
82    }
83
84    pub fn stop(&self, msg: impl Into<String>) {
85        self.spin.stop(msg);
86    }
87    pub fn cancel(&self, msg: impl Into<String>) {
88        self.spin.cancel(msg);
89    }
90    pub fn error(&self, msg: impl Into<String>) {
91        self.spin.error(msg);
92    }
93
94    fn render(&self, msg: &str) -> String {
95        self.render_with(msg, *self.value.lock().unwrap())
96    }
97    fn render_with(&self, msg: &str, value: u64) -> String {
98        let filled = ((value as usize) * self.size) / (self.max as usize);
99        let ch = self.style.ch();
100        let on = ch.repeat(filled);
101        let off = ch.repeat(self.size - filled);
102        format!(
103            "{}{} {}",
104            paint(super::settings::colors().accent, &on),
105            paint(DIM, &off),
106            msg
107        )
108    }
109}