tui/src/test_backend.rs
125 lines
use std::fmt::{self};use std::io::Write;use std::io::{self};use ratatui::prelude::CrosstermBackend;use ratatui::backend::Backend;use ratatui::backend::ClearType;use ratatui::backend::WindowSize;use ratatui::buffer::Cell;use ratatui::layout::Position;use ratatui::layout::Size;/// This wraps a CrosstermBackend and a vt100::Parser to mock/// a "real" terminal.////// Importantly, this wrapper avoids calling any crossterm methods/// which write to stdout regardless of the writer. This includes:/// - getting the terminal size/// - getting the cursor positionpub struct VT100Backend { crossterm_backend: CrosstermBackend<vt100::Parser>,}impl VT100Backend { /// Creates a new `TestBackend` with the specified width and height. pub fn new(width: u16, height: u16) -> Self { crossterm::style::force_color_output(true); Self { crossterm_backend: CrosstermBackend::new(vt100::Parser::new(height, width, 0)), } } pub fn vt100(&self) -> &vt100::Parser { self.crossterm_backend.writer() }}impl Write for VT100Backend { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.crossterm_backend.writer_mut().write(buf) } fn flush(&mut self) -> io::Result<()> { self.crossterm_backend.writer_mut().flush() }}impl fmt::Display for VT100Backend { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.crossterm_backend.writer().screen().contents()) }}impl Backend for VT100Backend { fn draw<'a, I>(&mut self, content: I) -> io::Result<()> where I: Iterator<Item = (u16, u16, &'a Cell)>, { self.crossterm_backend.draw(content)?; Ok(()) } fn hide_cursor(&mut self) -> io::Result<()> { self.crossterm_backend.hide_cursor()?; Ok(()) } fn show_cursor(&mut self) -> io::Result<()> { self.crossterm_backend.show_cursor()?; Ok(()) } fn get_cursor_position(&mut self) -> io::Result<Position> { Ok(self.vt100().screen().cursor_position().into()) } fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> { self.crossterm_backend.set_cursor_position(position) } fn clear(&mut self) -> io::Result<()> { self.crossterm_backend.clear() } fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> { self.crossterm_backend.clear_region(clear_type) } fn append_lines(&mut self, line_count: u16) -> io::Result<()> { self.crossterm_backend.append_lines(line_count) } fn size(&self) -> io::Result<Size> { let (rows, cols) = self.vt100().screen().size(); Ok(Size::new(cols, rows)) } fn window_size(&mut self) -> io::Result<WindowSize> { Ok(WindowSize { columns_rows: self.vt100().screen().size().into(), // Arbitrary size, we don't rely on this in testing. pixels: Size { width: 640, height: 480, }, }) } fn flush(&mut self) -> io::Result<()> { self.crossterm_backend.writer_mut().flush() } fn scroll_region_up(&mut self, region: std::ops::Range<u16>, scroll_by: u16) -> io::Result<()> { self.crossterm_backend.scroll_region_up(region, scroll_by) } fn scroll_region_down( &mut self, region: std::ops::Range<u16>, scroll_by: u16, ) -> io::Result<()> { self.crossterm_backend.scroll_region_down(region, scroll_by) }}