mirror of
https://github.com/junegunn/fzf.git
synced 2026-09-28 22:52:27 +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". 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, then drop the whole sequence when nothing matches it. The second-chance read now runs only while a sequence is unfinished, since after a complete one it blocked until the next keystroke. Telling a terminal's sequence from an ALT key is the hard part, so several shapes are deliberately left alone: - An unterminated sequence, since that is how ALT-[, ALT-O, ALT-], ALT-P and ALT-_ arrive, and an empty one for the same reason - A CSI or SS3 short enough to be an ALT key with one or two characters typed after it - SOS and PM, which nothing sends, so ALT-X and ALT-^ are not delayed - An ESC inside a string, which ends it and introduces a sequence of its own, so scanning on to a later ST would swallow that one - Only OSC ends with BEL. DCS and APC end with ST, and stopping at a BEL in their payload framed just its first half The read loop does not wait for a string terminator either. Waiting held ALT-], ALT-P and ALT-_ for ESCDELAY and let typed bytes pile into the buffer until they looked like a sequence, which swallowed a CTRL-G abort.
94 lines
2.4 KiB
Go
94 lines
2.4 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},
|
|
} {
|
|
if got := incompleteEscape([]byte(c.buffer)); got != c.want {
|
|
t.Errorf("incompleteEscape(%q) = %v, want %v", c.buffer, got, c.want)
|
|
}
|
|
}
|
|
}
|