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 position_cursor(&self) -> Control {
53 match self.shape.get() {
54 Some((_, height)) => {
55 let mut codes = vec![ControlType::CarriageReturn, ControlType::EraseInLine(2)];
56 for _ in 0..height.saturating_sub(1) {
57 codes.push(ControlType::CursorUp(1));
58 codes.push(ControlType::EraseInLine(2));
59 }
60 Control::new(&codes)
61 }
62 None => Control::new(&[]),
63 }
64 }
65
66 pub fn restore_cursor(&self) -> Control {
69 match self.shape.get() {
70 Some((_, height)) => {
71 let mut codes = vec![ControlType::CarriageReturn];
72 for _ in 0..height {
73 codes.push(ControlType::CursorUp(1));
74 codes.push(ControlType::EraseInLine(2));
75 }
76 Control::new(&codes)
77 }
78 None => Control::new(&[]),
79 }
80 }
81}
82
83impl Renderable for LiveRender {
84 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
85 let mut lines = console.render_lines(self.renderable.as_ref(), options, false);
87 if let Some(style) = &self.style {
88 for line in &mut lines {
89 *line = Segment::apply_style(line, style);
90 }
91 }
92
93 let height = lines.len();
95 let width = lines
96 .iter()
97 .map(|line| line.iter().map(Segment::cell_length).sum::<usize>())
98 .max()
99 .unwrap_or(0);
100 self.shape.set(Some((width, height)));
101
102 let mut segments = Vec::new();
103 let last = height.saturating_sub(1);
104 for (index, line) in lines.into_iter().enumerate() {
105 segments.extend(line);
106 if index != last {
107 segments.push(Segment::line());
108 }
109 }
110 segments
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use crate::color::ColorSystem;
118 use crate::text::Text;
119
120 fn console() -> Console {
121 Console::builder()
122 .force_terminal(true)
123 .color_system(Some(ColorSystem::Truecolor))
124 .width(20)
125 .no_color(false)
126 .build()
127 }
128
129 #[test]
130 fn renders_content_and_control_codes() {
131 let live = LiveRender::new(Box::new(Text::new("line one\nline two\nline three")));
132 let console = console();
133 assert_eq!(
135 console.render_to_string(&live),
136 "line one\nline two\nline three"
137 );
138 assert_eq!(
140 live.position_cursor().as_str(),
141 "\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
142 );
143 assert_eq!(
144 live.restore_cursor().as_str(),
145 "\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
146 );
147 }
148
149 #[test]
150 fn single_line_shape() {
151 let live = LiveRender::new(Box::new(Text::new("solo")));
152 let console = console();
153 assert_eq!(console.render_to_string(&live), "solo");
154 assert_eq!(live.position_cursor().as_str(), "\r\x1b[2K");
155 assert_eq!(live.restore_cursor().as_str(), "\r\x1b[1A\x1b[2K");
156 }
157
158 #[test]
159 fn no_control_before_first_render() {
160 let live = LiveRender::new(Box::new(Text::new("x")));
161 assert_eq!(live.position_cursor().as_str(), "");
162 assert_eq!(live.restore_cursor().as_str(), "");
163 }
164}