Projects STRLCPY fzf Commits aa10dccf
🤬
  • Support colon delimiter in ANSI escape sequences

    # Both should work
        printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
        printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi
    
    This change makes ANSI parsing slightly slower.
    
        cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
    
        Before:
          BenchmarkNextAnsiEscapeSequence-12            992.22 MB/s
          BenchmarkExtractColor-12                      174.35 MB/s
    
        After:
          BenchmarkNextAnsiEscapeSequence-12            925.05 MB/s
          BenchmarkExtractColor-12                      163.33 MB/s
    
    Fix #2913
  • Loading...
  • Junegunn Choi committed 2 years ago
    aa10dccf
    1 parent f4fd5321
  • ■ ■ ■ ■ ■ ■
    CHANGELOG.md
    1 1  CHANGELOG
    2 2  =========
    3 3   
     4 +0.32.2
     5 +------
     6 +- ANSI color sequences with colon delimiters are now supported.
     7 + ```sh
     8 + printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
     9 + printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi
     10 + ```
     11 + 
    4 12  0.32.1
    5 13  ------
    6 14  - Fixed incorrect ordering of `--tiebreak=chunk`
    skipped 1284 lines
  • ■ ■ ■ ■ ■ ■
    src/ansi.go
    skipped 84 lines
    85 85  }
    86 86   
    87 87  func matchOperatingSystemCommand(s string) int {
    88  - // `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
     88 + // `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
    89 89   // ^ match starting here
    90 90   //
    91 91   i := 5 // prefix matched in nextAnsiEscapeSequence()
    skipped 11 lines
    103 103  }
    104 104   
    105 105  func matchControlSequence(s string) int {
    106  - // `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
    107  - // ^ match starting here
     106 + // `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
     107 + // ^ match starting here
    108 108   //
    109 109   i := 2 // prefix matched in nextAnsiEscapeSequence()
    110  - for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == '?'); i++ {
     110 + for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == ':' || s[i] == '?'); i++ {
    111 111   }
    112 112   if i < len(s) {
    113 113   c := s[i]
    skipped 11 lines
    125 125  // nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
    126 126  // calling FindStringIndex() on the below regex (which was originally used):
    127 127  //
    128  -// "(?:\x1b[\\[()][0-9;?]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
     128 +// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
    129 129  func nextAnsiEscapeSequence(s string) (int, int) {
    130 130   // fast check for ANSI escape sequences
    131 131   i := 0
    skipped 21 lines
    153 153   return i - n, i + 1
    154 154   }
    155 155   case '\x1b':
    156  - // match: `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
     156 + // match: `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
    157 157   if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
    158 158   if j := matchControlSequence(s[i:]); j != -1 {
    159 159   return i, i + j
    160 160   }
    161 161   }
    162 162   
    163  - // match: `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
     163 + // match: `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
    164 164   if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
    165  - s[i+3] == ';' && isPrint(s[i+4]) {
     165 + (s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) {
    166 166   
    167 167   if j := matchOperatingSystemCommand(s[i:]); j != -1 {
    168 168   return i, i + j
    skipped 110 lines
    279 279   return trimmed, nil, state
    280 280  }
    281 281   
    282  -func parseAnsiCode(s string) (int, string) {
     282 +func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
    283 283   var remaining string
    284  - if i := strings.IndexByte(s, ';'); i >= 0 {
     284 + i := -1
     285 + if delimiter == 0 {
     286 + // Faster than strings.IndexAny(";:")
     287 + i = strings.IndexByte(s, ';')
     288 + if i < 0 {
     289 + i = strings.IndexByte(s, ':')
     290 + }
     291 + } else {
     292 + i = strings.IndexByte(s, delimiter)
     293 + }
     294 + if i >= 0 {
     295 + delimiter = s[i]
    285 296   remaining = s[i+1:]
    286 297   s = s[:i]
    287 298   }
    skipped 5 lines
    293 304   for _, ch := range []byte(s) {
    294 305   ch -= '0'
    295 306   if ch > 9 {
    296  - return -1, remaining
     307 + return -1, delimiter, remaining
    297 308   }
    298 309   code = code*10 + int(ch)
    299 310   }
    300  - return code, remaining
     311 + return code, delimiter, remaining
    301 312   }
    302 313   
    303  - return -1, remaining
     314 + return -1, delimiter, remaining
    304 315  }
    305 316   
    306 317  func interpretCode(ansiCode string, prevState *ansiState) ansiState {
    skipped 21 lines
    328 339   state256 := 0
    329 340   ptr := &state.fg
    330 341   
     342 + var delimiter byte = 0
    331 343   for len(ansiCode) != 0 {
    332 344   var num int
    333  - if num, ansiCode = parseAnsiCode(ansiCode); num != -1 {
     345 + if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
    334 346   switch state256 {
    335 347   case 0:
    336 348   switch num {
    skipped 73 lines
  • ■ ■ ■ ■ ■ ■
    src/ansi_test.go
    skipped 357 lines
    358 358   assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
    359 359   assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
    360 360   assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
     361 + assert("\x1b[38:5:100:48:5:200m", nil, "\x1b[38;5;100;48;5;200m")
    361 362   assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
    362 363   assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
    363 364   assert("\x1b[48;5;100;38;2;10;20;30;7m",
    skipped 13 lines
    377 378   {"-2", "", -1},
    378 379   }
    379 380   for _, x := range tests {
    380  - n, s := parseAnsiCode(x.In)
     381 + n, _, s := parseAnsiCode(x.In, 0)
    381 382   if n != x.N || s != x.Exp {
    382 383   t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
    383 384   }
    skipped 1 lines
    385 386  }
    386 387   
    387 388  // kernel/bpf/preload/iterators/README
    388  -const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" +
    389  - "\x1b[0m\x1b[38;5;81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
    390  - "\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m"
     389 +const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38:5:81mbpf/" +
     390 + "\x1b[0m\x1b[38:5:81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
     391 + "\x1b[0m\x1b[38:5:149mMakefile\x1b[m\x1b[K\x1b[0m"
    391 392   
    392 393  func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
    393 394   b.SetBytes(int64(len(ansiBenchmarkString)))
    skipped 35 lines
Please wait...
Page is in error, reload to recover