fast_rich/
live.rs

1use crate::console::{Console, RenderContext};
2use crate::renderable::Renderable;
3
4/// Live display context manager.
5///
6/// Manages a live-updating display in the terminal.
7pub struct Live<'a> {
8    console: &'a Console,
9    renderable: Box<dyn Renderable>,
10    refresh_rate: u64, // Refresh rate in Hz (not currently used for auto-refresh thread in this version)
11    transient: bool,
12    height: usize,
13    started: bool,
14}
15
16impl<'a> Live<'a> {
17    /// Create a new Live display.
18    pub fn new(renderable: impl Renderable + 'static, console: &'a Console) -> Self {
19        Live {
20            console,
21            renderable: Box::new(renderable),
22            refresh_rate: 4,
23            transient: false,
24            height: 0,
25            started: false,
26        }
27    }
28
29    /// Set whether the display should be cleared on exit.
30    pub fn transient(mut self, transient: bool) -> Self {
31        self.transient = transient;
32        self
33    }
34
35    /// Set the refresh rate (currently unused in manual refresh mode).
36    pub fn refresh_rate(mut self, rate: u64) -> Self {
37        self.refresh_rate = rate;
38        self
39    }
40
41    /// Start the live display.
42    pub fn start(&mut self) {
43        if self.started {
44            return;
45        }
46        self.console.show_cursor(false);
47        self.started = true;
48        self.refresh();
49    }
50
51    /// Stop the live display.
52    pub fn stop(&mut self) {
53        if !self.started {
54            return;
55        }
56
57        // Clean up
58        if self.height > 0 {
59            self.console.move_cursor_up(self.height as u16);
60            // We clear lines downwards to ensure clean exit
61            for _ in 0..self.height {
62                self.console.clear_line();
63                self.console.move_cursor_down(1);
64            }
65            self.console.move_cursor_up(self.height as u16);
66        }
67
68        if !self.transient {
69            // If not transient, we want to leave the last frame visible
70            // So we reprint it one last time, but this time we don't track height
71            // to "lock" it in place (as standard output)
72            self.console.print_renderable(self.renderable.as_ref());
73            self.console.newline(); // Ensure final newline
74        }
75
76        self.console.show_cursor(true);
77        self.started = false;
78        self.height = 0;
79    }
80
81    /// Update the renderable and refresh the display.
82    pub fn update(&mut self, renderable: impl Renderable + 'static) {
83        self.renderable = Box::new(renderable);
84        self.refresh();
85    }
86
87    /// Refresh the display with the current renderable.
88    pub fn refresh(&mut self) {
89        if !self.started {
90            return;
91        }
92
93        // 1. Move cursor up to overwrite previous output
94        if self.height > 0 {
95            self.console.move_cursor_up(self.height as u16);
96        }
97
98        // 2. Render content
99        let width = self.console.get_width();
100        let context = RenderContext {
101            width,
102            height: None,
103        };
104
105        let segments = self.renderable.render(&context);
106
107        // 3. Calculate new height
108        // We need to know how many lines this render took.
109        // Similar to console.print, but we track lines.
110        let mut lines = 0;
111        for segment in &segments {
112            // Write to console
113            // We can't use console.print because that might add a newline we don't control
114            // cleanly or we want to count lines.
115            // Actually console.print adds a newline at the end if we use println.
116            // Let's use internal write helpers or just count newlines in segments.
117            if segment.newline {
118                lines += 1;
119            }
120        }
121
122        // If the last segment didn't have a newline, it's still occupying a line?
123        // Usually renderables ensure newlines or we treat them as flow.
124        // For now let's assume one newline per line.
125
126        // Wait, Console::print_renderable just writes segments. It behaves linearly.
127        // We want to verify if the output ended with a newline or not.
128        // Simplest strategy:
129        // We write the segments.
130        // Then we ensure we end with a newline so the cursor is on the next line?
131        // Or we keep the cursor at the end of the last line?
132
133        // rich.live typically clears the area and rewrites.
134        // To avoid flicker:
135        // Move up N lines.
136        // Clear line? Or just overwrite? Overwriting is better (less flicker).
137        // If new content is shorter than old content, we must clear the remainder.
138
139        // Strategy:
140        // 1. Buffer the output (optional, but good for flicker).
141        // 2. Write output.
142        // 3. Count lines written.
143        // 4. If new_lines < old_lines, clear remaining old lines.
144
145        // Let's rely on Console to write but we need to buffer to count lines?
146        // Or we can just count segments newlines?
147
148        // Let's buffer it for now to be safe and to support "clearing".
149        // Actually, Console has a capture mode but we are using the live console.
150        // Let's just write and track.
151
152        // NOTE: This simple implementation assumes the renderable *is* the lines.
153        self.console.write_segments(&segments);
154
155        // If the segments didn't end with a newline, we add one to separate from potential next output?
156        // Rich usually ensures block display.
157        if !segments.is_empty() && !segments.last().unwrap().newline {
158            self.console.newline();
159            lines += 1;
160        }
161
162        let new_height = lines;
163
164        // If we shrank, clear the lines below (that were part of old height)
165        if self.height > new_height {
166            let diff = self.height - new_height;
167            // Cursor is currently at end of new content.
168            for _ in 0..diff {
169                self.console.clear_line(); // Clear this line
170                self.console.newline(); // Move down
171            }
172            // Move back up
173            self.console.move_cursor_up(diff as u16);
174        }
175
176        self.height = new_height;
177    }
178}
179
180impl<'a> Drop for Live<'a> {
181    fn drop(&mut self) {
182        self.stop();
183    }
184}