1use std::cell::Cell;
13
14use crate::console::{Console, ConsoleOptions, Justify, Overflow};
15use crate::control::{Control, ControlType};
16use crate::protocol::Renderable;
17use crate::segment::Segment;
18use crate::style::Style;
19use crate::text::Text;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum VerticalOverflow {
25 Crop,
27 #[default]
30 Ellipsis,
31 Visible,
33}
34
35pub struct LiveRender {
38 renderable: Box<dyn Renderable>,
39 style: Option<Style>,
40 vertical_overflow: VerticalOverflow,
41 shape: Cell<Option<(usize, usize)>>,
43}
44
45impl LiveRender {
46 pub fn new(renderable: Box<dyn Renderable>) -> Self {
47 LiveRender {
48 renderable,
49 style: None,
50 vertical_overflow: VerticalOverflow::Ellipsis,
51 shape: Cell::new(None),
52 }
53 }
54
55 pub fn style(mut self, style: Style) -> Self {
57 self.style = Some(style);
58 self
59 }
60
61 pub fn vertical_overflow(mut self, vertical_overflow: VerticalOverflow) -> Self {
64 self.vertical_overflow = vertical_overflow;
65 self
66 }
67
68 pub fn set_vertical_overflow(&mut self, vertical_overflow: VerticalOverflow) {
70 self.vertical_overflow = vertical_overflow;
71 }
72
73 pub fn set_renderable(&mut self, renderable: Box<dyn Renderable>) {
76 self.renderable = renderable;
77 }
78
79 pub fn renderable(&self) -> &dyn Renderable {
81 self.renderable.as_ref()
82 }
83
84 pub fn last_render_height(&self) -> usize {
87 self.shape.get().map_or(0, |(_, height)| height)
88 }
89
90 pub fn position_cursor(&self) -> Control {
93 position_cursor(self.shape.get())
94 }
95
96 pub fn restore_cursor(&self) -> Control {
99 restore_cursor(self.shape.get())
100 }
101}
102
103pub(crate) fn position_cursor(shape: Option<(usize, usize)>) -> Control {
105 match shape {
106 Some((_, height)) => {
107 let mut codes = vec![ControlType::CarriageReturn, ControlType::EraseInLine(2)];
108 for _ in 0..height.saturating_sub(1) {
109 codes.push(ControlType::CursorUp(1));
110 codes.push(ControlType::EraseInLine(2));
111 }
112 Control::new(&codes)
113 }
114 None => Control::new(&[]),
115 }
116}
117
118pub(crate) fn restore_cursor(shape: Option<(usize, usize)>) -> Control {
120 match shape {
121 Some((_, height)) => {
122 let mut codes = vec![ControlType::CarriageReturn];
123 for _ in 0..height {
124 codes.push(ControlType::CursorUp(1));
125 codes.push(ControlType::EraseInLine(2));
126 }
127 Control::new(&codes)
128 }
129 None => Control::new(&[]),
130 }
131}
132
133impl Renderable for LiveRender {
134 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
135 let (segments, shape) = render(
136 self.renderable.as_ref(),
137 self.style.as_ref(),
138 self.vertical_overflow,
139 console,
140 options,
141 );
142 self.shape.set(Some(shape));
144 segments
145 }
146
147 fn print_end_line(&self, _options: &ConsoleOptions) -> bool {
151 false
152 }
153}
154
155pub(crate) fn render(
158 renderable: &dyn Renderable,
159 style: Option<&Style>,
160 vertical_overflow: VerticalOverflow,
161 console: &Console,
162 options: &ConsoleOptions,
163) -> (Vec<Segment>, (usize, usize)) {
164 let mut lines = console.render_lines(renderable, options, false);
166 if let Some(style) = style {
167 for line in &mut lines {
168 *line = Segment::apply_style(line, style);
169 }
170 }
171
172 let screen_height = options.size.height;
174 if lines.len() > screen_height {
175 match vertical_overflow {
176 VerticalOverflow::Crop => lines.truncate(screen_height),
177 VerticalOverflow::Ellipsis => {
178 let keep = screen_height
181 .checked_sub(1)
182 .unwrap_or_else(|| lines.len() - 1);
183 lines.truncate(keep);
184 let overflow_text = Text::styled("...", "live.ellipsis")
185 .overflow(Overflow::Crop)
186 .justify(Justify::Center);
187 lines.push(console.render(&overflow_text, None));
188 }
189 VerticalOverflow::Visible => {}
190 }
191 }
192
193 let height = lines.len();
194 let width = lines
195 .iter()
196 .map(|line| line.iter().map(Segment::cell_length).sum::<usize>())
197 .max()
198 .unwrap_or(0);
199
200 let mut segments = Vec::new();
201 let last = height.saturating_sub(1);
202 for (index, line) in lines.into_iter().enumerate() {
203 segments.extend(line);
204 if index != last {
205 segments.push(Segment::line());
206 }
207 }
208 (segments, (width, height))
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::color::ColorSystem;
215 use crate::text::Text;
216
217 fn console() -> Console {
218 Console::builder()
219 .force_terminal(true)
220 .color_system(Some(ColorSystem::Truecolor))
221 .width(20)
222 .no_color(false)
223 .build()
224 }
225
226 #[test]
227 fn renders_content_and_control_codes() {
228 let live = LiveRender::new(Box::new(Text::new("line one\nline two\nline three")));
229 let console = console();
230 assert_eq!(
232 console.render_to_string(&live),
233 "line one\nline two\nline three"
234 );
235 assert_eq!(
237 live.position_cursor().as_str(),
238 "\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
239 );
240 assert_eq!(
241 live.restore_cursor().as_str(),
242 "\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
243 );
244 }
245
246 #[test]
247 fn single_line_shape() {
248 let live = LiveRender::new(Box::new(Text::new("solo")));
249 let console = console();
250 assert_eq!(console.render_to_string(&live), "solo");
251 assert_eq!(live.position_cursor().as_str(), "\r\x1b[2K");
252 assert_eq!(live.restore_cursor().as_str(), "\r\x1b[1A\x1b[2K");
253 }
254
255 #[test]
256 fn no_control_before_first_render() {
257 let live = LiveRender::new(Box::new(Text::new("x")));
258 assert_eq!(live.position_cursor().as_str(), "");
259 assert_eq!(live.restore_cursor().as_str(), "");
260 }
261}