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