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 last_render_height(&self) -> usize {
82 self.shape.get().map_or(0, |(_, height)| height)
83 }
84
85 pub fn position_cursor(&self) -> Control {
88 match self.shape.get() {
89 Some((_, height)) => {
90 let mut codes = vec![ControlType::CarriageReturn, ControlType::EraseInLine(2)];
91 for _ in 0..height.saturating_sub(1) {
92 codes.push(ControlType::CursorUp(1));
93 codes.push(ControlType::EraseInLine(2));
94 }
95 Control::new(&codes)
96 }
97 None => Control::new(&[]),
98 }
99 }
100
101 pub fn restore_cursor(&self) -> Control {
104 match self.shape.get() {
105 Some((_, height)) => {
106 let mut codes = vec![ControlType::CarriageReturn];
107 for _ in 0..height {
108 codes.push(ControlType::CursorUp(1));
109 codes.push(ControlType::EraseInLine(2));
110 }
111 Control::new(&codes)
112 }
113 None => Control::new(&[]),
114 }
115 }
116}
117
118impl Renderable for LiveRender {
119 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
120 let mut lines = console.render_lines(self.renderable.as_ref(), options, false);
122 if let Some(style) = &self.style {
123 for line in &mut lines {
124 *line = Segment::apply_style(line, style);
125 }
126 }
127
128 let screen_height = options.size.height;
130 if lines.len() > screen_height {
131 match self.vertical_overflow {
132 VerticalOverflow::Crop => lines.truncate(screen_height),
133 VerticalOverflow::Ellipsis => {
134 let keep = screen_height
137 .checked_sub(1)
138 .unwrap_or_else(|| lines.len() - 1);
139 lines.truncate(keep);
140 let overflow_text = Text::styled("...", "live.ellipsis")
141 .overflow(Overflow::Crop)
142 .justify(Justify::Center);
143 lines.push(console.render(&overflow_text, None));
144 }
145 VerticalOverflow::Visible => {}
146 }
147 }
148
149 let height = lines.len();
151 let width = lines
152 .iter()
153 .map(|line| line.iter().map(Segment::cell_length).sum::<usize>())
154 .max()
155 .unwrap_or(0);
156 self.shape.set(Some((width, height)));
157
158 let mut segments = Vec::new();
159 let last = height.saturating_sub(1);
160 for (index, line) in lines.into_iter().enumerate() {
161 segments.extend(line);
162 if index != last {
163 segments.push(Segment::line());
164 }
165 }
166 segments
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::color::ColorSystem;
174 use crate::text::Text;
175
176 fn console() -> Console {
177 Console::builder()
178 .force_terminal(true)
179 .color_system(Some(ColorSystem::Truecolor))
180 .width(20)
181 .no_color(false)
182 .build()
183 }
184
185 #[test]
186 fn renders_content_and_control_codes() {
187 let live = LiveRender::new(Box::new(Text::new("line one\nline two\nline three")));
188 let console = console();
189 assert_eq!(
191 console.render_to_string(&live),
192 "line one\nline two\nline three"
193 );
194 assert_eq!(
196 live.position_cursor().as_str(),
197 "\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
198 );
199 assert_eq!(
200 live.restore_cursor().as_str(),
201 "\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
202 );
203 }
204
205 #[test]
206 fn single_line_shape() {
207 let live = LiveRender::new(Box::new(Text::new("solo")));
208 let console = console();
209 assert_eq!(console.render_to_string(&live), "solo");
210 assert_eq!(live.position_cursor().as_str(), "\r\x1b[2K");
211 assert_eq!(live.restore_cursor().as_str(), "\r\x1b[1A\x1b[2K");
212 }
213
214 #[test]
215 fn no_control_before_first_render() {
216 let live = LiveRender::new(Box::new(Text::new("x")));
217 assert_eq!(live.position_cursor().as_str(), "");
218 assert_eq!(live.restore_cursor().as_str(), "");
219 }
220}