1use std::{
2 io::{self, Write},
3 ops,
4};
5
6use super::{Backend, ClearType, MoveDirection, Size};
7use crate::{
8 layout::Layout,
9 style::{Attributes, Color},
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13struct Cell {
14 value: Option<char>,
15 fg: Color,
16 bg: Color,
17 attributes: Attributes,
18}
19
20impl Default for Cell {
21 fn default() -> Self {
22 Self {
23 value: None,
24 fg: Color::Reset,
25 bg: Color::Reset,
26 attributes: Attributes::empty(),
27 }
28 }
29}
30
31#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
32struct Cursor {
33 x: u16,
34 y: u16,
35}
36
37impl Cursor {
38 fn to_linear(self, width: u16) -> usize {
39 (self.x + self.y * width) as usize
40 }
41}
42
43impl From<Cursor> for (u16, u16) {
44 fn from(c: Cursor) -> Self {
45 (c.x, c.y)
46 }
47}
48
49#[derive(Debug, Clone)]
56pub struct TestBackend {
57 cells: Vec<Cell>,
58 cursor: Cursor,
59 size: Size,
60 raw: bool,
61 hidden_cursor: bool,
62 current_fg: Color,
63 current_bg: Color,
64 current_attributes: Attributes,
65 viewport_start: usize,
66}
67
68impl PartialEq for TestBackend {
69 fn eq(&self, other: &Self) -> bool {
74 self.viewport() == other.viewport()
75 && self.size == other.size
76 && self.hidden_cursor == other.hidden_cursor
77 && (self.hidden_cursor || self.cursor == other.cursor)
78 }
79}
80
81impl Eq for TestBackend {}
82
83impl TestBackend {
84 pub fn new(size: Size) -> Self {
86 Self::new_with_layout(size, Layout::new(0, size))
87 }
88
89 pub fn new_with_layout(size: Size, layout: Layout) -> Self {
91 let mut this = Self {
92 cells: [Cell::default()].repeat(size.area() as usize),
93 cursor: Cursor::default(),
94 size,
95 raw: false,
96 hidden_cursor: false,
97 current_fg: Color::Reset,
98 current_bg: Color::Reset,
99 current_attributes: Attributes::empty(),
100 viewport_start: 0,
101 };
102
103 this.move_x(layout.line_offset + layout.offset_x);
104 this.move_y(layout.offset_y);
105
106 this
107 }
108
109 pub fn from_lines(lines: &[&str], size: Size) -> Self {
120 let mut backend = Self::new(size);
121
122 assert!(lines.len() <= size.height as usize);
123 let last_i = lines.len() - 1;
124
125 for (i, line) in lines.iter().enumerate() {
126 for c in line.chars() {
127 assert!(backend.cursor.x + 1 < backend.size.width);
128 backend.put_char(c);
129 }
130 if i < last_i {
131 backend.move_x(0);
132 backend.add_y(1);
133 }
134 }
135
136 backend
137 }
138
139 pub fn reset_with_layout(&mut self, layout: Layout) {
141 self.clear_range(..);
142 self.move_x(layout.offset_x + layout.line_offset);
143 self.move_y(layout.offset_y);
144 }
145
146 fn viewport(&self) -> &[Cell] {
147 &self.cells[self.viewport_start..(self.viewport_start + self.size.area() as usize)]
148 }
149
150 fn move_x(&mut self, x: u16) {
151 self.cursor.x = x.min(self.size.width.wrapping_sub(1));
153 }
154
155 fn move_y(&mut self, y: u16) {
156 self.cursor.y = y.min(self.size.height.wrapping_sub(1));
158 }
159
160 fn add_x(&mut self, x: u16) {
161 let x = self.cursor.x + x;
162 let dy = x / self.size.width;
163 self.cursor.x = x % self.size.width;
164 self.move_y(self.cursor.y + dy);
165 }
166
167 fn sub_x(&mut self, x: u16) {
168 self.cursor.x = self.cursor.x.saturating_sub(x);
169 }
170
171 fn add_y(&mut self, y: u16) {
172 self.move_y(self.cursor.y + y)
173 }
174
175 fn sub_y(&mut self, y: u16) {
176 self.cursor.y = self.cursor.y.saturating_sub(y);
177 }
178
179 fn cell_i(&self) -> usize {
180 self.viewport_start + self.cursor.to_linear(self.size.width)
181 }
182
183 fn cell(&mut self) -> &mut Cell {
184 let i = self.cell_i();
185 &mut self.cells[i]
186 }
187
188 fn clear_range<R: ops::RangeBounds<usize>>(&mut self, range: R) {
189 let start = match range.start_bound() {
190 ops::Bound::Included(&start) => start,
191 ops::Bound::Excluded(start) => start.checked_add(1).unwrap(),
192 ops::Bound::Unbounded => 0,
193 };
194
195 let end = match range.end_bound() {
196 ops::Bound::Included(end) => end.checked_add(1).unwrap(),
197 ops::Bound::Excluded(&end) => end,
198 ops::Bound::Unbounded => self.cells.len(),
199 };
200
201 self.cells[start..end]
202 .iter_mut()
203 .for_each(|c| *c = Cell::default());
204 }
205
206 fn put_char(&mut self, c: char) {
207 match c {
208 '\n' => {
209 self.add_y(1);
210 if !self.raw {
211 self.cursor.x = 0;
212 }
213 }
214 '\r' => self.cursor.x = 0,
215 '\t' => {
216 let x = 8 + self.cursor.x - (self.cursor.x % 8);
217 if x >= self.size.width && self.cursor.y < self.size.width - 1 {
218 self.cursor.x = 0;
219 self.cursor.y += 1;
220 } else {
221 self.move_x(x);
222 }
223 }
224 c => {
225 self.cell().value = Some(c);
226 self.cell().attributes = self.current_attributes;
227 self.cell().fg = self.current_fg;
228 self.cell().bg = self.current_bg;
229 self.add_x(1);
230 }
231 }
232 }
233
234 #[cfg(any(feature = "crossterm", feature = "termion"))]
235 fn assertion_failed(&self, other: &Self) {
236 panic!(
237 r#"assertion failed: `(left == right)`
238 left:
239{}
240right:
241{}
242"#,
243 self, other
244 );
245 }
246
247 #[cfg(not(any(feature = "crossterm", feature = "termion")))]
248 fn assertion_failed(&self, other: &Self) {
249 panic!(
250 r#"assertion failed: `(left == right)`
251 left:
252`TestBackend` {:p}
253right:
254`TestBackend` {:p}
255
256Enable any of the default backends to view what the `TestBackend`s looked like
257"#,
258 self, other
259 );
260 }
261
262 pub fn assert_eq(&self, other: &Self) {
265 if *self != *other {
266 self.assertion_failed(other);
267 }
268 }
269}
270
271impl Write for TestBackend {
272 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
273 std::str::from_utf8(buf)
274 .map_err(|_| io::ErrorKind::InvalidInput)?
275 .chars()
276 .for_each(|c| self.put_char(c));
277
278 Ok(buf.len())
279 }
280
281 fn flush(&mut self) -> io::Result<()> {
282 Ok(())
283 }
284}
285
286impl super::Backend for TestBackend {
287 fn enable_raw_mode(&mut self) -> io::Result<()> {
288 self.raw = true;
289 Ok(())
290 }
291
292 fn disable_raw_mode(&mut self) -> io::Result<()> {
293 self.raw = false;
294 Ok(())
295 }
296
297 fn hide_cursor(&mut self) -> io::Result<()> {
298 self.hidden_cursor = true;
299 Ok(())
300 }
301
302 fn show_cursor(&mut self) -> io::Result<()> {
303 self.hidden_cursor = false;
304 Ok(())
305 }
306
307 fn get_cursor_pos(&mut self) -> io::Result<(u16, u16)> {
308 Ok(self.cursor.into())
309 }
310
311 fn move_cursor_to(&mut self, x: u16, y: u16) -> io::Result<()> {
312 self.move_x(x);
313 self.move_y(y);
314 Ok(())
315 }
316
317 fn move_cursor(&mut self, direction: MoveDirection) -> io::Result<()> {
318 match direction {
319 MoveDirection::Up(n) => self.sub_y(n),
320 MoveDirection::Down(n) => self.add_y(n),
321 MoveDirection::Left(n) => self.sub_x(n),
322 MoveDirection::Right(n) => self.add_y(n),
323 MoveDirection::NextLine(n) => {
324 self.cursor.x = 0;
325 self.add_y(n);
326 }
327 MoveDirection::Column(n) => self.move_x(n),
328 MoveDirection::PrevLine(n) => {
329 self.cursor.x = 0;
330 self.sub_y(n);
331 }
332 }
333 Ok(())
334 }
335
336 fn scroll(&mut self, dist: i16) -> io::Result<()> {
337 if dist.is_positive() {
338 self.viewport_start = self
339 .viewport_start
340 .saturating_sub(dist as usize * self.size.width as usize);
341 } else {
342 self.viewport_start += (-dist as usize) * self.size.width as usize;
343 let new_len = self.viewport_start + self.size.area() as usize;
344
345 if new_len > self.cells.len() {
346 self.cells.resize_with(new_len, Cell::default)
347 };
348 }
349 Ok(())
350 }
351
352 fn set_attributes(&mut self, attributes: Attributes) -> io::Result<()> {
353 self.current_attributes = attributes;
354 Ok(())
355 }
356
357 fn set_fg(&mut self, color: Color) -> io::Result<()> {
358 self.current_fg = color;
359 Ok(())
360 }
361
362 fn set_bg(&mut self, color: Color) -> io::Result<()> {
363 self.current_bg = color;
364 Ok(())
365 }
366
367 fn clear(&mut self, clear_type: ClearType) -> io::Result<()> {
368 match clear_type {
369 ClearType::All => self.clear_range(..),
370 ClearType::FromCursorDown => self.clear_range(self.cell_i()..),
371 ClearType::FromCursorUp => self.clear_range(..=self.cell_i()),
372 ClearType::CurrentLine => {
373 let s = (self.cursor.y * self.size.width) as usize;
374 let e = ((self.cursor.y + 1) * self.size.width) as usize;
375 self.clear_range(s..e)
376 }
377 ClearType::UntilNewLine => {
378 let e = ((self.cursor.y + 1) * self.size.width) as usize;
379 self.clear_range(self.cell_i()..e)
380 }
381 }
382 Ok(())
383 }
384
385 fn size(&self) -> io::Result<Size> {
386 Ok(self.size)
387 }
388}
389
390#[cfg(any(feature = "crossterm", feature = "termion"))]
391#[cfg_attr(docsrs, doc(cfg(any(feature = "crossterm", feature = "termion"))))]
392impl std::fmt::Display for TestBackend {
393 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401 let mut buf = Vec::with_capacity(self.size.area() as usize);
402
403 if let Err(e) = self.write_to_buf(&mut buf) {
404 return write!(f, "<could not render TestBackend: {}>", e);
405 }
406
407 match std::str::from_utf8(&buf) {
408 Ok(s) => write!(f, "{}", s),
409 Err(e) => write!(f, "<could not render TestBackend: {}>", e),
410 }
411 }
412}
413
414fn map_reset(c: Color, to: Color) -> Color {
415 match c {
416 Color::Reset => to,
417 c => c,
418 }
419}
420
421impl TestBackend {
422 pub fn write_to_backend<B: Backend>(&self, mut backend: B) -> io::Result<()> {
428 let mut fg = Color::Reset;
429 let mut bg = Color::Reset;
430 let mut attributes = Attributes::empty();
431
432 let cursor = if self.hidden_cursor {
433 usize::MAX
434 } else {
435 self.cursor.to_linear(self.size.width) as usize
436 };
437
438 let width = self.size.width as usize;
439
440 let symbol_set = crate::symbols::current();
441
442 write!(backend, "{}", symbol_set.box_top_left)?;
443 for _ in 0..self.size.width {
444 write!(backend, "{}", symbol_set.box_horizontal)?;
445 }
446 writeln!(backend, "{}", symbol_set.box_top_right)?;
447
448 for (i, cell) in self.viewport().iter().enumerate() {
449 if i % width == 0 {
450 write!(backend, "{}", symbol_set.box_vertical)?;
451 }
452
453 if cell.attributes != attributes {
454 backend.set_attributes(cell.attributes)?;
455 attributes = cell.attributes;
456 }
457
458 let (cell_fg, cell_bg) = if i == cursor {
459 (
460 map_reset(cell.bg, Color::Black),
461 map_reset(cell.fg, Color::Grey),
462 )
463 } else {
464 (cell.fg, cell.bg)
465 };
466
467 if cell_fg != fg {
468 backend.set_fg(cell_fg)?;
469 fg = cell_fg;
470 }
471 if cell_bg != bg {
472 backend.set_bg(cell_bg)?;
473 bg = cell_bg;
474 }
475
476 write!(backend, "{}", cell.value.unwrap_or(' '))?;
477
478 if (i + 1) % width == 0 {
479 if !attributes.is_empty() {
480 backend.set_attributes(Attributes::empty())?;
481 attributes = Attributes::empty();
482 }
483 if fg != Color::Reset {
484 fg = Color::Reset;
485 backend.set_fg(fg)?;
486 }
487 if bg != Color::Reset {
488 bg = Color::Reset;
489 backend.set_bg(bg)?;
490 }
491 writeln!(backend, "{}", symbol_set.box_vertical)?;
492 }
493 }
494
495 write!(backend, "{}", symbol_set.box_bottom_left)?;
496 for _ in 0..self.size.width {
497 write!(backend, "{}", symbol_set.box_horizontal)?;
498 }
499 write!(backend, "{}", symbol_set.box_bottom_right)?;
500
501 backend.flush()
502 }
503
504 #[cfg(any(feature = "crossterm", feature = "termion"))]
512 #[cfg_attr(docsrs, doc(cfg(any(feature = "crossterm", feature = "termion"))))]
513 pub fn write_to_buf<W: Write>(&self, buf: W) -> io::Result<()> {
514 self.write_to_backend(super::get_backend(buf))
515 }
516}