123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- package door
- import (
- "bytes"
- "fmt"
- "log"
- "regexp"
- "strconv"
- "strings"
- )
- var FindANSIColor *regexp.Regexp
- const WRITE_DEBUG bool = false
- func init() {
- FindANSIColor = regexp.MustCompile("\x1b\\[([0-9;]*)m")
- }
- /*
- find \x1b[0;1;37;40m codes.
- return array of start/end positions in the given string.
- */
- func find_ansicolor(text string) [][]int {
- var color_codes [][]int
- // word, _ := regexp.Compile("\x1b\\[([0-9;]+)m")
- var colors [][]int = FindANSIColor.FindAllStringIndex(text, -1)
- // regexp seems to be ignoring the capture groups.
- // colors := word.FindAllSubmatchIndex([]byte(text), len(text))
- for _, pos := range colors {
- var txt string = text[pos[0]+2 : pos[1]-1]
- if txt == "" {
- txt = "0"
- }
- // log.Printf("Text: [%s]\n", txt)
- var codes []string = strings.Split(txt, ";")
- // log.Printf("Codes: [%#v]\n", codes)
- var code []int = make([]int, len(codes))
- for idx, c := range codes {
- var err error
- code[idx], err = strconv.Atoi(c)
- if err != nil {
- log.Printf("Atoi: %#v [%s]\n", err, c)
- }
- color_codes = append(color_codes, code)
- }
- }
- return color_codes
- }
- type ANSIColorParts struct {
- Bold bool
- Blink bool
- FG int
- BG int
- }
- // Parse an array of codes into their ANSIColorParts.
- func ParseColorArray(colorarray []int) ANSIColorParts {
- var acp ANSIColorParts
- acp.FG = -1
- acp.BG = -1
- if len(colorarray) == 0 {
- colorarray = []int{0}
- }
- for _, c := range colorarray {
- switch c {
- case 0:
- acp.FG = 7
- acp.BG = 0
- acp.Bold = false
- acp.Blink = false
- case 1:
- acp.Bold = true
- case 5:
- acp.Blink = true
- case 30, 31, 32, 33, 34, 35, 36, 37:
- acp.FG = c - 30
- case 40, 41, 42, 43, 44, 45, 46, 47:
- acp.BG = c - 40
- }
- }
- return acp
- }
- // Update the LastColor with the latest codes.
- func (d *Door) UpdateLastColor(output string, LastColor *[]int) {
- // use the last color information + whatever is in the string to
- // track the last color set
- var updated ANSIColorParts = ParseColorArray(*LastColor)
- var colors [][]int = find_ansicolor(output)
- for _, codes := range colors {
- if codes[0] == 0 {
- updated = ParseColorArray(codes)
- } else {
- newCode := ParseColorArray(codes)
- if newCode.Bold {
- updated.Bold = true
- }
- if newCode.Blink {
- updated.Blink = true
- }
- if (newCode.FG != -1) && (newCode.FG != updated.FG) {
- updated.FG = newCode.FG
- }
- if (newCode.BG != -1) && (newCode.BG != updated.BG) {
- updated.BG = newCode.BG
- }
- }
- }
- *LastColor = make([]int, 1)
- (*LastColor)[0] = 0
- if updated.Blink {
- *LastColor = append(*LastColor, 5)
- }
- if updated.Bold {
- *LastColor = append(*LastColor, 1)
- }
- if updated.FG != -1 {
- *LastColor = append(*LastColor, updated.FG+30)
- }
- if updated.BG != -1 {
- *LastColor = append(*LastColor, updated.BG+40)
- }
- }
- /*
- reading from a closed channel is easy to detect.
- res, ok := <-channel
- ok == false
- writing to a closed channel is a panic.
- Have the Write manage itself.
- */
- // For now, update is SavePos + output + RestorePos.
- // FUTURE: Access the screen struct to "save" Color+Attr+Pos.
- func (d *Door) Update(output []byte) {
- // sync.Pool ?
- d.WriteA(SAVE_POS, output, RESTORE_POS)
- /*
- var buffer bytes.Buffer
- buffer.WriteString(SAVE_POS)
- buffer.Write(output)
- buffer.WriteString(RESTORE_POS)
- d.Writer.Write(buffer.Bytes())
- */
- // d.Write(SAVE_POS + output + RESTORE_POS)
- }
- func (d *Door) WriteS(output string) {
- d.Write([]byte(output))
- }
- func (d *Door) WriteA(a ...interface{}) {
- d.writeMutex.Lock()
- defer d.writeMutex.Unlock()
- for _, item := range a {
- switch item.(type) {
- case string:
- d.LockedWrite([]byte(item.(string)))
- case []byte:
- d.LockedWrite(item.([]byte))
- case *bytes.Buffer:
- d.LockedWrite(item.(*bytes.Buffer).Bytes())
- default:
- log.Printf("Unknown/unsupported type: %T\n", item)
- }
- }
- }
- // Is this byte the end of the ANSI CSI?
- func EndCSI(c byte) bool {
- return (c >= 0x40) && (c <= 0x7f)
- }
- func (d *Door) ANSIProcess() {
- var csi byte = d.ansiCode[len(d.ansiCode)-1]
- switch csi {
- case 's':
- // Save Color
- var slen int = len(d.LastColor)
- if cap(d.Writer.LastSavedColor) < slen {
- d.Writer.LastSavedColor = make([]int, slen)
- } else {
- d.Writer.LastSavedColor = d.Writer.LastSavedColor[0:slen]
- }
- copy(d.Writer.LastSavedColor, d.LastColor)
- if WRITE_DEBUG {
- log.Printf("ColorSave: %d\n", d.Writer.LastSavedColor)
- }
- case 'u':
- // Restore Color
- var slen int = len(d.Writer.LastSavedColor)
- if cap(d.LastColor) < slen {
- d.LastColor = make([]int, slen)
- } else {
- d.LastColor = d.LastColor[0:slen]
- }
- copy(d.LastColor, d.Writer.LastSavedColor)
- if WRITE_DEBUG {
- log.Printf("ColorRestore: %d\n", d.LastColor)
- }
- case 'm':
- // Process color code
- var color [5]int
- var cpos int
- var code int
- var pos int
- for pos = 0; pos < len(d.ansiCode); pos++ {
- var b byte = d.ansiCode[pos]
- switch b {
- case ';':
- color[cpos] = code
- code = 0
- cpos++
- case '1', '2', '3', '4', '5', '6', '7', '8', '9', '0':
- code *= 10
- code += int(b - '0')
- }
- }
- if code != 0 {
- color[cpos] = code
- code = 0
- cpos++
- }
- // Make sure there is always at least one code here.
- if cpos == 0 {
- color[cpos] = 0
- cpos++
- }
- if WRITE_DEBUG {
- log.Printf("color: [%d]", color[0:cpos])
- }
- var newColor = ParseColorArray(d.LastColor)
- for _, c := range color[0:cpos] {
- switch c {
- case 0:
- newColor.FG = 7
- newColor.BG = 0
- newColor.Bold = false
- newColor.Blink = false
- case 1:
- newColor.Bold = true
- case 5:
- newColor.Blink = true
- case 30, 31, 32, 33, 34, 35, 36, 37:
- newColor.FG = c - 30
- case 40, 41, 42, 43, 44, 45, 46, 47:
- newColor.BG = c - 40
- }
- }
- // Reset LastColor
- d.LastColor = d.LastColor[0:0]
- d.LastColor = append(d.LastColor, 0)
- if newColor.Blink {
- d.LastColor = append(d.LastColor, 5)
- }
- if newColor.Bold {
- d.LastColor = append(d.LastColor, 1)
- }
- if newColor.FG != -1 {
- d.LastColor = append(d.LastColor, newColor.FG+30)
- }
- if newColor.BG != -1 {
- d.LastColor = append(d.LastColor, newColor.BG+40)
- }
- if WRITE_DEBUG {
- log.Printf("LastColor: [%d]", d.LastColor)
- }
- }
- if WRITE_DEBUG {
- var outputByte [20]byte
- var output []byte = outputByte[0:0]
- output = fmt.Appendf(output, "ANSI: [%q]\n", d.ansiCode)
- log.Printf("%s", output)
- }
- // Reset
- d.ansiCode = d.ansiCode[0:0]
- d.ansiCSI = false
- }
- func (d *Door) ANSIScan(output []byte) {
- var pos, nextpos int
- var olen int = len(output)
- var c byte
- if d.ansiCSI {
- for pos < olen {
- c = output[pos]
- d.ansiCode = append(d.ansiCode, c)
- pos++
- if EndCSI(c) {
- d.ANSIProcess()
- break
- }
- }
- if pos == olen {
- return
- // pos == -1 // end
- }
- }
- for pos != -1 {
- nextpos = bytes.Index(output[pos:], []byte{'\x1b'})
- if nextpos != -1 {
- // Found ESC
- nextpos += pos + 1
- if output[nextpos] == '[' {
- d.ansiCSI = true
- nextpos++
- for nextpos < olen {
- c = output[nextpos]
- d.ansiCode = append(d.ansiCode, c)
- nextpos++
- if EndCSI(c) {
- d.ANSIProcess()
- break
- }
- }
- }
- pos = nextpos
- } else {
- return
- }
- }
- }
- func (d *Door) NewLinesTranslate(output []byte) []byte {
- var pos, nextpos int
- if d.nlBuffer == nil {
- d.nlBuffer = &bytes.Buffer{}
- }
- d.nlBuffer.Reset()
- for pos != -1 {
- nextpos = bytes.Index(output[pos:], []byte{'\n'})
- if nextpos != -1 {
- nextpos += pos
- // Something to do
- d.nlBuffer.Write(output[pos:nextpos])
- nextpos++
- pos = nextpos
- d.nlBuffer.Write([]byte("\r\n"))
- } else {
- d.nlBuffer.Write(output[pos:])
- pos = nextpos // -1
- }
- }
- // log.Printf(">> %q\n", ow.nlBuffer.Bytes())
- return d.nlBuffer.Bytes()
- }
- func (d *Door) LockedWrite(output []byte) {
- if d.writeMutex.TryLock() {
- log.Panic("LockedWrite: mutex was NOT locked.")
- }
- /*
- if bytes.HasSuffix(output, []byte(RestorePos)) {
- // Write the current color
- lastColor = Color(d.LastColor)
- //fmt.Append(lastColor, []byte(Color(d.LastColor)))
- log.Printf("Restore LastColor: %d => %q", d.LastColor, lastColor)
- }
- */
- if true {
- output = d.NewLinesTranslate(output)
- }
- if WRITE_DEBUG {
- log.Printf(">> %q\n", output)
- }
- d.Writer.Write(output)
- d.ANSIScan(output)
- if bytes.HasSuffix(output, []byte(RestorePos)) {
- lastColor := Color(d.LastColor)
- //fmt.Append(lastColor, []byte(Color(d.LastColor)))
- if WRITE_DEBUG {
- log.Printf("Restore LastColor: %d => %q", d.LastColor, lastColor)
- }
- d.Writer.Write([]byte(lastColor))
- //d.ANSIScan([]byte(lastColor))
- }
- /*
- if len(lastColor) != 0 {
- log.Println("Restored HERE...")
- d.Writer.Write([]byte(lastColor))
- d.ANSIScan([]byte(lastColor))
- }
- */
- // else {
- // Only update if we're NOT restoring...
- // Update
- // d.ANSIScan(output)
- // }
- }
- func (d *Door) Write(output []byte) {
- if len(output) == 0 {
- return
- }
- d.writeMutex.Lock()
- defer d.writeMutex.Unlock()
- d.LockedWrite(output)
- }
|