Skip to main content

ic_host_text/progress/
mod.rs

1//! Pure single-line progress frames; callers own terminal admission and output.
2
3use crate::{escape::terminal_text, width::display_width};
4
5/// Tracks the last rendered width and constructs carriage-return progress frames.
6///
7/// Input controls are escaped before width accounting. Callers write returned
8/// frames verbatim and own terminal detection, input limits, clipping to prevent
9/// wrapping, output failures, flushing and when to finish or clear the line.
10/// Width sums Unicode scalar columns, like [`crate::width::fit_line`],
11/// conservatively counting joined emoji separately rather than as one grapheme.
12/// No output or automatic cleanup occurs when this value is dropped.
13#[derive(Debug, Default)]
14pub struct ProgressLine {
15    last_width: usize,
16}
17
18impl ProgressLine {
19    /// Start with no outstanding progress text.
20    #[must_use]
21    pub const fn new() -> Self {
22        Self { last_width: 0 }
23    }
24
25    /// Replace the current text, blanking columns left by a longer previous frame.
26    #[must_use]
27    pub fn update(&mut self, text: &str) -> String {
28        self.render(text, false)
29    }
30
31    /// Render the final text and newline, then forget the outstanding width.
32    #[must_use]
33    pub fn finish(&mut self, text: &str) -> String {
34        self.render(text, true)
35    }
36
37    /// Blank outstanding text and return to its starting column.
38    /// Returns an empty string when no columns need clearing.
39    #[must_use]
40    pub fn clear(&mut self) -> String {
41        if self.last_width == 0 {
42            return String::new();
43        }
44        let mut frame = String::from("\r");
45        frame.extend(std::iter::repeat_n(' ', self.last_width));
46        frame.push('\r');
47        self.last_width = 0;
48        frame
49    }
50
51    fn render(&mut self, text: &str, newline: bool) -> String {
52        let text = terminal_text(text);
53        let width = display_width(&text);
54        let mut frame = String::from("\r");
55        frame.push_str(&text);
56        frame.extend(std::iter::repeat_n(
57            ' ',
58            self.last_width.saturating_sub(width),
59        ));
60        if newline {
61            frame.push('\n');
62        }
63        self.last_width = if newline { 0 } else { width };
64        frame
65    }
66}
67
68#[cfg(test)]
69mod tests;