namespace Bliku.Tui.Terminal
/-- Cached terminal size to avoid spawning `stty` every frame. -/
initialize terminalSizeCache : IO.Ref (Option (Nat × Nat)) ← IO.mkRef none
private def parseEnvSize : IO (Option (Nat × Nat)) := do
let rows? ← IO.getEnv "LINES"
let cols? ← IO.getEnv "COLUMNS"
match (rows?.bind String.toNat?, cols?.bind String.toNat?) with
| (some r, some c) =>
if r > 0 && c > 0 then
return some (r, c)
else
return none
| _ => return none
/-- Get terminal size (rows, cols). -/
def getWindowSize : IO (Nat × Nat) := do
if let some sz ← terminalSizeCache.get then
return sz
if let some sz ← parseEnvSize then
terminalSizeCache.set (some sz)
return sz
try
let out ← IO.Process.run { cmd := "stty", args := #["-F", "/dev/tty", "size"] }
let parts := out.trimAscii.toString.splitOn " "
let sz := match parts with
| [rows, cols] =>
match (rows.toNat?, cols.toNat?) with
| (some r, some c) =>
if r > 0 && c > 0 then (r, c) else (24, 80)
| _ => (24, 80)
| _ => (24, 80)
terminalSizeCache.set (some sz)
return sz
catch _ =>
return (24, 80)
def moveCursorStr (row col : Nat) : String :=
s!"\x1b[{row + 1};{col + 1}H"
def hideCursorStr : String := "\x1b[?25l"
def showCursorStr : String := "\x1b[?25h"
def clearLineStr : String := "\x1b[K"
def homeCursorStr : String := "\x1b[H"
def clearScreenStr : String := "\x1b[2J"
end Bliku.Tui.Terminal