262 lines
5.8 KiB
Go
262 lines
5.8 KiB
Go
package menu
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type TextStyle int
|
|
|
|
const (
|
|
Bold TextStyle = 1 << 0
|
|
Faint TextStyle = 1 << 1
|
|
Italic TextStyle = 1 << 2
|
|
Blinking TextStyle = 1 << 3
|
|
Inversed TextStyle = 1 << 4
|
|
Invisible TextStyle = 1 << 5
|
|
Underlined TextStyle = 1 << 6
|
|
Strikethrough TextStyle = 1 << 7
|
|
)
|
|
|
|
type StyledText struct {
|
|
Label string
|
|
FgCol *Col
|
|
BgCol *Col
|
|
Style TextStyle
|
|
}
|
|
|
|
func (s *StyledText) Bold() bool {
|
|
return s.Style&Bold == Bold
|
|
}
|
|
|
|
func (s *StyledText) Faint() bool {
|
|
return s.Style&Faint == Faint
|
|
}
|
|
|
|
func (s *StyledText) Italic() bool {
|
|
return s.Style&Italic == Italic
|
|
}
|
|
|
|
func (s *StyledText) Blinking() bool {
|
|
return s.Style&Blinking == Blinking
|
|
}
|
|
|
|
func (s *StyledText) Inversed() bool {
|
|
return s.Style&Inversed == Inversed
|
|
}
|
|
|
|
func (s *StyledText) Invisible() bool {
|
|
return s.Style&Invisible == Invisible
|
|
}
|
|
|
|
func (s *StyledText) Underlined() bool {
|
|
return s.Style&Underlined == Underlined
|
|
}
|
|
|
|
func (s *StyledText) Strikethrough() bool {
|
|
return s.Style&Strikethrough == Strikethrough
|
|
}
|
|
|
|
var ansiColorMap = map[string]map[string]*Col{
|
|
"Normal": {
|
|
"30": Cols[0],
|
|
"31": Cols[1],
|
|
"32": Cols[2],
|
|
"33": Cols[3],
|
|
"34": Cols[4],
|
|
"35": Cols[5],
|
|
"36": Cols[6],
|
|
"37": Cols[7],
|
|
},
|
|
"Bold": {
|
|
"30": Cols[8],
|
|
"31": Cols[9],
|
|
"32": Cols[10],
|
|
"33": Cols[11],
|
|
"34": Cols[12],
|
|
"35": Cols[13],
|
|
"36": Cols[14],
|
|
"37": Cols[15],
|
|
},
|
|
"Faint": {
|
|
"30": Cols[0],
|
|
"31": Cols[1],
|
|
"32": Cols[2],
|
|
"33": Cols[3],
|
|
"34": Cols[4],
|
|
"35": Cols[5],
|
|
"36": Cols[6],
|
|
"37": Cols[7],
|
|
},
|
|
}
|
|
|
|
func ParseANSI(input string) ([]*StyledText, error) {
|
|
var result []*StyledText
|
|
invalid := fmt.Errorf("invalid ansi string")
|
|
missingTerminator := fmt.Errorf("missing escape terminator 'm'")
|
|
invalidTrueColorSequence := fmt.Errorf("invalid TrueColor sequence")
|
|
invalid256ColSequence := fmt.Errorf("invalid 256 colour sequence")
|
|
index := 0
|
|
var currentStyledText *StyledText = &StyledText{}
|
|
|
|
if len(input) == 0 {
|
|
return nil, invalid
|
|
}
|
|
|
|
for {
|
|
// Read all chars to next escape code
|
|
esc := strings.Index(input, "\033[")
|
|
|
|
// If no more esc chars, save what's left and return
|
|
if esc == -1 {
|
|
text := input[index:]
|
|
if len(text) > 0 {
|
|
currentStyledText.Label = text
|
|
result = append(result, currentStyledText)
|
|
}
|
|
return result, nil
|
|
}
|
|
label := input[:esc]
|
|
if len(label) > 0 {
|
|
currentStyledText.Label = label
|
|
result = append(result, currentStyledText)
|
|
currentStyledText = &StyledText{
|
|
Label: "",
|
|
FgCol: currentStyledText.FgCol,
|
|
BgCol: currentStyledText.BgCol,
|
|
Style: currentStyledText.Style,
|
|
}
|
|
}
|
|
input = input[esc:]
|
|
// skip
|
|
input = input[2:]
|
|
|
|
// Read in params
|
|
endesc := strings.Index(input, "m")
|
|
if endesc == -1 {
|
|
return nil, missingTerminator
|
|
}
|
|
paramText := input[:endesc]
|
|
input = input[endesc+1:]
|
|
params := strings.Split(paramText, ";")
|
|
colourMap := ansiColorMap["Normal"]
|
|
skip := 0
|
|
for index, param := range params {
|
|
if skip > 0 {
|
|
skip--
|
|
continue
|
|
}
|
|
switch param {
|
|
case "0":
|
|
// Reset styles
|
|
if len(params) == 1 {
|
|
if len(currentStyledText.Label) > 0 {
|
|
result = append(result, currentStyledText)
|
|
currentStyledText = &StyledText{
|
|
Label: "",
|
|
FgCol: currentStyledText.FgCol,
|
|
BgCol: currentStyledText.BgCol,
|
|
Style: currentStyledText.Style,
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
currentStyledText.Style = 0
|
|
currentStyledText.FgCol = nil
|
|
currentStyledText.BgCol = nil
|
|
case "1":
|
|
// Bold
|
|
colourMap = ansiColorMap["Bold"]
|
|
currentStyledText.Style |= Bold
|
|
case "2":
|
|
// Dim/Feint
|
|
colourMap = ansiColorMap["Faint"]
|
|
currentStyledText.Style |= Faint
|
|
case "3":
|
|
// Italic
|
|
currentStyledText.Style |= Italic
|
|
case "4":
|
|
// Underlined
|
|
currentStyledText.Style |= Underlined
|
|
case "5":
|
|
// Blinking
|
|
currentStyledText.Style |= Blinking
|
|
case "7":
|
|
// Inverse
|
|
currentStyledText.Style |= Inversed
|
|
case "8":
|
|
// Invisible
|
|
currentStyledText.Style |= Invisible
|
|
case "9":
|
|
// Strikethrough
|
|
currentStyledText.Style |= Strikethrough
|
|
case "30", "31", "32", "33", "34", "35", "36", "37":
|
|
currentStyledText.FgCol = colourMap[param]
|
|
case "40", "41", "42", "43", "44", "45", "46", "47":
|
|
currentStyledText.BgCol = colourMap[param]
|
|
case "38", "48":
|
|
if len(params)-index < 2 {
|
|
return nil, invalid
|
|
}
|
|
// 256 colours
|
|
if params[index+1] == "5" {
|
|
skip = 2
|
|
colIndexText := params[index+2]
|
|
colIndex, err := strconv.Atoi(colIndexText)
|
|
if err != nil {
|
|
return nil, invalid256ColSequence
|
|
}
|
|
if colIndex < 0 || colIndex > 255 {
|
|
return nil, invalid256ColSequence
|
|
}
|
|
if param == "38" {
|
|
currentStyledText.FgCol = Cols[colIndex]
|
|
continue
|
|
}
|
|
currentStyledText.BgCol = Cols[colIndex]
|
|
continue
|
|
}
|
|
// we must have 4 params left
|
|
if len(params)-index < 4 {
|
|
return nil, invalidTrueColorSequence
|
|
}
|
|
if params[index+1] != "2" {
|
|
return nil, invalidTrueColorSequence
|
|
}
|
|
var r, g, b uint8
|
|
ri, err := strconv.Atoi(params[index+2])
|
|
if err != nil {
|
|
return nil, invalidTrueColorSequence
|
|
}
|
|
gi, err := strconv.Atoi(params[index+3])
|
|
if err != nil {
|
|
return nil, invalidTrueColorSequence
|
|
}
|
|
bi, err := strconv.Atoi(params[index+4])
|
|
if err != nil {
|
|
return nil, invalidTrueColorSequence
|
|
}
|
|
if bi > 255 || gi > 255 || ri > 255 {
|
|
return nil, invalidTrueColorSequence
|
|
}
|
|
if bi < 0 || gi < 0 || ri < 0 {
|
|
return nil, invalidTrueColorSequence
|
|
}
|
|
r = uint8(ri)
|
|
g = uint8(gi)
|
|
b = uint8(bi)
|
|
skip = 4
|
|
colvalue := fmt.Sprintf("#%02x%02x%02x", r, g, b)
|
|
if param == "38" {
|
|
currentStyledText.FgCol = &Col{Hex: colvalue, Rgb: Rgb{r, g, b}}
|
|
continue
|
|
}
|
|
currentStyledText.BgCol = &Col{Hex: colvalue}
|
|
default:
|
|
return nil, invalid
|
|
}
|
|
}
|
|
}
|
|
}
|