165 lines
3.5 KiB
Go
165 lines
3.5 KiB
Go
package internal
|
|
|
|
import (
|
|
"log/slog"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// AnsiColorKind says how an AnsiColor is given.
|
|
type AnsiColorKind uint8
|
|
|
|
const (
|
|
AnsiDefault AnsiColorKind = iota
|
|
AnsiIndexed
|
|
AnsiRGB
|
|
)
|
|
|
|
// AnsiColor is a terminal color: default, a 256-color index, or true color.
|
|
type AnsiColor struct {
|
|
Kind AnsiColorKind
|
|
Index uint8
|
|
R, G, B uint8
|
|
}
|
|
|
|
// AnsiStyle is the SGR state a span is drawn with.
|
|
type AnsiStyle struct {
|
|
FG, BG AnsiColor
|
|
Bold, Dim, Italic, Underline, Inverse bool
|
|
Strike bool
|
|
}
|
|
|
|
// AnsiSpan is a run of text in one style.
|
|
type AnsiSpan struct {
|
|
Text string
|
|
Style AnsiStyle
|
|
}
|
|
|
|
// ParseAnsi splits s into styled spans from st; unknown escapes stay literal.
|
|
func ParseAnsi(s string, st AnsiStyle) ([]AnsiSpan, AnsiStyle) {
|
|
var spans []AnsiSpan
|
|
var b strings.Builder
|
|
flush := func() {
|
|
if b.Len() > 0 {
|
|
spans = append(spans, AnsiSpan{b.String(), st})
|
|
b.Reset()
|
|
}
|
|
}
|
|
for i := 0; i < len(s); {
|
|
if s[i] != 0x1b || i+1 >= len(s) || s[i+1] != '[' {
|
|
b.WriteByte(s[i])
|
|
i++
|
|
continue
|
|
}
|
|
j := i + 2
|
|
for j < len(s) && (s[j] < 0x40 || s[j] > 0x7e) {
|
|
j++
|
|
}
|
|
if j >= len(s) {
|
|
b.WriteString(s[i:])
|
|
break
|
|
}
|
|
if s[j] != 'm' {
|
|
slog.Debug("mujica: ansi unsupported sequence kept", "seq", strconv.Quote(s[i:j+1]))
|
|
b.WriteString(s[i : j+1])
|
|
i = j + 1
|
|
continue
|
|
}
|
|
flush()
|
|
st = applySGR(st, s[i+2:j])
|
|
i = j + 1
|
|
}
|
|
flush()
|
|
return spans, st
|
|
}
|
|
|
|
func applySGR(st AnsiStyle, params string) AnsiStyle {
|
|
if params == "" {
|
|
return AnsiStyle{}
|
|
}
|
|
codes := strings.Split(params, ";")
|
|
for k := 0; k < len(codes); k++ {
|
|
n, err := strconv.Atoi(codes[k])
|
|
if err != nil {
|
|
slog.Debug("mujica: ansi bad SGR parameter", "params", params)
|
|
return st
|
|
}
|
|
switch {
|
|
case n == 0:
|
|
st = AnsiStyle{}
|
|
case n == 1:
|
|
st.Bold = true
|
|
case n == 2:
|
|
st.Dim = true
|
|
case n == 3:
|
|
st.Italic = true
|
|
case n == 4:
|
|
st.Underline = true
|
|
case n == 7:
|
|
st.Inverse = true
|
|
case n == 9:
|
|
st.Strike = true
|
|
case n == 22:
|
|
st.Bold, st.Dim = false, false
|
|
case n == 23:
|
|
st.Italic = false
|
|
case n == 24:
|
|
st.Underline = false
|
|
case n == 27:
|
|
st.Inverse = false
|
|
case n == 29:
|
|
st.Strike = false
|
|
case n >= 30 && n <= 37:
|
|
st.FG = AnsiColor{Kind: AnsiIndexed, Index: uint8(n - 30)}
|
|
case n >= 90 && n <= 97:
|
|
st.FG = AnsiColor{Kind: AnsiIndexed, Index: uint8(n - 90 + 8)}
|
|
case n == 39:
|
|
st.FG = AnsiColor{}
|
|
case n >= 40 && n <= 47:
|
|
st.BG = AnsiColor{Kind: AnsiIndexed, Index: uint8(n - 40)}
|
|
case n >= 100 && n <= 107:
|
|
st.BG = AnsiColor{Kind: AnsiIndexed, Index: uint8(n - 100 + 8)}
|
|
case n == 49:
|
|
st.BG = AnsiColor{}
|
|
case n == 38 || n == 48:
|
|
c, used, ok := extendedColor(codes[k+1:])
|
|
if !ok {
|
|
slog.Debug("mujica: ansi bad extended color", "params", params)
|
|
return st
|
|
}
|
|
if n == 38 {
|
|
st.FG = c
|
|
} else {
|
|
st.BG = c
|
|
}
|
|
k += used
|
|
}
|
|
}
|
|
return st
|
|
}
|
|
|
|
// extendedColor reads "5;n" or "2;r;g;b" after 38 or 48.
|
|
func extendedColor(rest []string) (AnsiColor, int, bool) {
|
|
num := func(i int) (uint8, bool) {
|
|
if i >= len(rest) {
|
|
return 0, false
|
|
}
|
|
v, err := strconv.Atoi(rest[i])
|
|
return uint8(v), err == nil && v >= 0 && v <= 255
|
|
}
|
|
if len(rest) == 0 {
|
|
return AnsiColor{}, 0, false
|
|
}
|
|
switch rest[0] {
|
|
case "5":
|
|
v, ok := num(1)
|
|
return AnsiColor{Kind: AnsiIndexed, Index: v}, 2, ok
|
|
case "2":
|
|
r, ok1 := num(1)
|
|
g, ok2 := num(2)
|
|
b, ok3 := num(3)
|
|
return AnsiColor{Kind: AnsiRGB, R: r, G: g, B: b}, 4, ok1 && ok2 && ok3
|
|
}
|
|
return AnsiColor{}, 0, false
|
|
}
|