package text
import (
"fmt"
"strconv"
"strings"
)
const Escape = '\x1b'
type EscapeState struct {
Bold bool
Dim bool
Italic bool
Underlined bool
Blink bool
Reverse bool
Hidden bool
CrossedOut bool
FgColor Color
BgColor Color
}
type Color interface {
Codes() []string
}
func (es *EscapeState) Witness(s string) {
inEscape := false
var start int
runes := []rune(s)
for i, r := range runes {
if r == Escape {
inEscape = true
start = i
continue
}
if inEscape {
if r == 'm' {
inEscape = false
es.witnessCode(string(runes[start+1 : i]))
}
continue
}
}
}
func (es *EscapeState) witnessCode(s string) {
if s == "" {
return
}
if s == "[" {
es.reset()
return
}
if len(s) < 2 {
return
}
if s[0] != '[' {
return
}
s = s[1:]
split := strings.Split(s, ";")
dequeue := func() {
split = split[1:]
}
color := func(ground int) Color {
if len(split) < 1 {
// the whole sequence is broken, ignoring the rest
return nil
}
subCode := split[0]
dequeue()
switch subCode {
case "2":
if len(split) < 3 {
return nil
}
r, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
g, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
b, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
return &ColorRGB{ground: ground, R: r, G: g, B: b}
case "5":
if len(split) < 1 {
return nil
}
index, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
return &Color256{ground: ground, Index: index}
}
return nil
}
for len(split) > 0 {
code, err := strconv.Atoi(split[0])
if err != nil {
return
}
dequeue()
switch {
case code == 0:
es.reset()
case code == 1:
es.Bold = true
case code == 2:
es.Dim = true
case code == 3:
es.Italic = true
case code == 4:
es.Underlined = true
case code == 5:
es.Blink = true
// case code == 6:
case code == 7:
es.Reverse = true
case code == 8:
es.Hidden = true
case code == 9:
es.CrossedOut = true
case code == 21:
es.Bold = false
case code == 22:
es.Dim = false
case code == 23:
es.Italic = false
case code == 24:
es.Underlined = false
case code == 25:
es.Blink = false
// case code == 26:
case code == 27:
es.Reverse = false
case code == 28:
es.Hidden = false
case code == 29:
es.CrossedOut = false
case (code >= 30 && code <= 37) || code == 39 || (code >= 90 && code <= 97):
es.FgColor = ColorIndex(code)
case (code >= 40 && code <= 47) || code == 49 || (code >= 100 && code <= 107):
es.BgColor = ColorIndex(code)
case code == 38:
es.FgColor = color(code)
if es.FgColor == nil {
return
}
case code == 48:
es.BgColor = color(code)
if es.BgColor == nil {
return
}
}
}
}
func (es *EscapeState) reset() {
*es = EscapeState{}
}
func (es *EscapeState) String() string {
var codes []string
if es.Bold {
codes = append(codes, strconv.Itoa(1))
}
if es.Dim {
codes = append(codes, strconv.Itoa(2))
}
if es.Italic {
codes = append(codes, strconv.Itoa(3))
}
if es.Underlined {
codes = append(codes, strconv.Itoa(4))
}
if es.Blink {
codes = append(codes, strconv.Itoa(5))
}
if es.Reverse {
codes = append(codes, strconv.Itoa(7))
}
if es.Hidden {
codes = append(codes, strconv.Itoa(8))
}
if es.CrossedOut {
codes = append(codes, strconv.Itoa(9))
}
if es.FgColor != nil {
codes = append(codes, es.FgColor.Codes()...)
}
if es.BgColor != nil {
codes = append(codes, es.BgColor.Codes()...)
}
if len(codes) == 0 {
return "\x1b[0m"
}
return fmt.Sprintf("\x1b[%sm", strings.Join(codes, ";"))
}
type ColorIndex int
func (cInd ColorIndex) Codes() []string {
return []string{strconv.Itoa(int(cInd))}
}
type Color256 struct {
ground int
Index int
}
func (c256 Color256) Codes() []string {
return []string{
strconv.Itoa(c256.ground),
"5",
strconv.Itoa(c256.Index),
}
}
type ColorRGB struct {
ground int
R, G, B int
}
func (cRGB ColorRGB) Codes() []string {
return []string{
strconv.Itoa(cRGB.ground),
"2",
strconv.Itoa(cRGB.R),
strconv.Itoa(cRGB.G),
strconv.Itoa(cRGB.B),
}
}