mirror of
https://github.com/junegunn/fzf.git
synced 2026-10-14 08:59:01 +08:00
fzf consumed only the part of a sequence it recognized, and the rest was typed into the query. CTRL-A sent as \e[97;5u became "97;5u", and CTRL-F sent as \e[70;5u fired Home before typing ";5u". A BEL-terminated OSC reply also aborted fzf, because the BEL that followed the typed payload was read as CTRL-G. Frame CSI and SS3 by their parameter and final byte ranges, OSC, DCS and APC by their terminator. Drop the whole frame when the parser does not recognize it, or recognizes only a prefix of it, and never consume past it: the parser could count a key typed after it. rxvt ends keys with $, as in \e[7$ and \e[23$, so $ after plain digits is a final byte. Elsewhere it stays intermediate, as in DECRPM replies. Take the second-chance read only when the sequence at the start of the buffer is the only one and is unfinished. Otherwise it blocked until the next key, holding back what followed, CTRL-G included. That read has no time limit, so what it returns may be the next key, typed any time later. It is never dropped with the frame. Telling a terminal's sequence from an ALT key is the hard part, so these keep what the parser made of them: - An unterminated sequence, since that is how ALT-[, ALT-O, ALT-], ALT-P and ALT-_ arrive - A CSI or SS3 of four bytes or fewer. It could be an ALT key and typed text, and rxvt sends keys of that size fzf does not know, such as \e[3^ - A string sequence whose payload does not start like a reply: digits and ';' for OSC, '>|', '!|', '[01]$r' or '[01]+r' for DCS, 'G' and a key for APC - SOS and PM, which nothing sends Within a string, an ESC ends it and introduces a sequence of its own, and only OSC ends with BEL. The read loop does not wait for a string terminator: that held ALT-], ALT-P and ALT-_ for ESCDELAY and let typed bytes pile up until they looked like a sequence.
97 lines
2.6 KiB
Go
97 lines
2.6 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A string sequence is not waited for. Its introducer is also ALT-], ALT-P or
|
|
// ALT-_, and holding the read open on those both delayed the key and let typed
|
|
// bytes accumulate into something that looked like a sequence.
|
|
func TestIncompleteStringEscape(t *testing.T) {
|
|
for _, buffer := range []string{
|
|
"\x1b]11;rgb:",
|
|
"\x1bP>|kitty",
|
|
"\x1b_Gi=1",
|
|
"\x1b]0;t\x1b",
|
|
"\x1b]0;title\a",
|
|
"ab\x1b]11;rgb:",
|
|
} {
|
|
if incompleteEscape([]byte(buffer)) {
|
|
t.Errorf("incompleteEscape(%q) = true, want false", buffer)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SS3 reaches the same parameterized subcases as CSI in the parser, so it has to
|
|
// be judged the same way here. Declaring it finished after one byte made the
|
|
// parser give up on a split \eO1;5A and type its tail into the query.
|
|
func TestIncompleteSS3(t *testing.T) {
|
|
for _, c := range []struct {
|
|
buffer string
|
|
want bool
|
|
}{
|
|
{"\x1bO", true},
|
|
{"\x1bO1", true},
|
|
{"\x1bO1;", true},
|
|
{"\x1bO1;5", true},
|
|
{"\x1bOA", false},
|
|
{"\x1bOP", false},
|
|
{"\x1bO1;5A", false},
|
|
{"\x1b[23$", false}, // rxvt SHIFT-F11 is complete
|
|
{"\x1b[7$", false},
|
|
{"\x1b[4;2$", true}, // $ after a ; is intermediate
|
|
} {
|
|
if got := incompleteEscape([]byte(c.buffer)); got != c.want {
|
|
t.Errorf("incompleteEscape(%q) = %v, want %v", c.buffer, got, c.want)
|
|
}
|
|
}
|
|
}
|