package app import ( "strings" "charm.land/lipgloss/v2" ) // ansiTruncate — potong string ber-ANSI ke lebar tampilan w (sel), aman // terhadap escape sequence; bila terpotong ditutup reset + "…". // Dipakai panel() agar konten tidak pernah wrap (tinggi panel tetap). func ansiTruncate(s string, w int) string { if w <= 0 { return "" } if lipgloss.Width(s) <= w { return s } const ( stNormal = iota stEsc stCSI stOSC stOSCEsc ) state := stNormal width := 0 var b strings.Builder for _, r := range s { switch state { case stNormal: if r == 0x1b { state = stEsc b.WriteRune(r) continue } if width >= w-1 { // sisakan satu sel untuk "…" b.WriteString("\x1b[0m…") return b.String() } b.WriteRune(r) width++ case stEsc: b.WriteRune(r) switch { case r == '[': state = stCSI case r == ']': state = stOSC case r >= 0x20 && r <= 0x2f: // intermediate — tunggu final byte default: state = stNormal } case stCSI: b.WriteRune(r) if r >= 0x40 && r <= 0x7e { state = stNormal } case stOSC: b.WriteRune(r) if r == 0x07 { state = stNormal } else if r == 0x1b { state = stOSCEsc } case stOSCEsc: b.WriteRune(r) if r == '\\' { state = stNormal } else { state = stOSC } } } return b.String() }