blob: b097b25f87bce8382809cb2c085881a185ac1e63 [file] [log] [blame]
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
}