diff --git a/CHANGELOG.md b/CHANGELOG.md index 85ed43e9..3d2f8859 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +0.32.2 +------ +- ANSI color sequences with colon delimiters are now supported. + ```sh + printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi + printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi + ``` + 0.32.1 ------ - Fixed incorrect ordering of `--tiebreak=chunk` diff --git a/src/ansi.go b/src/ansi.go index 543dabf3..f7910d7a 100644 --- a/src/ansi.go +++ b/src/ansi.go @@ -85,7 +85,7 @@ func isPrint(c uint8) bool { } func matchOperatingSystemCommand(s string) int { - // `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)` + // `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)` // ^ match starting here // i := 5 // prefix matched in nextAnsiEscapeSequence() @@ -103,11 +103,11 @@ func matchOperatingSystemCommand(s string) int { } func matchControlSequence(s string) int { - // `\x1b[\\[()][0-9;?]*[a-zA-Z@]` - // ^ match starting here + // `\x1b[\\[()][0-9;:?]*[a-zA-Z@]` + // ^ match starting here // i := 2 // prefix matched in nextAnsiEscapeSequence() - for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == '?'); i++ { + for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == ':' || s[i] == '?'); i++ { } if i < len(s) { c := s[i] @@ -125,7 +125,7 @@ func isCtrlSeqStart(c uint8) bool { // nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to // calling FindStringIndex() on the below regex (which was originally used): // -// "(?:\x1b[\\[()][0-9;?]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)" +// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)" func nextAnsiEscapeSequence(s string) (int, int) { // fast check for ANSI escape sequences i := 0 @@ -153,16 +153,16 @@ Loop: return i - n, i + 1 } case '\x1b': - // match: `\x1b[\\[()][0-9;?]*[a-zA-Z@]` + // match: `\x1b[\\[()][0-9;:?]*[a-zA-Z@]` if i+2 < len(s) && isCtrlSeqStart(s[i+1]) { if j := matchControlSequence(s[i:]); j != -1 { return i, i + j } } - // match: `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)` + // match: `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)` if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) && - s[i+3] == ';' && isPrint(s[i+4]) { + (s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) { if j := matchOperatingSystemCommand(s[i:]); j != -1 { return i, i + j @@ -279,9 +279,20 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo return trimmed, nil, state } -func parseAnsiCode(s string) (int, string) { +func parseAnsiCode(s string, delimiter byte) (int, byte, string) { var remaining string - if i := strings.IndexByte(s, ';'); i >= 0 { + i := -1 + if delimiter == 0 { + // Faster than strings.IndexAny(";:") + i = strings.IndexByte(s, ';') + if i < 0 { + i = strings.IndexByte(s, ':') + } + } else { + i = strings.IndexByte(s, delimiter) + } + if i >= 0 { + delimiter = s[i] remaining = s[i+1:] s = s[:i] } @@ -293,14 +304,14 @@ func parseAnsiCode(s string) (int, string) { for _, ch := range []byte(s) { ch -= '0' if ch > 9 { - return -1, remaining + return -1, delimiter, remaining } code = code*10 + int(ch) } - return code, remaining + return code, delimiter, remaining } - return -1, remaining + return -1, delimiter, remaining } func interpretCode(ansiCode string, prevState *ansiState) ansiState { @@ -328,9 +339,10 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState { state256 := 0 ptr := &state.fg + var delimiter byte = 0 for len(ansiCode) != 0 { var num int - if num, ansiCode = parseAnsiCode(ansiCode); num != -1 { + if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 { switch state256 { case 0: switch num { diff --git a/src/ansi_test.go b/src/ansi_test.go index 781c0a10..657cf4e0 100644 --- a/src/ansi_test.go +++ b/src/ansi_test.go @@ -358,6 +358,7 @@ func TestAnsiCodeStringConversion(t *testing.T) { assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m") assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m") assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m") + assert("\x1b[38:5:100:48:5:200m", nil, "\x1b[38;5;100;48;5;200m") assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m") assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m") assert("\x1b[48;5;100;38;2;10;20;30;7m", @@ -377,7 +378,7 @@ func TestParseAnsiCode(t *testing.T) { {"-2", "", -1}, } for _, x := range tests { - n, s := parseAnsiCode(x.In) + n, _, s := parseAnsiCode(x.In, 0) if n != x.N || s != x.Exp { t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp) } @@ -385,9 +386,9 @@ func TestParseAnsiCode(t *testing.T) { } // kernel/bpf/preload/iterators/README -const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" + - "\x1b[0m\x1b[38;5;81mpreload/\x1b[0m\x1b[38;5;81miterators/" + - "\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m" +const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38:5:81mbpf/" + + "\x1b[0m\x1b[38:5:81mpreload/\x1b[0m\x1b[38;5;81miterators/" + + "\x1b[0m\x1b[38:5:149mMakefile\x1b[m\x1b[K\x1b[0m" func BenchmarkNextAnsiEscapeSequence(b *testing.B) { b.SetBytes(int64(len(ansiBenchmarkString)))