78 lines
1.3 KiB
Go
78 lines
1.3 KiB
Go
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()
|
|
}
|