package tui import ( "strings" "github.com/hinshun/vt10x" ) // VTRender emulates a terminal of size cols×rows, feeds raw into it, // and returns the resulting screen as plain text preserving the visual layout. // // Unlike strip_ansi which removes escape sequences from sequential output, // VTRender correctly handles TUIs that use absolute cursor positioning // (ESC[row;colH, ESC[colG, etc.) by maintaining a 2D grid and reconstructing // real spaces between columns. // // Defaults: rows<=0 → 40, cols<=0 → 120. // Trailing spaces on each line are trimmed. Trailing empty lines are removed. func VTRender(raw string, rows, cols int) string { if rows <= 0 { rows = 40 } if cols <= 0 { cols = 120 } // Create a fresh terminal emulator for each call — no shared state. term := vt10x.New(vt10x.WithSize(cols, rows)) term.Write([]byte(raw)) //nolint:errcheck // Write on vt10x never returns a meaningful error // String() returns all rows joined by '\n', one row per terminal line. // Each row is exactly `cols` runes wide (padded with NUL/space for empty cells). raw = term.String() lines := strings.Split(raw, "\n") // Trim trailing spaces from every line (cells that were never written // contain NUL '\x00' in some versions, so we trim both NUL and space). for i, line := range lines { // Replace NUL characters (unwritten cells) with spaces first. line = strings.ReplaceAll(line, "\x00", " ") lines[i] = strings.TrimRight(line, " ") } // Remove trailing empty lines — the TUI probably only used the top portion // of the grid. Keep intermediate empty lines (real visual separators). last := len(lines) - 1 for last >= 0 && lines[last] == "" { last-- } lines = lines[:last+1] return strings.Join(lines, "\n") }