package cview
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
)
const (
ansiText = iota
ansiEscape
ansiSubstring
ansiControlSequence
)
type ansi struct {
io .Writer
buffer *bytes .Buffer
csiParameter, csiIntermediate *bytes .Buffer
attributes string
state int
}
func ANSIWriter (writer io .Writer ) io .Writer {
return &ansi {
Writer : writer ,
buffer : new (bytes .Buffer ),
csiParameter : new (bytes .Buffer ),
csiIntermediate : new (bytes .Buffer ),
state : ansiText ,
}
}
func (a *ansi ) Write (text []byte ) (int , error ) {
defer func () {
a .buffer .Reset ()
}()
for _ , r := range string (text ) {
switch a .state {
case ansiEscape :
switch r {
case '[' :
a .csiParameter .Reset ()
a .csiIntermediate .Reset ()
a .state = ansiControlSequence
case 'c' :
fmt .Fprint (a .buffer , "[-:-:-]" )
a .state = ansiText
case 'P' , ']' , 'X' , '^' , '_' :
a .state = ansiSubstring
default :
a .state = ansiText
}
case ansiControlSequence :
switch {
case r >= 0x30 && r <= 0x3f :
if _ , err := a .csiParameter .WriteRune (r ); err != nil {
return 0 , err
}
case r >= 0x20 && r <= 0x2f :
if _ , err := a .csiIntermediate .WriteRune (r ); err != nil {
return 0 , err
}
case r >= 0x40 && r <= 0x7e :
switch r {
case 'E' :
count , _ := strconv .Atoi (a .csiParameter .String ())
if count == 0 {
count = 1
}
fmt .Fprint (a .buffer , strings .Repeat ("\n" , count ))
case 'm' :
var background , foreground string
params := a .csiParameter .String ()
fields := strings .Split (params , ";" )
if len (params ) == 0 || len (fields ) == 1 && fields [0 ] == "0" {
a .attributes = ""
if _ , err := a .buffer .WriteString ("[-:-:-]" ); err != nil {
return 0 , err
}
break
}
lookupColor := func (colorNumber int ) string {
if colorNumber < 0 || colorNumber > 15 {
return "black"
}
return []string {
"black" ,
"maroon" ,
"green" ,
"olive" ,
"navy" ,
"purple" ,
"teal" ,
"silver" ,
"gray" ,
"red" ,
"lime" ,
"yellow" ,
"blue" ,
"fuchsia" ,
"aqua" ,
"white" ,
}[colorNumber ]
}
FieldLoop :
for index , field := range fields {
switch field {
case "1" , "01" :
if strings .IndexRune (a .attributes , 'b' ) < 0 {
a .attributes += "b"
}
case "2" , "02" :
if strings .IndexRune (a .attributes , 'd' ) < 0 {
a .attributes += "d"
}
case "3" , "03" :
if strings .IndexRune (a .attributes , 'i' ) < 0 {
a .attributes += "i"
}
case "4" , "04" :
if strings .IndexRune (a .attributes , 'u' ) < 0 {
a .attributes += "u"
}
case "5" , "05" :
if strings .IndexRune (a .attributes , 'l' ) < 0 {
a .attributes += "l"
}
case "7" , "07" :
if strings .IndexRune (a .attributes , 'r' ) < 0 {
a .attributes += "r"
}
case "9" , "09" :
if strings .IndexRune (a .attributes , 's' ) < 0 {
a .attributes += "s"
}
case "22" :
if i := strings .IndexRune (a .attributes , 'b' ); i >= 0 {
a .attributes = a .attributes [:i ] + a .attributes [i +1 :]
}
if i := strings .IndexRune (a .attributes , 'd' ); i >= 0 {
a .attributes = a .attributes [:i ] + a .attributes [i +1 :]
}
case "24" :
if i := strings .IndexRune (a .attributes , 'u' ); i >= 0 {
a .attributes = a .attributes [:i ] + a .attributes [i +1 :]
}
case "25" :
if i := strings .IndexRune (a .attributes , 'l' ); i >= 0 {
a .attributes = a .attributes [:i ] + a .attributes [i +1 :]
}
case "30" , "31" , "32" , "33" , "34" , "35" , "36" , "37" :
colorNumber , _ := strconv .Atoi (field )
foreground = lookupColor (colorNumber - 30 )
case "39" :
foreground = "-"
case "40" , "41" , "42" , "43" , "44" , "45" , "46" , "47" :
colorNumber , _ := strconv .Atoi (field )
background = lookupColor (colorNumber - 40 )
case "49" :
background = "-"
case "90" , "91" , "92" , "93" , "94" , "95" , "96" , "97" :
colorNumber , _ := strconv .Atoi (field )
foreground = lookupColor (colorNumber - 82 )
case "100" , "101" , "102" , "103" , "104" , "105" , "106" , "107" :
colorNumber , _ := strconv .Atoi (field )
background = lookupColor (colorNumber - 92 )
case "38" , "48" :
var color string
if len (fields ) > index +1 {
if fields [index +1 ] == "5" && len (fields ) > index +2 {
colorNumber , _ := strconv .Atoi (fields [index +2 ])
if colorNumber <= 15 {
color = lookupColor (colorNumber )
} else if colorNumber <= 231 {
red := (colorNumber - 16 ) / 36
green := ((colorNumber - 16 ) / 6 ) % 6
blue := (colorNumber - 16 ) % 6
color = fmt .Sprintf ("#%02x%02x%02x" , 255 *red /5 , 255 *green /5 , 255 *blue /5 )
} else if colorNumber <= 255 {
grey := 255 * (colorNumber - 232 ) / 23
color = fmt .Sprintf ("#%02x%02x%02x" , grey , grey , grey )
}
} else if fields [index +1 ] == "2" && len (fields ) > index +4 {
red , _ := strconv .Atoi (fields [index +2 ])
green , _ := strconv .Atoi (fields [index +3 ])
blue , _ := strconv .Atoi (fields [index +4 ])
color = fmt .Sprintf ("#%02x%02x%02x" , red , green , blue )
}
}
if len (color ) > 0 {
if field == "38" {
foreground = color
} else {
background = color
}
}
break FieldLoop
}
}
var colon string
if len (a .attributes ) > 0 {
colon = ":"
}
if len (foreground ) > 0 || len (background ) > 0 || len (a .attributes ) > 0 {
fmt .Fprintf (a .buffer , "[%s:%s%s%s]" , foreground , background , colon , a .attributes )
}
}
a .state = ansiText
default :
a .state = ansiText
}
case ansiSubstring :
if r == 27 {
a .state = ansiEscape
}
default :
if r == 27 {
a .state = ansiEscape
} else {
if _ , err := a .buffer .WriteRune (r ); err != nil {
return 0 , err
}
}
}
}
n , err := a .buffer .WriteTo (a .Writer )
if err != nil {
return int (n ), err
}
return len (text ), nil
}
func TranslateANSI (text string ) string {
var buffer bytes .Buffer
writer := ANSIWriter (&buffer )
writer .Write ([]byte (text ))
return buffer .String ()
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .