Skip to main content

gobby_code/
progress.rs

1//! Lightweight progress bar for indexing operations.
2//!
3//! Single-line overwriting display on stderr. No-op when stderr is not a TTY
4//! or when quiet mode is enabled. Zero external dependencies.
5
6use std::io::{IsTerminal, Write};
7
8/// A simple progress bar that overwrites a single line on stderr.
9pub struct ProgressBar {
10    total: usize,
11    current: usize,
12    enabled: bool,
13    bar_width: usize,
14}
15
16impl ProgressBar {
17    /// Create a new progress bar. Renders only when stderr is a TTY and `quiet` is false.
18    pub fn new(total: usize, quiet: bool) -> Self {
19        let enabled = !quiet && total > 0 && std::io::stderr().is_terminal();
20        Self {
21            total,
22            current: 0,
23            enabled,
24            bar_width: 20,
25        }
26    }
27
28    /// Advance the progress bar and display the current file being indexed.
29    pub fn tick(&mut self, file_path: &str) {
30        self.current += 1;
31        if !self.enabled {
32            return;
33        }
34
35        let pct = self.current as f64 / self.total as f64;
36        let filled = (pct * self.bar_width as f64) as usize;
37        let empty = self.bar_width - filled;
38
39        let bar: String = "█".repeat(filled) + &"░".repeat(empty);
40        let counter = format!("{}/{}", self.current, self.total);
41
42        // Truncate path from the left to fit remaining terminal width
43        // Layout: \r[{bar}] {counter} : {path}\x1b[K
44        // Fixed prefix width: 1 + bar_width + 1 + 1 + counter + 3 = bar_width + 6 + counter.len()
45        let prefix_width = self.bar_width + 6 + counter.len();
46        let max_path = 80usize.saturating_sub(prefix_width);
47        let display_path = if file_path.len() > max_path && max_path > 3 {
48            let start = file_path
49                .char_indices()
50                .rev()
51                .nth(max_path - 4)
52                .map(|(i, _)| i)
53                .unwrap_or(0);
54            format!("...{}", &file_path[start..])
55        } else {
56            file_path.to_string()
57        };
58
59        // \r overwrites the line, \x1b[K clears to end of line
60        eprint!("\r[{bar}] {counter} : {display_path}\x1b[K");
61        let _ = std::io::stderr().flush();
62    }
63
64    /// Clear the progress line after completion.
65    pub fn finish(&self) {
66        if self.enabled {
67            eprint!("\r\x1b[K");
68            let _ = std::io::stderr().flush();
69        }
70    }
71}