import { paintFrameBackgroundClears, paintFrameBackgroundLines, padFrameRows, resolveFrameBackgroundAnsi } from "../theme/frame-background.js"; import { getOriginalTuiMethod, rememberTuiMethodWrapper } from "./tui-proxy-original.js"; import { profileCount } from "./profiler.js"; const PATCHED = Symbol.for("pi-droid-styling.render-frame-background.patched"); const WRITE_PATCHED = Symbol.for("pi-droid-styling.render-frame-background.write-patched"); type ApplyLineResetsFunction = (lines: string[]) => string[]; type TuiLike = { applyLineResets?: ApplyLineResetsFunction; terminal?: TerminalLike; [PATCHED]?: FrameBackgroundApplyLineResetsPatch | ApplyLineResetsFunction | boolean; }; type TerminalWriteFunction = (data: string) => unknown; type TerminalLike = { columns?: number; rows?: number; write?: TerminalWriteFunction; [WRITE_PATCHED]?: FrameBackgroundWritePatch; }; type FrameBackgroundApplyLineResetsPatch = { theme: any; wrapper: ApplyLineResetsFunction; }; type FrameBackgroundWritePatch = { theme: any; wrapper: TerminalWriteFunction; }; function readRows(tui: TuiLike): number { const rows = tui.terminal?.rows; return typeof rows === "number" && Number.isFinite(rows) ? Math.max(1, Math.floor(rows)) : 0; } function readColumns(tui: TuiLike): number { const columns = tui.terminal?.columns; return typeof columns === "number" && Number.isFinite(columns) ? Math.max(1, Math.floor(columns)) : 0; } function installFrameBackgroundClearWriter(tui: TuiLike, theme: any): void { const terminal = tui.terminal; if (!terminal || typeof terminal.write !== "function") return; const existingPatch = terminal[WRITE_PATCHED]; if (existingPatch && terminal.write === existingPatch.wrapper) { existingPatch.theme = theme; return; } const originalWrite = terminal.write.bind(terminal); const patch: FrameBackgroundWritePatch = { theme, wrapper(data: string): unknown { const text = String(data); const bgAnsi = resolveFrameBackgroundAnsi(patch.theme); return originalWrite(bgAnsi ? paintFrameBackgroundClears(text, bgAnsi) : text); }, }; terminal[WRITE_PATCHED] = patch; terminal.write = patch.wrapper; } export function installRenderFrameBackground(tui: TuiLike, theme: any): void { if (process.env.PI_DROID_RENDER_FRAME_BG === "0") return; if (!tui || typeof tui.applyLineResets !== "function") return; installFrameBackgroundClearWriter(tui, theme); const existingPatch = tui[PATCHED]; if (existingPatch && typeof existingPatch === "object" && tui.applyLineResets === existingPatch.wrapper) { existingPatch.theme = theme; return; } if (typeof existingPatch === "function" && tui.applyLineResets === existingPatch) return; if (existingPatch === true && tui.applyLineResets.name === "droidFrameBackgroundApplyLineResets") return; const originalApplyLineResets = getOriginalTuiMethod(tui, "applyLineResets"); const patch: FrameBackgroundApplyLineResetsPatch = { theme, wrapper: function droidFrameBackgroundApplyLineResets(this: TuiLike, lines: string[]): string[] { const bgAnsi = resolveFrameBackgroundAnsi(patch.theme); if (!bgAnsi) return originalApplyLineResets.call(this, lines) as string[]; const frameLines = padFrameRows(lines, readRows(this)); const resetLines = originalApplyLineResets.call(this, frameLines) as string[]; profileCount("render.frameBackground.row", resetLines.length); return paintFrameBackgroundLines(resetLines, bgAnsi, readColumns(this)); }, }; tui[PATCHED] = patch; tui.applyLineResets = patch.wrapper; rememberTuiMethodWrapper(tui, "applyLineResets", patch.wrapper); }