Skip to main content

anstyle_progress/
progress.rs

1/// Terminal progress formatter
2///
3/// # Example
4///
5/// ```rust
6/// # use anstyle_progress::TermProgress;
7/// # use anstyle_progress::TermProgressStatus;
8/// let mut progress = TermProgress::start();
9///
10/// let progress = progress.percent(0);
11/// println!("{progress}");
12///
13/// let progress = progress.percent(50);
14/// println!("{progress}");
15///
16/// let progress = progress.percent(100);
17/// println!("{progress}");
18///
19/// let progress = TermProgress::remove();
20/// println!("{progress}");
21/// ```
22#[derive(Copy, Clone)]
23pub struct TermProgress {
24    status: Option<TermProgressStatus>,
25    percent: Option<u8>,
26}
27
28impl TermProgress {
29    /// No progress to display
30    pub fn none() -> Self {
31        Self {
32            status: None,
33            percent: None,
34        }
35    }
36
37    /// Start a progress indicator
38    ///
39    /// This starts in an indeterminate state
40    pub fn start() -> Self {
41        Self::none().status(TermProgressStatus::Normal)
42    }
43
44    /// Start an error indicator
45    pub fn error() -> Self {
46        Self::none().status(TermProgressStatus::Error)
47    }
48
49    /// Remove the indicator
50    pub fn remove() -> Self {
51        Self::none().status(TermProgressStatus::Removed)
52    }
53
54    /// Set progress percentage (between `0..=100`)
55    ///
56    /// Without setting this, progress will be indeterminate
57    pub fn percent(mut self, percent: u8) -> Self {
58        assert!(matches!(percent, 0..=100));
59        self.percent = Some(percent);
60        self
61    }
62
63    /// Change the reported status
64    pub fn status(mut self, status: TermProgressStatus) -> Self {
65        self.status = Some(status);
66        self
67    }
68}
69
70impl Default for TermProgress {
71    fn default() -> Self {
72        Self::none()
73    }
74}
75
76/// Reported status along with progress
77#[allow(missing_docs)]
78#[derive(Copy, Clone)]
79pub enum TermProgressStatus {
80    Removed,
81    Normal,
82    /// Some terminals treat this as a Warning
83    Paused,
84    Error,
85}
86
87impl core::fmt::Display for TermProgress {
88    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
89        let Some(status) = self.status else {
90            return Ok(());
91        };
92        let (st, pr) = match (status, self.percent) {
93            (TermProgressStatus::Removed, _) => (0, None),
94            (TermProgressStatus::Normal, Some(_)) => (1, self.percent),
95            (TermProgressStatus::Error, _) => (2, self.percent),
96            (TermProgressStatus::Normal, None) => (3, None),
97            (TermProgressStatus::Paused, _) => (4, self.percent),
98        };
99        write!(f, "\x1b]9;4;{st};")?;
100        if let Some(pr) = pr {
101            write!(f, "{pr}")?;
102        }
103        write!(f, "\x1b\\")
104    }
105}