mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
3c353a3aca
Codex Unified Exec injects NO_COLOR=1 (and TERM=dumb) into shell tool commands to keep output stable. Crossterm respects NO_COLOR and suppresses ANSI escapes, which breaks our VT100-backed tests that assert on parsed ANSI color output (they see vt100::Color::Default everywhere). Force ANSI color output back on in the VT100 test backend by overriding crossterm's memoized NO_COLOR setting in VT100Backend::new. This keeps Unified Exec behavior unchanged while making the VT100 tests meaningful and deterministic under Codex. > [!WARNING] > it's possible that this might be a race condition problem for this and need to be solved a different way. Feel free to revert if it causes the opposite problem for other tests that assume NOCOLOR is set. If it does then we need to probably add some extra AGENTS.md lines for how to run tests when using unified exec. (this same change was made in tui, so it's probably safe).
126 lines
3.5 KiB
Rust
126 lines
3.5 KiB
Rust
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<vt100::Parser>,
|
|
}
|
|
|
|
impl VT100Backend {
|
|
/// Creates a new `TestBackend` with the specified width and height.
|
|
pub fn new(width: u16, height: u16) -> Self {
|
|
crossterm::style::Colored::set_ansi_color_disabled(false);
|
|
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)
|
|
}
|
|
}
|