mirror of
https://github.com/junegunn/fzf.git
synced 2026-08-26 01:19:55 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3900dd17e4 | ||
|
|
5e73c2ddd3 | ||
|
|
18e5009e0f |
@@ -26,7 +26,6 @@ const (
|
|||||||
offsetPollTries = 10
|
offsetPollTries = 10
|
||||||
queryTimeout = 500 * time.Millisecond
|
queryTimeout = 500 * time.Millisecond
|
||||||
maxInputBuffer = 1024 * 1024
|
maxInputBuffer = 1024 * 1024
|
||||||
escapeLookback = 256
|
|
||||||
maxSelectTries = 100
|
maxSelectTries = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -339,45 +338,6 @@ func getEnv(name string, defaultValue int) int {
|
|||||||
return atoi(env, defaultValue)
|
return atoi(env, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes of a CSI sequence: parameter and intermediate bytes continue it, a
|
|
||||||
// final byte ends it. Order is not enforced. Strictness would only make fzf
|
|
||||||
// give up on a sequence it could have framed.
|
|
||||||
//
|
|
||||||
// https://vt100.net/emu/dec_ansi_parser
|
|
||||||
func csiContinues(b byte) bool { return b >= 0x20 && b <= 0x3f }
|
|
||||||
func csiFinal(b byte) bool { return b >= 0x40 && b <= 0x7e }
|
|
||||||
|
|
||||||
// incompleteEscape reports whether the buffer ends in an escape sequence that
|
|
||||||
// has not been terminated yet. The read loop keeps waiting in that case, so the
|
|
||||||
// parser is never handed a fragment to guess at.
|
|
||||||
func incompleteEscape(buffer []byte) bool {
|
|
||||||
// Only the tail can hold a sequence still arriving. This runs once per byte
|
|
||||||
// read, so scanning all of a large paste would make the read quadratic.
|
|
||||||
tail := buffer
|
|
||||||
if len(tail) > escapeLookback {
|
|
||||||
tail = tail[len(tail)-escapeLookback:]
|
|
||||||
}
|
|
||||||
start := bytes.LastIndexByte(tail, Esc.Byte())
|
|
||||||
if start < 0 || len(tail)-start < 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch tail[start+1] {
|
|
||||||
case '[':
|
|
||||||
for _, b := range tail[start+2:] {
|
|
||||||
if csiFinal(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !csiContinues(b) {
|
|
||||||
return false // malformed, do not wait for a terminator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case 'O':
|
|
||||||
return len(tail)-start < 3
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) getBytes(cancellable bool) ([]byte, getCharResult, error) {
|
func (r *LightRenderer) getBytes(cancellable bool) ([]byte, getCharResult, error) {
|
||||||
return r.getBytesInternal(cancellable, r.buffer, false)
|
return r.getBytesInternal(cancellable, r.buffer, false)
|
||||||
}
|
}
|
||||||
@@ -418,13 +378,6 @@ func (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblo
|
|||||||
retries = 0
|
retries = 0
|
||||||
}
|
}
|
||||||
buffer = append(buffer, byte(c))
|
buffer = append(buffer, byte(c))
|
||||||
// Keep waiting while a sequence is still arriving. Dropping the budget
|
|
||||||
// after every byte left fzf parsing whatever the read happened to end on.
|
|
||||||
// Past the introducer this is not the ESC key, so the wait costs no
|
|
||||||
// Escape latency and ESCDELAY=0 must not reduce it to nothing.
|
|
||||||
if retries == 0 && incompleteEscape(buffer) {
|
|
||||||
retries = max(r.escDelay, defaultEscDelay) / escPollInterval
|
|
||||||
}
|
|
||||||
pc = c
|
pc = c
|
||||||
|
|
||||||
// This should never happen under normal conditions,
|
// This should never happen under normal conditions,
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
package tui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIncompleteEscape(t *testing.T) {
|
|
||||||
for _, c := range []struct {
|
|
||||||
buffer string
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
// Complete sequences: nothing to wait for
|
|
||||||
{"\x1b[A", false},
|
|
||||||
{"\x1bOA", false},
|
|
||||||
{"\x1b[1;5A", false},
|
|
||||||
{"\x1b[200~", false},
|
|
||||||
{"\x1b[<0;1;1M", false},
|
|
||||||
{"\x1b[12;34R", false},
|
|
||||||
{"\x1b[?2004;2$y", false},
|
|
||||||
{"\x1b[?1;2c", false},
|
|
||||||
|
|
||||||
// Fragments: keep waiting
|
|
||||||
{"\x1b[", true},
|
|
||||||
{"\x1b[?", true},
|
|
||||||
{"\x1b[1;", true},
|
|
||||||
{"\x1b[?2004;2$", true},
|
|
||||||
{"\x1bO", true},
|
|
||||||
{"\x1b[<0;1;", true},
|
|
||||||
|
|
||||||
// Only the trailing sequence matters
|
|
||||||
{"ab\x1b[?2004;2$", true},
|
|
||||||
{"\x1b[A\x1b[", true},
|
|
||||||
{"\x1b[A\x1b[B", false},
|
|
||||||
|
|
||||||
// Long buffers: only the tail is scanned, so an introducer further
|
|
||||||
// back than escapeLookback is not waited for
|
|
||||||
{strings.Repeat("a", 100000), false},
|
|
||||||
{"\x1b[" + strings.Repeat("a", 100000), false},
|
|
||||||
{strings.Repeat("a", 100000) + "\x1b[1;", true},
|
|
||||||
|
|
||||||
// Not a sequence fzf waits on
|
|
||||||
{"", false},
|
|
||||||
{"abc", false},
|
|
||||||
{"\x1b", false}, // lone ESC, handled by the existing escDelay branch
|
|
||||||
{"\x1ba", false}, // ALT-a
|
|
||||||
{"\x1b[\x01", false}, // malformed, do not stall on it
|
|
||||||
} {
|
|
||||||
if got := incompleteEscape([]byte(c.buffer)); got != c.want {
|
|
||||||
t.Errorf("incompleteEscape(%q) = %v, want %v", c.buffer, got, c.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user