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 position pub struct VT100Backend { crossterm_backend: CrosstermBackend, } impl VT100Backend { /// Creates a new `TestBackend` with the specified width and height. pub fn new(width: u16, height: u16) -> Self { 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 { 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, { 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 { Ok(self.vt100().screen().cursor_position().into()) } fn set_cursor_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 { let (rows, cols) = self.vt100().screen().size(); Ok(Size::new(cols, rows)) } fn window_size(&mut self) -> io::Result { 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, scroll_by: u16) -> io::Result<()> { self.crossterm_backend.scroll_region_up(region, scroll_by) } fn scroll_region_down( &mut self, region: std::ops::Range, scroll_by: u16, ) -> io::Result<()> { self.crossterm_backend.scroll_region_down(region, scroll_by) } }