1use std::cell::Cell;
13
14use crate::console::{Console, ConsoleOptions};
15use crate::control::{Control, ControlType};
16use crate::protocol::Renderable;
17use crate::segment::Segment;
18use crate::style::Style;
19
20pub struct LiveRender {
23 renderable: Box<dyn Renderable>,
24 style: Option<Style>,
25 shape: Cell<Option<(usize, usize)>>,
27}
28
29impl LiveRender {
30 pub fn new(renderable: Box<dyn Renderable>) -> Self {
31 LiveRender {
32 renderable,
33 style: None,
34 shape: Cell::new(None),
35 }
36 }
37
38 pub fn style(mut self, style: Style) -> Self {
40 self.style = Some(style);
41 self
42 }
43
44 pub fn set_renderable(&mut self, renderable: Box<dyn Renderable>) {
47 self.renderable = renderable;
48 }
49
50 pub fn last_render_height(&self) -> usize {
53 self.shape.get().map_or(0, |(_, height)| height)
54 }
55
56 pub fn position_cursor(&self) -> Control {
59 match self.shape.get() {
60 Some((_, height)) => {
61 let mut codes = vec![ControlType::CarriageReturn, ControlType::EraseInLine(2)];
62 for _ in 0..height.saturating_sub(1) {
63 codes.push(ControlType::CursorUp(1));
64 codes.push(ControlType::EraseInLine(2));
65 }
66 Control::new(&codes)
67 }
68 None => Control::new(&[]),
69 }
70 }
71
72 pub fn restore_cursor(&self) -> Control {
75 match self.shape.get() {
76 Some((_, height)) => {
77 let mut codes = vec![ControlType::CarriageReturn];
78 for _ in 0..height {
79 codes.push(ControlType::CursorUp(1));
80 codes.push(ControlType::EraseInLine(2));
81 }
82 Control::new(&codes)
83 }
84 None => Control::new(&[]),
85 }
86 }
87}
88
89impl Renderable for LiveRender {
90 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
91 let mut lines = console.render_lines(self.renderable.as_ref(), options, false);
93 if let Some(style) = &self.style {
94 for line in &mut lines {
95 *line = Segment::apply_style(line, style);
96 }
97 }
98
99 let height = lines.len();
101 let width = lines
102 .iter()
103 .map(|line| line.iter().map(Segment::cell_length).sum::<usize>())
104 .max()
105 .unwrap_or(0);
106 self.shape.set(Some((width, height)));
107
108 let mut segments = Vec::new();
109 let last = height.saturating_sub(1);
110 for (index, line) in lines.into_iter().enumerate() {
111 segments.extend(line);
112 if index != last {
113 segments.push(Segment::line());
114 }
115 }
116 segments
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::color::ColorSystem;
124 use crate::text::Text;
125
126 fn console() -> Console {
127 Console::builder()
128 .force_terminal(true)
129 .color_system(Some(ColorSystem::Truecolor))
130 .width(20)
131 .no_color(false)
132 .build()
133 }
134
135 #[test]
136 fn renders_content_and_control_codes() {
137 let live = LiveRender::new(Box::new(Text::new("line one\nline two\nline three")));
138 let console = console();
139 assert_eq!(
141 console.render_to_string(&live),
142 "line one\nline two\nline three"
143 );
144 assert_eq!(
146 live.position_cursor().as_str(),
147 "\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
148 );
149 assert_eq!(
150 live.restore_cursor().as_str(),
151 "\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
152 );
153 }
154
155 #[test]
156 fn single_line_shape() {
157 let live = LiveRender::new(Box::new(Text::new("solo")));
158 let console = console();
159 assert_eq!(console.render_to_string(&live), "solo");
160 assert_eq!(live.position_cursor().as_str(), "\r\x1b[2K");
161 assert_eq!(live.restore_cursor().as_str(), "\r\x1b[1A\x1b[2K");
162 }
163
164 #[test]
165 fn no_control_before_first_render() {
166 let live = LiveRender::new(Box::new(Text::new("x")));
167 assert_eq!(live.position_cursor().as_str(), "");
168 assert_eq!(live.restore_cursor().as_str(), "");
169 }
170}