Codex Handbook
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)    }}