import Bliku.Tui.Primitives
import Bliku.Tui.Model
import Bliku.Tui.RenderInput
import Bliku.Tui.Terminal
import Bliku.Widget.Popup
import Bliku.Widget.List
import Bliku.Widget.Prompt
namespace Bliku.Tui
open Bliku.Tui.Terminal
def shouldRenderMessageAsFloat (msg : String) : Bool :=
let m := msg.trimAscii.toString
if m.isEmpty then
false
else
m.startsWith "Error" ||
m.startsWith "Cannot" ||
m.startsWith "Invalid" ||
m.startsWith "Unknown" ||
m.startsWith "No " ||
m.startsWith "Empty " ||
m.startsWith "Usage:" ||
m.startsWith "failed" ||
m.startsWith "Failed" ||
m.contains "not found"
def renderStatusBar (input : RenderInput) : String :=
let plain := input.messageLine.trimAscii.toString
let floatMsg := shouldRenderMessageAsFloat plain
match input.commandLine with
| some cmd => Bliku.Widget.renderPrompt { leader := cmd.leader, text := cmd.text, cursorCol := cmd.cursorCol }
| none => if floatMsg then input.statusLine else plain
structure FloatingOverlayLayout where
top : Nat
left : Nat
innerWidth : Nat
titleRows : Nat
contentRows : Nat
deriving Inhabited
def computeFloatingOverlayLayout (rows cols : Nat) (overlay : OverlayView) : Option FloatingOverlayLayout := Id.run do
let availableRows := if rows > 1 then rows - 1 else rows
if cols < 8 || availableRows < 4 then
return none
let box := Bliku.Widget.renderPopupBox { title := overlay.title, lines := overlay.lines, maxWidth := overlay.maxWidth }
let maxInnerWidth := if cols > 8 then cols - 8 else 1
let innerWidth := max 1 (min box.innerWidth maxInnerWidth)
let maxContentRows := if availableRows > box.titleRows + 2 then availableRows - box.titleRows - 2 else 0
if maxContentRows == 0 then return none
let contentRows := max 1 (min box.contentRows maxContentRows)
let boxHeight := contentRows + box.titleRows + 2
let boxWidth := innerWidth + 4
let top := (availableRows - boxHeight) / 2
let left := (cols - boxWidth) / 2
return some { top, left, innerWidth, titleRows := box.titleRows, contentRows }
def renderFloatingOverlay (rows cols : Nat) (overlay : OverlayView) : Array String := Id.run do
let some layout := computeFloatingOverlayLayout rows cols overlay | return #[]
let popup := Bliku.Widget.renderPopupBox { title := overlay.title, lines := overlay.lines, maxWidth := layout.innerWidth }
let mut out : Array String := #[]
for i in [0:popup.lines.size] do
out := out.push (moveCursorStr (layout.top + i) layout.left)
out := out.push popup.lines[i]!
out
def messageOverlayForState (input : RenderInput) : Option OverlayView :=
let plain := input.messageLine.trimAscii.toString
let floatMsg := shouldRenderMessageAsFloat plain
if input.overlay.isNone && input.commandLine.isNone && floatMsg then
if plain.isEmpty then none
else some { title := "Message", lines := (plain.splitOn "\n").toArray }
else
none
def renderCompletionPopup (_rows cols : Nat) (config : UiConfig) (popup : CompletionView) (cursorPos : Option (Nat × Nat)) : Array String := Id.run do
let some (cursorRow, cursorCol) := cursorPos | return #[]
let items := popup.items.map (fun it => Bliku.Widget.ListItem.mk it.label)
let borderCh := if config.hSplitStr.isEmpty then '-' else config.hSplitStr.toList[0]!
let box := Bliku.Widget.renderListBox {
items := items
selected := popup.selected
maxVisible := 8
borderLeft := config.vSplitStr
borderRight := config.vSplitStr
borderFill := borderCh
selectedStyle := "\x1b[7m"
resetStyle := config.resetStyle
}
if box.lines.isEmpty then
return #[]
let top := cursorRow + 1
let left := if cursorCol + box.width < cols then cursorCol else (cols - box.width)
let mut out : Array String := #[]
for i in [0:box.lines.size] do
out := out.push (moveCursorStr (top + i) left)
out := out.push box.lines[i]!
out
end Bliku.Tui