rust_analyzer/cli/
progress_report.rs

1//! A simple progress bar
2//!
3//! A single thread non-optimized progress bar
4use std::io::{self, Write};
5
6/// A Simple ASCII Progress Bar
7pub(crate) struct ProgressReport<'a> {
8    curr: f32,
9    text: String,
10    hidden: bool,
11
12    len: usize,
13    pos: u64,
14    msg: Option<Box<dyn Fn() -> String + 'a>>,
15}
16
17impl<'a> ProgressReport<'a> {
18    pub(crate) fn new(len: usize) -> ProgressReport<'a> {
19        ProgressReport { curr: 0.0, text: String::new(), hidden: false, len, pos: 0, msg: None }
20    }
21
22    pub(crate) fn hidden() -> ProgressReport<'a> {
23        ProgressReport { curr: 0.0, text: String::new(), hidden: true, len: 0, pos: 0, msg: None }
24    }
25
26    pub(crate) fn set_message(&mut self, msg: impl Fn() -> String + 'a) {
27        if !self.hidden {
28            self.msg = Some(Box::new(msg));
29        }
30        self.tick();
31    }
32
33    pub(crate) fn println<I: Into<String>>(&mut self, msg: I) {
34        self.clear();
35        println!("{}", msg.into());
36        self.tick();
37    }
38
39    pub(crate) fn inc(&mut self, delta: u64) {
40        self.pos += delta;
41        if self.len == 0 {
42            self.set_value(0.0)
43        } else {
44            self.set_value((self.pos as f32) / (self.len as f32))
45        }
46        self.tick();
47    }
48
49    pub(crate) fn finish_and_clear(&mut self) {
50        self.clear();
51    }
52
53    pub(crate) fn tick(&mut self) {
54        if self.hidden {
55            return;
56        }
57        let percent = (self.curr * 100.0) as u32;
58        let text = format!(
59            "{}/{} {percent:3>}% {}",
60            self.pos,
61            self.len,
62            self.msg.as_ref().map_or_else(String::new, |it| it())
63        );
64        self.update_text(&text);
65    }
66
67    fn update_text(&mut self, text: &str) {
68        // Get length of common portion
69        let mut common_prefix_length = 0;
70        let common_length = usize::min(self.text.len(), text.len());
71
72        while common_prefix_length < common_length
73            && text.chars().nth(common_prefix_length).unwrap()
74                == self.text.chars().nth(common_prefix_length).unwrap()
75        {
76            common_prefix_length += 1;
77        }
78
79        // Backtrack to the first differing character
80        let mut output = String::new();
81        output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length);
82        // Output new suffix, using chars() iter to ensure unicode compatibility
83        output.extend(text.chars().skip(common_prefix_length));
84
85        // If the new text is shorter than the old one: delete overlapping characters
86        if let Some(overlap_count) = self.text.len().checked_sub(text.len()) {
87            if overlap_count > 0 {
88                output += &" ".repeat(overlap_count);
89                output += &"\x08".repeat(overlap_count);
90            }
91        }
92
93        let _ = io::stdout().write(output.as_bytes());
94        let _ = io::stdout().flush();
95        text.clone_into(&mut self.text);
96    }
97
98    fn set_value(&mut self, value: f32) {
99        self.curr = value.clamp(0.0, 1.0);
100    }
101
102    fn clear(&mut self) {
103        if self.hidden {
104            return;
105        }
106
107        // Fill all last text to space and return the cursor
108        let spaces = " ".repeat(self.text.len());
109        let backspaces = "\x08".repeat(self.text.len());
110        print!("{backspaces}{spaces}{backspaces}");
111        let _ = io::stdout().flush();
112
113        self.text = String::new();
114    }
115}