Files
fzf/src/tui/light_escape_test.go
T
Junegunn Choi 9c667afb73 Wait for a string terminator split across reads
A trailing ESC was read as a lone ESC, so the read loop stopped waiting and
the parser was handed an unterminated sequence, whose payload was typed into
the query. With ESCDELAY=0 nothing covered it.

Reported by Copilot on #4926.
2026-09-27 21:03:07 +09:00

86 lines
2.2 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)
}
}
}
// String sequences must be waited for until their terminator arrives
func TestIncompleteStringEscape(t *testing.T) {
for _, c := range []struct {
buffer string
want bool
}{
{"\x1b]11;rgb:", true},
{"\x1bP>|kitty", true},
{"\x1b_Gi=1", true},
{"\x1b]0;title\a", false},
{"\x1b]0;title\x1b\\", false},
{"\x1bP>|kitty(0.48.2)\x1b\\", false},
{"ab\x1b]11;rgb:", true},
// A string terminator split across reads: the ESC has arrived, the
// backslash has not
{"\x1b]0;t\x1b", true},
{"\x1bP>|kitty\x1b", true},
{"\x1b_G\x1b", true},
// Complete sequence followed by a lone ESC, which is the ESC key
{"\x1b]0;title\a\x1b", false},
{"\x1b\x1b", false},
{"\x1ba\x1b", false},
{"\x1b[A\x1b", false},
} {
if got := incompleteEscape([]byte(c.buffer)); got != c.want {
t.Errorf("incompleteEscape(%q) = %v, want %v", c.buffer, got, c.want)
}
}
}