summaryrefslogtreecommitdiff
path: root/Omni/Log/Terminal.hs
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-11-15 08:04:26 -0500
committerBen Sima <ben@bsima.me>2025-11-15 08:04:26 -0500
commit335a1a4692dc921d6b0b6fab0e93c30063b622d5 (patch)
treec339a247e12dad2087604af5d5587bb3867e0fb8 /Omni/Log/Terminal.hs
parentc435b9a8a87f558cc76c761d6d4ba7a9ae69740c (diff)
feat(bild): adaptive terminal output with width detection
- Add Omni/Log/Terminal module to detect terminal capabilities - Implement 3 output modes: RichMultiLine (≥80 cols), SingleLine (40-79), SimpleFallback (<40 or dumb terminals) - Terminal width auto-detection via System.Console.ANSI.getTerminalSize - Text truncation with ellipsis to prevent line wrapping - Manual override via BILD_OUTPUT_MODE env var (simple|single|rich|auto) Fixes UI corruption on small terminals (mobile SSH, narrow windows). Wide terminals keep existing multi-line behavior but with truncation. Design doc: _/llm/CONCURRENT_LOG_DESIGN.md
Diffstat (limited to 'Omni/Log/Terminal.hs')
-rw-r--r--Omni/Log/Terminal.hs78
1 files changed, 78 insertions, 0 deletions
diff --git a/Omni/Log/Terminal.hs b/Omni/Log/Terminal.hs
new file mode 100644
index 0000000..1230eb3
--- /dev/null
+++ b/Omni/Log/Terminal.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Terminal detection and output mode selection
+module Omni.Log.Terminal
+ ( TerminalInfo (..),
+ OutputMode (..),
+ detectTerminal,
+ truncateToWidth,
+ )
+where
+
+import Alpha
+import qualified Data.Text as Text
+import qualified System.Console.ANSI as ANSI
+import qualified System.Environment as Env
+
+data OutputMode
+ = RichMultiLine -- Wide terminals (≥80 cols)
+ | SingleLine -- Narrow terminals (40-79 cols)
+ | SimpleFallback -- Very narrow (<40) or dumb terminals
+ deriving (Eq, Show)
+
+data TerminalInfo = TerminalInfo
+ { tiWidth :: Int,
+ tiHeight :: Int,
+ tiMode :: OutputMode,
+ tiSupportsANSI :: Bool
+ }
+ deriving (Eq, Show)
+
+detectTerminal :: IO TerminalInfo
+detectTerminal = do
+ term <- Env.lookupEnv "TERM"
+ area <- Env.lookupEnv "AREA"
+ modeOverride <- Env.lookupEnv "BILD_OUTPUT_MODE"
+
+ -- Check if we support ANSI
+ let supportsANSI = case (term, area) of
+ (Just "dumb", _) -> False
+ (_, Just "Live") -> False -- production logs
+ (Nothing, _) -> False
+ _ -> True
+
+ -- Get terminal size
+ mSize <- ANSI.getTerminalSize
+ let (width, height) = case mSize of
+ Just (h, w) -> (w, h)
+ Nothing -> (80, 24) -- sensible default
+
+ -- Determine mode
+ let autoMode
+ | not supportsANSI = SimpleFallback
+ | width < 40 = SimpleFallback
+ | width < 80 = SingleLine
+ | otherwise = RichMultiLine
+
+ -- Allow manual override
+ let mode = case modeOverride of
+ Just "simple" -> SimpleFallback
+ Just "single" -> SingleLine
+ Just "rich" -> RichMultiLine
+ _ -> autoMode
+
+ pure
+ TerminalInfo
+ { tiWidth = width,
+ tiHeight = height,
+ tiMode = mode,
+ tiSupportsANSI = supportsANSI
+ }
+
+-- | Truncate text to fit width with ellipsis
+truncateToWidth :: Int -> Text -> Text
+truncateToWidth maxWidth text
+ | Text.length text <= maxWidth = text
+ | maxWidth <= 3 = Text.take maxWidth text
+ | otherwise = Text.take (maxWidth - 3) text <> "..."