package door import ( "log" "regexp" "strconv" "strings" ) var FindANSIColor *regexp.Regexp 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 for _, c := range colorarray { switch c { case 0: acp.FG = 7 acp.BG = 0 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 string) { d.Write(SAVE_POS + output + RESTORE_POS) } // Write string to client. func (d *Door) Write(output string) { if output == "" { // That was easy. return } /* if d.Disconnect() { return } defer func() { if err := recover(); err != nil { log.Println("Write FAILURE:", err) // Display error to user // This displays stack trace stderr // debug.PrintStack() } }() if d.Disconnect() { return } */ // DATA RACE. Because WriterClosed is getting changed by // the closeChannel ... this isn't synchronized/safe. // DATA RACE. If I just close the writerChannel, there's // a data race on the channel. (writing to and closing) // DATA RACE. Sending a "" to the channel for a "close" // signal doesn't work either ... (called from go routine) // The channel <- is synced, the access to the bool isn't. :( //if !d.WriterClosed { // Apparently, when I get to the next line, there's // already a data race here... /* if !d.WriterIsClosed() { d.writerChannel <- output } */ defer func() { if err := recover(); err != nil { log.Println("Write failed.", err) } }() //if !d.WriterIsClosed() { d.writerChannel <- output //} /* d.writerMutex.Lock() defer d.writerMutex.Unlock() if d.WriterClosed { return } d.writerChannel <- output */ }