blob: 64fe01fdb58aab8c084774cf4c21aed43dace222 [file] [log] [blame]
package vtclean
import (
"bytes"
"regexp"
"strconv"
)
// regex based on ECMA-48:
// 1. optional:
// one of [ or ]
// any amount of 0x30-0x3f
// any amount of 0x20-0x2f
// 3. exactly one 0x40-0x7e
var vt100re = regexp.MustCompile(`^\033([\[\]]([0-9:;<=>\?]*)([!"#$%&'()*+,\-./]*))?([@A-Z\[\]^_\x60a-z{|}~])`)
var vt100exc = regexp.MustCompile(`^\033(\[[^a-zA-Z0-9@\?]+|[\(\)]).`)
// this is to handle the RGB escape generated by `tput initc 1 500 500 500`
var vt100long = regexp.MustCompile(`^\033](\d+);([^\033]+)\033\\`)
func Clean(line string, color bool) string {
var edit = newLineEdit(len(line))
lineb := []byte(line)
hadColor := false
for i := 0; i < len(lineb); {
c := lineb[i]
switch c {
case '\r':
edit.MoveAbs(0)
case '\b':
edit.Move(-1)
case '\033':
// set terminal title
if bytes.HasPrefix(lineb[i:], []byte("\x1b]0;")) {
pos := bytes.Index(lineb[i:], []byte("\a"))
if pos != -1 {
i += pos + 1
continue
}
}
if m := vt100long.Find(lineb[i:]); m != nil {
i += len(m)
} else if m := vt100exc.Find(lineb[i:]); m != nil {
i += len(m)
} else if m := vt100re.FindSubmatch(lineb[i:]); m != nil {
i += len(m[0])
num := string(m[2])
n, err := strconv.Atoi(num)
if err != nil || n > 10000 {
n = 1
}
switch m[4][0] {
case 'm':
if color {
hadColor = true
edit.Vt100(m[0])
}
case '@':
edit.Insert(bytes.Repeat([]byte{' '}, n))
case 'G':
edit.MoveAbs(n)
case 'C':
edit.Move(n)
case 'D':
edit.Move(-n)
case 'P':
edit.Delete(n)
case 'K':
switch num {
case "", "0":
edit.ClearRight()
case "1":
edit.ClearLeft()
case "2":
edit.Clear()
}
}
} else {
i += 1
}
continue
default:
if c == '\n' || c == '\t' || c >= ' ' {
edit.Write([]byte{c})
}
}
i += 1
}
out := edit.Bytes()
if hadColor {
out = append(out, []byte("\033[0m")...)
}
return string(out)
}