1use std::io::{self, Write};
6
7use termion::style::Reset;
8use termion::{clear, cursor};
9
10use crate::Style;
11use crate::element::Element;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct RenderChunk<'s> {
16 pub(crate) value: &'s str,
18 pub(crate) width: usize,
20 pub(crate) style: Style,
22 pub(crate) cursor: bool,
26}
27
28impl<'s> RenderChunk<'s> {
29 pub const CURSOR: RenderChunk<'static> = RenderChunk {
30 value: "",
31 width: 0,
32 style: Style::EMPTY,
33 cursor: true,
34 };
35
36 pub fn new(value: &'s str, style: Style) -> Self {
37 RenderChunk::with_known_width(value, crate::width(value), style)
38 }
39
40 pub(crate) fn with_known_width(value: &'s str, width: usize, style: Style) -> Self {
41 debug_assert_eq!(crate::width(value), width);
42 RenderChunk {
43 value,
44 width,
45 style,
46 cursor: false,
47 }
48 }
49}
50
51impl<'s> From<&'s str> for RenderChunk<'s> {
52 fn from(value: &'s str) -> Self {
53 RenderChunk::new(value, Style::EMPTY)
54 }
55}
56
57pub struct Renderer<W: Write> {
59 pub(crate) writer: W,
60 lines_rendered: u16,
61 desired_cursor: Option<(u16, u16)>,
62 is_dirty: bool, }
64
65impl<W: Write> Renderer<W> {
66 pub fn new(writer: W) -> Self {
68 Renderer {
69 writer,
70 lines_rendered: 0,
71 desired_cursor: None,
72 is_dirty: false,
73 }
74 }
75
76 fn reset_state(&mut self) {
78 self.lines_rendered = 0;
79 self.desired_cursor = None;
80 self.is_dirty = false;
81 }
82
83 pub fn reset(&mut self) -> io::Result<&mut Self> {
85 assert!(!self.is_dirty, "finalize() must be called after rendering");
86 let current_cursor_line = match self.desired_cursor {
88 Some((line, _)) => line,
90 None => self.lines_rendered.saturating_sub(1),
92 };
93 if current_cursor_line != 0 {
94 write!(self.writer, "{}", cursor::Up(current_cursor_line))?;
95 }
96 write!(self.writer, "\r")?;
97
98 self.reset_state();
99 Ok(self)
100 }
101
102 pub fn clear(&mut self) -> io::Result<()> {
107 assert!(!self.is_dirty, "finalize() must be called after rendering");
108 self.reset()?;
109 write!(self.writer, "{}{}", clear::AfterCursor, cursor::Show)
110 }
111
112 pub fn render<E: Element>(&mut self, line: E) -> io::Result<&mut Self> {
114 self.is_dirty = true;
115 if self.lines_rendered != 0 {
117 write!(self.writer, "\n\r")?;
118 }
119 let mut column = 0;
121 for chunk in line.render() {
122 if chunk.cursor {
123 debug_assert_eq!(chunk.value, "");
124 debug_assert_eq!(chunk.width, 0);
125 self.desired_cursor = Some((self.lines_rendered, column as u16));
126 } else {
127 write!(self.writer, "{}{}{Reset}", chunk.style, chunk.value)?;
128 column += chunk.width;
129 }
130 }
131 write!(self.writer, "{}", clear::UntilNewline)?;
132 self.lines_rendered += 1;
133 Ok(self)
134 }
135
136 pub fn finish(&mut self) -> io::Result<()> {
139 self.is_dirty = false;
140 if let Some((line, column)) = self.desired_cursor {
141 let up = self.lines_rendered - line - 1;
142 if up != 0 {
143 write!(self.writer, "{}", cursor::Up(up))?;
144 }
145 write!(self.writer, "\r")?;
146 if column != 0 {
147 write!(self.writer, "{}", cursor::Right(column))?;
148 }
149 write!(self.writer, "{}", cursor::Show)?;
150 } else {
151 write!(self.writer, "{}", cursor::Hide)?;
152 }
153 self.writer.flush()
154 }
155
156 pub fn leave(&mut self) -> io::Result<()> {
162 assert!(!self.is_dirty, "finalize() must be called after rendering");
163 if self.lines_rendered == 0 {
164 return Ok(());
165 }
166 let down = match self.desired_cursor {
167 Some((row, _)) => self.lines_rendered - row - 1,
168 None => 0,
169 };
170 if down != 0 {
171 write!(self.writer, "{}", cursor::Down(down))?;
172 }
173 write!(self.writer, "\n\r")?;
174 self.reset_state();
175 Ok(())
176 }
177}
178
179impl<W: Write> Drop for Renderer<W> {
180 fn drop(&mut self) {
181 let _ = self.clear();
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use crate::element::{Cursor, IntoElement};
188
189 use super::*;
190
191 #[test]
192 fn empty() -> io::Result<()> {
193 let mut r = Renderer::new(vec![]);
194 for _ in 0..3 {
195 r.writer.clear();
196 r.reset()?.finish()?;
197 assert_eq!(r.writer, b"\r\x1b[?25l");
198 }
199 Ok(())
200 }
201
202 #[test]
203 fn empty_line() -> io::Result<()> {
204 let mut r = Renderer::new(vec![]);
205 for _ in 0..3 {
206 r.writer.clear();
207 r.reset()?.render(())?.finish()?;
208 assert_eq!(r.writer, b"\r\x1b[K\x1b[?25l");
209 }
210 Ok(())
211 }
212
213 #[test]
214 fn one_line() -> io::Result<()> {
215 let mut r = Renderer::new(vec![]);
216 for _ in 0..3 {
217 r.writer.clear();
218 r.reset()?.render("trans rights".into_element())?.finish()?;
219 assert_eq!(r.writer, b"\rtrans rights\x1b[m\x1b[K\x1b[?25l");
220 }
221 Ok(())
222 }
223
224 #[test]
225 fn two_lines() -> io::Result<()> {
226 let mut r = Renderer::new(vec![]);
227 r.reset()?
228 .render("trans rights".into_element())?
229 .render("enby rights".into_element())?
230 .finish()?;
231 assert_eq!(
232 r.writer,
233 b"\rtrans rights\x1b[m\x1b[K\n\renby rights\x1b[m\x1b[K\x1b[?25l",
234 );
235
236 for _ in 0..3 {
237 r.writer.clear();
238 r.reset()?
239 .render("trans rights".into_element())?
240 .render("enby rights".into_element())?
241 .finish()?;
242 assert_eq!(
243 r.writer,
244 b"\x1b[1A\rtrans rights\x1b[m\x1b[K\n\renby rights\x1b[m\x1b[K\x1b[?25l",
245 );
246 }
247 Ok(())
248 }
249
250 #[test]
251 fn drop() {
252 let mut out = vec![];
253 Renderer::new(&mut out);
254 assert_eq!(out, b"\r\x1b[J\x1b[?25h");
255 }
256
257 #[test]
258 fn cursor_at_start_of_last_line() -> io::Result<()> {
259 let mut r = Renderer::new(vec![]);
260 r.reset()?
261 .render("trans rights".into_element())?
262 .render((Cursor, "enby rights".into_element()))?
263 .finish()?;
264 assert_eq!(
265 r.writer,
266 b"\rtrans rights\x1b[m\x1b[K\n\renby rights\x1b[m\x1b[K\r\x1b[?25h",
267 );
268 Ok(())
269 }
270
271 #[test]
272 fn cursor_in_last_line() -> io::Result<()> {
273 let mut r = Renderer::new(vec![]);
274 r.reset()?
275 .render("trans rights".into_element())?
276 .render(("enby ".into_element(), Cursor, "rights".into_element()))?
277 .finish()?;
278 assert_eq!(
279 r.writer,
280 b"\rtrans rights\x1b[m\x1b[K\n\renby \x1b[mrights\x1b[m\x1b[K\r\x1b[5C\x1b[?25h",
281 );
282 Ok(())
283 }
284
285 #[test]
286 fn cursor_in_previous_line() -> io::Result<()> {
287 let mut r = Renderer::new(vec![]);
288 r.reset()?
289 .render(("trans rights".into_element(), Cursor))?
290 .render("enby rights".into_element())?
291 .finish()?;
292 assert_eq!(
293 r.writer,
294 b"\rtrans rights\x1b[m\x1b[K\n\renby rights\x1b[m\x1b[K\x1b[1A\r\x1b[12C\x1b[?25h",
295 );
296 Ok(())
297 }
298
299 #[test]
300 fn leave_empty() -> io::Result<()> {
301 let mut r = Renderer::new(vec![]);
302 r.reset()?.finish()?;
303 r.writer.clear();
304 r.leave()?;
305 assert_eq!(r.writer, b"");
306 Ok(())
307 }
308
309 #[test]
310 fn leave() -> io::Result<()> {
311 let mut r = Renderer::new(vec![]);
312 r.reset()?
313 .render("trans rights".into_element())?
314 .render("enby rights".into_element())?
315 .finish()?;
316 r.writer.clear();
317 r.leave()?;
318 r.clear()?;
319 assert_eq!(r.writer, b"\n\r\r\x1b[J\x1b[?25h");
320 Ok(())
321 }
322
323 #[test]
324 fn leave_with_cursor() -> io::Result<()> {
325 let mut r = Renderer::new(vec![]);
326 r.reset()?
327 .render(("trans rights".into_element(), Cursor))?
328 .render("enby rights".into_element())?
329 .finish()?;
330 r.writer.clear();
331 r.leave()?;
332 r.clear()?;
333 assert_eq!(r.writer, b"\x1b[1B\n\r\r\x1b[J\x1b[?25h");
334 Ok(())
335 }
336}