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, "finish() 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, "finish() must be called after rendering");
108 self.reset()?;
109 write!(self.writer, "{}{}", clear::AfterCursor, cursor::Show)
110 }
111
112 pub fn render<'s, E: Element<'s>>(&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, "finish() 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 if !std::thread::panicking() {
182 let _ = self.clear();
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use std::panic::AssertUnwindSafe;
191
192 use crate::element::{Cursor, IntoElement};
193
194 use super::*;
195
196 #[test]
197 fn empty() -> io::Result<()> {
198 let mut r = Renderer::new(vec![]);
199 for _ in 0..3 {
200 r.writer.clear();
201 r.reset()?.finish()?;
202 assert_eq!(r.writer, b"\r\x1b[?25l");
203 }
204 Ok(())
205 }
206
207 #[test]
208 fn empty_line() -> io::Result<()> {
209 let mut r = Renderer::new(vec![]);
210 for _ in 0..3 {
211 r.writer.clear();
212 r.reset()?.render(())?.finish()?;
213 assert_eq!(r.writer, b"\r\x1b[K\x1b[?25l");
214 }
215 Ok(())
216 }
217
218 #[test]
219 fn one_line() -> io::Result<()> {
220 let mut r = Renderer::new(vec![]);
221 for _ in 0..3 {
222 r.writer.clear();
223 r.reset()?.render("trans rights".into_element())?.finish()?;
224 assert_eq!(r.writer, b"\rtrans rights\x1b[m\x1b[K\x1b[?25l");
225 }
226 Ok(())
227 }
228
229 #[test]
230 fn two_lines() -> io::Result<()> {
231 let mut r = Renderer::new(vec![]);
232 r.reset()?
233 .render("trans rights".into_element())?
234 .render("enby rights".into_element())?
235 .finish()?;
236 assert_eq!(
237 r.writer,
238 b"\rtrans rights\x1b[m\x1b[K\n\renby rights\x1b[m\x1b[K\x1b[?25l",
239 );
240
241 for _ in 0..3 {
242 r.writer.clear();
243 r.reset()?
244 .render("trans rights".into_element())?
245 .render("enby rights".into_element())?
246 .finish()?;
247 assert_eq!(
248 r.writer,
249 b"\x1b[1A\rtrans rights\x1b[m\x1b[K\n\renby rights\x1b[m\x1b[K\x1b[?25l",
250 );
251 }
252 Ok(())
253 }
254
255 #[test]
256 fn drop() {
257 let mut out = vec![];
258 Renderer::new(&mut out);
259 assert_eq!(out, b"\r\x1b[J\x1b[?25h");
260 }
261
262 #[test]
263 fn cursor_at_start_of_last_line() -> io::Result<()> {
264 let mut r = Renderer::new(vec![]);
265 r.reset()?
266 .render("trans rights".into_element())?
267 .render((Cursor, "enby rights".into_element()))?
268 .finish()?;
269 assert_eq!(
270 r.writer,
271 b"\rtrans rights\x1b[m\x1b[K\n\renby rights\x1b[m\x1b[K\r\x1b[?25h",
272 );
273 Ok(())
274 }
275
276 #[test]
277 fn cursor_in_last_line() -> io::Result<()> {
278 let mut r = Renderer::new(vec![]);
279 r.reset()?
280 .render("trans rights".into_element())?
281 .render(("enby ".into_element(), Cursor, "rights".into_element()))?
282 .finish()?;
283 assert_eq!(
284 r.writer,
285 b"\rtrans rights\x1b[m\x1b[K\n\renby \x1b[mrights\x1b[m\x1b[K\r\x1b[5C\x1b[?25h",
286 );
287 Ok(())
288 }
289
290 #[test]
291 fn cursor_in_previous_line() -> io::Result<()> {
292 let mut r = Renderer::new(vec![]);
293 r.reset()?
294 .render(("trans rights".into_element(), Cursor))?
295 .render("enby rights".into_element())?
296 .finish()?;
297 assert_eq!(
298 r.writer,
299 b"\rtrans rights\x1b[m\x1b[K\n\renby rights\x1b[m\x1b[K\x1b[1A\r\x1b[12C\x1b[?25h",
300 );
301 Ok(())
302 }
303
304 #[test]
305 fn leave_empty() -> io::Result<()> {
306 let mut r = Renderer::new(vec![]);
307 r.reset()?.finish()?;
308 r.writer.clear();
309 r.leave()?;
310 assert_eq!(r.writer, b"");
311 Ok(())
312 }
313
314 #[test]
315 fn leave() -> io::Result<()> {
316 let mut r = Renderer::new(vec![]);
317 r.reset()?
318 .render("trans rights".into_element())?
319 .render("enby rights".into_element())?
320 .finish()?;
321 r.writer.clear();
322 r.leave()?;
323 r.clear()?;
324 assert_eq!(r.writer, b"\n\r\r\x1b[J\x1b[?25h");
325 Ok(())
326 }
327
328 #[test]
329 fn leave_with_cursor() -> io::Result<()> {
330 let mut r = Renderer::new(vec![]);
331 r.reset()?
332 .render(("trans rights".into_element(), Cursor))?
333 .render("enby rights".into_element())?
334 .finish()?;
335 r.writer.clear();
336 r.leave()?;
337 r.clear()?;
338 assert_eq!(r.writer, b"\x1b[1B\n\r\r\x1b[J\x1b[?25h");
339 Ok(())
340 }
341
342 #[test]
343 fn no_drop_during_panic() {
344 let mut output = vec![];
345 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
346 let mut r = Renderer::new(&mut output);
347 let _ = r.render("hello".into_element());
348 panic!();
349 }));
350 result.unwrap_err();
351 assert_eq!(output, b"hello\x1b[m\x1b[K");
352 }
353}