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}
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 }
73 }
74
75 pub fn reset(&mut self) -> io::Result<&mut Self> {
78 let current_cursor_line = match self.desired_cursor {
80 Some((line, _)) => line,
82 None => self.lines_rendered.saturating_sub(1),
84 };
85 if current_cursor_line != 0 {
86 write!(self.writer, "{}", cursor::Up(current_cursor_line))?;
87 }
88 write!(self.writer, "\r")?;
89
90 self.lines_rendered = 0;
92 self.desired_cursor = None;
93 Ok(self)
94 }
95
96 pub fn clear(&mut self) -> io::Result<()> {
102 self.reset()?;
103 write!(self.writer, "{}{}", clear::AfterCursor, cursor::Show)
104 }
105
106 pub fn render<E: Element>(&mut self, line: E) -> io::Result<&mut Self> {
108 if self.lines_rendered != 0 {
110 write!(self.writer, "\n\r")?;
111 }
112 let mut column = 0;
114 for chunk in line.render() {
115 if chunk.cursor {
116 debug_assert_eq!(chunk.value, "");
117 debug_assert_eq!(chunk.width, 0);
118 self.desired_cursor = Some((self.lines_rendered, column as u16));
119 } else {
120 write!(self.writer, "{}{}{Reset}", chunk.style, chunk.value)?;
121 column += chunk.width;
122 }
123 }
124 self.lines_rendered += 1;
125 Ok(self)
126 }
127
128 pub fn finish(&mut self) -> io::Result<()> {
131 if let Some((line, column)) = self.desired_cursor {
132 let up = self.lines_rendered - line - 1;
133 if up != 0 {
134 write!(self.writer, "{}", cursor::Up(up))?;
135 }
136 write!(self.writer, "\r")?;
137 if column != 0 {
138 write!(self.writer, "{}", cursor::Right(column))?;
139 }
140 write!(self.writer, "{}", cursor::Show)?;
141 } else {
142 write!(self.writer, "{}", cursor::Hide)?;
143 }
144 self.writer.flush()
145 }
146}
147
148impl<W: Write> Drop for Renderer<W> {
149 fn drop(&mut self) {
150 let _ = self.clear();
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use crate::element::{Cursor, IntoElement};
157
158 use super::*;
159
160 #[test]
161 fn empty() -> io::Result<()> {
162 let mut r = Renderer::new(vec![]);
163 for _ in 0..3 {
164 r.writer.clear();
165 r.reset()?.finish()?;
166 assert_eq!(r.writer, b"\r\x1b[?25l");
167 }
168 Ok(())
169 }
170
171 #[test]
172 fn one_line() -> io::Result<()> {
173 let mut r = Renderer::new(vec![]);
174 for _ in 0..3 {
175 r.writer.clear();
176 r.reset()?.render("trans rights".into_element())?.finish()?;
177 assert_eq!(r.writer, b"\rtrans rights\x1b[m\x1b[?25l");
178 }
179 Ok(())
180 }
181
182 #[test]
183 fn two_lines() -> io::Result<()> {
184 let mut r = Renderer::new(vec![]);
185 r.reset()?
186 .render("trans rights".into_element())?
187 .render("enby rights".into_element())?
188 .finish()?;
189 assert_eq!(
190 r.writer,
191 b"\rtrans rights\x1b[m\n\renby rights\x1b[m\x1b[?25l",
192 );
193
194 for _ in 0..3 {
195 r.writer.clear();
196 r.reset()?
197 .render("trans rights".into_element())?
198 .render("enby rights".into_element())?
199 .finish()?;
200 assert_eq!(
201 r.writer,
202 b"\x1b[1A\rtrans rights\x1b[m\n\renby rights\x1b[m\x1b[?25l",
203 );
204 }
205 Ok(())
206 }
207
208 #[test]
209 fn drop() {
210 let mut out = vec![];
211 Renderer::new(&mut out);
212 assert_eq!(out, b"\r\x1b[J\x1b[?25h");
213 }
214
215 #[test]
216 fn cursor_at_start_of_last_line() -> io::Result<()> {
217 let mut r = Renderer::new(vec![]);
218 r.reset()?
219 .render("trans rights".into_element())?
220 .render((Cursor, "enby rights".into_element()))?
221 .finish()?;
222 assert_eq!(
223 r.writer,
224 b"\rtrans rights\x1b[m\n\renby rights\x1b[m\r\x1b[?25h",
225 );
226 Ok(())
227 }
228
229 #[test]
230 fn cursor_in_last_line() -> io::Result<()> {
231 let mut r = Renderer::new(vec![]);
232 r.reset()?
233 .render("trans rights".into_element())?
234 .render(("enby ".into_element(), Cursor, "rights".into_element()))?
235 .finish()?;
236 assert_eq!(
237 r.writer,
238 b"\rtrans rights\x1b[m\n\renby \x1b[mrights\x1b[m\r\x1b[5C\x1b[?25h",
239 );
240 Ok(())
241 }
242
243 #[test]
244 fn cursor_in_previous_line() -> io::Result<()> {
245 let mut r = Renderer::new(vec![]);
246 r.reset()?
247 .render(("trans rights".into_element(), Cursor))?
248 .render("enby rights".into_element())?
249 .finish()?;
250 assert_eq!(
251 r.writer,
252 b"\rtrans rights\x1b[m\n\renby rights\x1b[m\x1b[1A\r\x1b[12C\x1b[?25h",
253 );
254 Ok(())
255 }
256}