Skip to main content

rusty_rich/
live.rs

1//! Live — auto-updating display. Equivalent to Rich's `live.py`.
2
3use std::io::{self, Write};
4use std::time::Instant;
5
6use crate::console::{ConsoleOptions, DynRenderable, Renderable};
7
8/// Manages a live-updating region of the terminal.
9pub struct Live {
10    renderable: Option<DynRenderable>,
11    screen: bool,
12    auto_refresh: bool,
13    refresh_per_second: f64,
14    transient: bool,
15    started: Option<Instant>,
16    previous_line_count: usize,
17}
18
19impl std::fmt::Debug for Live {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_struct("Live")
22            .field("screen", &self.screen)
23            .field("started", &self.started)
24            .finish()
25    }
26}
27
28impl Live {
29    pub fn new(renderable: impl Renderable + Send + Sync + 'static) -> Self {
30        Self {
31            renderable: Some(DynRenderable::new(renderable)),
32            screen: false,
33            auto_refresh: true,
34            refresh_per_second: 4.0,
35            transient: false,
36            started: None,
37            previous_line_count: 0,
38        }
39    }
40
41    pub fn screen(mut self) -> Self { self.screen = true; self }
42    pub fn no_auto_refresh(mut self) -> Self { self.auto_refresh = false; self }
43    pub fn refresh_per_second(mut self, rate: f64) -> Self { self.refresh_per_second = rate; self }
44    pub fn transient(mut self) -> Self { self.transient = true; self }
45
46    pub fn start(&mut self) -> io::Result<()> {
47        self.started = Some(Instant::now());
48        if self.screen {
49            write!(io::stdout(), "\x1b[?1049h")?;
50        }
51        write!(io::stdout(), "\x1b[?25l")?;
52        self.refresh()
53    }
54
55    pub fn stop(&mut self) -> io::Result<()> {
56        if self.transient {
57            for _ in 0..self.previous_line_count {
58                write!(io::stdout(), "\x1b[1A\x1b[2K")?;
59            }
60        }
61        if self.screen {
62            write!(io::stdout(), "\x1b[?1049l")?;
63        }
64        write!(io::stdout(), "\x1b[?25h")?;
65        io::stdout().flush()?;
66        self.started = None;
67        Ok(())
68    }
69
70    pub fn update(&mut self, renderable: impl Renderable + Send + Sync + 'static) -> io::Result<()> {
71        self.renderable = Some(DynRenderable::new(renderable));
72        self.refresh()
73    }
74
75    pub fn refresh(&mut self) -> io::Result<()> {
76        if let Some(ref renderable) = self.renderable {
77            let opts = ConsoleOptions::default();
78            let result = renderable.render(&opts);
79
80            if self.previous_line_count > 0 {
81                write!(io::stdout(), "\x1b[{}F", self.previous_line_count)?;
82            }
83
84            let ansi = result.to_ansi();
85            let line_count = ansi.lines().count();
86
87            write!(io::stdout(), "{ansi}")?;
88            if line_count < self.previous_line_count {
89                for _ in line_count..self.previous_line_count {
90                    write!(io::stdout(), "\x1b[2K\n")?;
91                }
92            }
93
94            self.previous_line_count = line_count;
95            io::stdout().flush()?;
96        }
97        Ok(())
98    }
99}
100
101impl Drop for Live {
102    fn drop(&mut self) {
103        let _ = self.stop();
104    }
105}