/** * Cell geometry resolution. * * Getting this right is the difference between a clean render and a flickering * one: the cell size decides how many rows an image occupies, and pi's TUI * falls back to a full clear-and-redraw whenever a reserved image block does * not fit where it expected. * * Measured on this setup: * bare Ghostty -> CSI 16 t replies "\x1b[6;21;10t" (21px tall, 10px wide) * Herdr pane -> CSI 16 t replies NOTHING; herdr answers pane.graphics.info * * Herdr multiplexes the pty and does not forward the size report, so a pane * that trusted CSI 16 t silently kept pi-tui's 9x18 default and mis-sized every * image. Ask herdr directly when we are inside a pane. */ import { herdrRequest, isHerdrPane } from "./herdr-socket.ts"; import type { CellSize } from "./placeholder.ts"; /** pi-tui's built-in default. Only used when every probe fails. */ export const FALLBACK_CELL: CellSize = { widthPx: 9, heightPx: 18 }; /** Parse a CSI 16 t reply: ESC [ 6 ; ; t */ export function parseCellSizeReport(reply: string): CellSize | null { const match = /\x1b\[6;(\d+);(\d+)t/.exec(reply); if (!match) return null; const heightPx = Number(match[1]); const widthPx = Number(match[2]); if (!Number.isFinite(widthPx) || !Number.isFinite(heightPx)) return null; if (widthPx <= 0 || heightPx <= 0) return null; return { widthPx, heightPx }; } async function fromHerdr(): Promise { const paneId = process.env.HERDR_PANE_ID; if (!paneId) return null; try { const result = await herdrRequest("pane.graphics.info", { pane_id: paneId }); const widthPx = Number(result.cell_width_px ?? 0); const heightPx = Number(result.cell_height_px ?? 0); // Herdr reports 0 when the attached client has no graphics support. A // zero here means frames would be accepted and silently dropped, so it // must be treated as "unknown", not as a size. if (widthPx > 0 && heightPx > 0) return { widthPx, heightPx }; } catch { // Fall through to the terminal probe. } return null; } /** * Resolve cell size once per session. * * `tuiCellSize` is pi-tui's own value (it queries the terminal at startup and * keeps the answer). We prefer it over re-probing because probing means writing * to the tty and reading stdin back, which races pi's input handling. */ export async function resolveCellSize(tuiCellSize?: CellSize): Promise { if (isHerdrPane()) { const fromPane = await fromHerdr(); if (fromPane) return fromPane; } // Trust pi-tui only if it actually got a report. It exposes the 9x18 default // indistinguishably from a real answer, so treat exactly-default as unknown // when we have no other evidence. A real 9x18 terminal loses nothing: it // gets 9x18 back from the fallback anyway. if ( tuiCellSize && tuiCellSize.widthPx > 0 && tuiCellSize.heightPx > 0 && !(tuiCellSize.widthPx === FALLBACK_CELL.widthPx && tuiCellSize.heightPx === FALLBACK_CELL.heightPx) ) { return tuiCellSize; } return FALLBACK_CELL; }