Files
dev/tmp.md
T

3.7 KiB
Raw Blame History

Proposed Changes: Reliable image sizing for ratatui-image

This approach avoids Picker::from_query_stdio() races by measuring terminal cell size via /dev/tty once and feeding that to Picker::from_fontsize. It removes the startup timing constraint and yields accurate image scaling.

1) codex-rs/tui/src/terminal_info.rs

Add a helper that returns the exact cell size in pixels (width, height). Place it near the bottom, above DEFAULT_CELL_ASPECT_RATIO so it can reuse internal helpers.

pub fn get_cell_size_pixels() -> Option<(u16, u16)> {
    // Open /dev/tty for reading and writing
    let mut tty_w = OpenOptions::new().write(true).open("/dev/tty").ok()?;
    let mut tty_r = OpenOptions::new().read(true).open("/dev/tty").ok()?;

    // Try direct cell size query (CSI 16 t) -> expect: CSI 6;height;width t
    tty_w.write_all(b"\x1b[16t").ok()?;
    tty_w.flush().ok()?;
    if let Some(reply) = read_reply(&mut tty_r, Duration::from_millis(100)) {
        if let Some((kind, height, width)) = parse_three_nums(&reply) {
            if kind == 6 && width > 0 && height > 0 {
                return Some((width as u16, height as u16));
            }
        }
    }

    // Fallback: window size in pixels (CSI 14 t) -> CSI 4;win_h;win_w t
    tty_w.write_all(b"\x1b[14t").ok()?;
    tty_w.flush().ok()?;
    let (mut win_h, mut win_w) = (0u32, 0u32);
    if let Some(reply) = read_reply(&mut tty_r, Duration::from_millis(100)) {
        if let Some((kind, h, w)) = parse_three_nums(&reply) {
            if kind == 4 {
                win_h = h;
                win_w = w;
            }
        }
    }

    // Text area in characters (CSI 18 t) -> CSI 8;rows;cols t
    tty_w.write_all(b"\x1b[18t").ok()?;
    tty_w.flush().ok()?;
    let (mut rows, mut cols) = (0u32, 0u32);
    if let Some(reply) = read_reply(&mut tty_r, Duration::from_millis(100)) {
        if let Some((kind, r, c)) = parse_three_nums(&reply) {
            if kind == 8 {
                rows = r;
                cols = c;
            }
        }
    }

    if win_h > 0 && win_w > 0 && rows > 0 && cols > 0 {
        let cell_w = (win_w as f32 / cols as f32).round() as u16;
        let cell_h = (win_h as f32 / rows as f32).round() as u16;
        if cell_w > 0 && cell_h > 0 {
            return Some((cell_w, cell_h));
        }
    }
    None
}

2) codex-rs/tui/src/chatwidget.rs

  • Add a cached cell-size field to ChatWidget:
// Cached cell size (width,height) in pixels
cached_cell_size: std::cell::OnceCell<(u16, u16)>,
  • Add a helper to compute the picker font size once, with fallback:
impl ChatWidget<'_> {
    fn measured_font_size(&self) -> (u16, u16) {
        let default_guess = if std::env::var("TERM_PROGRAM").unwrap_or_default() == "iTerm.app" {
            (7, 15)
        } else {
            (8, 16)
        };
        *self.cached_cell_size.get_or_init(|| {
            crate::terminal_info::get_cell_size_pixels().unwrap_or(default_guess)
        })
    }
    // ...
}
  • Use measured_font_size() when creating the Picker (two places):
  1. In the branch where we create a new picker (around the first Picker::from_fontsize(...)):
// Replace the conditional guessed font size block with
let picker = Picker::from_fontsize(self.measured_font_size());
  1. In render_screenshot_highlevel, when cached_picker is None:
// Replace the guessed font size block with
*cached_picker = Some(Picker::from_fontsize(self.measured_font_size()));

Notes

  • No changes to the event thread ordering are required.
  • This approach avoids races with stdin and makes screenshot images fill the intended area using Resize::Fit.
  • Keep the existing aspect‑ratio calculation in popup_area_top_right; it remains correct.