| package ansiescape |
| |
| import "bytes" |
| |
| // dropCR drops a leading or terminal \r from the data. |
| func dropCR(data []byte) []byte { |
| if len(data) > 0 && data[len(data)-1] == '\r' { |
| data = data[0 : len(data)-1] |
| } |
| if len(data) > 0 && data[0] == '\r' { |
| data = data[1:] |
| } |
| return data |
| } |
| |
| // escapeSequenceLength calculates the length of an ANSI escape sequence |
| // If there is not enough characters to match a sequence, -1 is returned, |
| // if there is no valid sequence 0 is returned, otherwise the number |
| // of bytes in the sequence is returned. Only returns length for |
| // line moving sequences. |
| func escapeSequenceLength(data []byte) int { |
| next := 0 |
| if len(data) <= next { |
| return -1 |
| } |
| if data[next] != '[' { |
| return 0 |
| } |
| for { |
| next = next + 1 |
| if len(data) <= next { |
| return -1 |
| } |
| if (data[next] > '9' || data[next] < '0') && data[next] != ';' { |
| break |
| } |
| } |
| if len(data) <= next { |
| return -1 |
| } |
| // Only match line moving codes |
| switch data[next] { |
| case 'A', 'B', 'E', 'F', 'H', 'h': |
| return next + 1 |
| } |
| |
| return 0 |
| } |
| |
| // ScanANSILines is a scanner function which splits the |
| // input based on ANSI escape codes and new lines. |
| func ScanANSILines(data []byte, atEOF bool) (advance int, token []byte, err error) { |
| if atEOF && len(data) == 0 { |
| return 0, nil, nil |
| } |
| |
| // Look for line moving escape sequence |
| if i := bytes.IndexByte(data, '\x1b'); i >= 0 { |
| last := 0 |
| for i >= 0 { |
| last = last + i |
| |
| // get length of ANSI escape sequence |
| sl := escapeSequenceLength(data[last+1:]) |
| if sl == -1 { |
| return 0, nil, nil |
| } |
| if sl == 0 { |
| // If no relevant sequence was found, skip |
| last = last + 1 |
| i = bytes.IndexByte(data[last:], '\x1b') |
| continue |
| } |
| |
| return last + 1 + sl, dropCR(data[0:(last)]), nil |
| } |
| } |
| if i := bytes.IndexByte(data, '\n'); i >= 0 { |
| // No escape sequence, check for new line |
| return i + 1, dropCR(data[0:i]), nil |
| } |
| |
| // If we're at EOF, we have a final, non-terminated line. Return it. |
| if atEOF { |
| return len(data), dropCR(data), nil |
| } |
| // Request more data. |
| return 0, nil, nil |
| } |