Take the second chance only for an unfinished sequence

The check looked at the last ESC in the buffer, not at the sequence just
parsed. A dropped \e[97;5u followed by CTRL-G and ALT-[ in one read blocked
until the next key, holding back the abort. Require both to be unfinished:
an unfinished sequence followed by other bytes cannot complete either.

Reported by Copilot on #4926.
This commit is contained in:
Junegunn Choi committed 2026-10-03 11:59:35 +09:00
1 parent 34243060eb
commit ec365401f0
2 files changed
+34 -4

No files matched your search

+12 -4
View File
@@ -444,6 +444,14 @@ func stringReply(buffer []byte) bool {
return false
}
// stillArriving reports whether the sequence that consumed sz bytes of the
// buffer may still be completed by more input. Both it and the buffer have to
// end unfinished: a complete sequence followed by ALT-[ is not waiting for
// anything, and an unfinished one followed by other bytes cannot complete.
func stillArriving(buffer []byte, sz int) bool {
return incompleteEscape(buffer[:min(sz, len(buffer))]) && incompleteEscape(buffer)
}
// incompleteEscape reports whether the buffer ends in an escape sequence that
// has not been terminated yet. The read loop keeps waiting while it does, so a
// fragment reaches the parser only once that wait has run out.
@@ -578,10 +586,10 @@ func (r *LightRenderer) GetChar(cancellable bool) Event {
return Event{CtrlSlash, 0, nil}
case Esc.Byte():
ev := r.escSequence(&sz)
// Second chance, but only while the buffer ends in an unfinished
// sequence. Re-reading otherwise blocks until the next keystroke,
// holding back whatever follows in the buffer.
if ev.Type == Invalid && incompleteEscape(r.buffer) {
// Second chance, but only for an unfinished sequence. Re-reading
// otherwise blocks until the next keystroke, holding back whatever
// follows in the buffer.
if ev.Type == Invalid && stillArriving(r.buffer, sz) {
r.buffer, result, err = r.getBytes(true)
if err != nil {
return Event{Fatal, 0, nil}
+22
View File
@@ -127,6 +127,28 @@ func TestStringEnd(t *testing.T) {
}
}
// A dropped sequence followed by ALT-[ is not waiting for anything, so the
// bytes in between must not be held back until the next keystroke
func TestStillArriving(t *testing.T) {
for _, c := range []struct {
buffer string
sz int
want bool
}{
{"\x1b[97;5u\a\x1b[", 7, false},
{"\x1b[97;5u\x1b[", 7, false},
{"\x1b[1;\a", 4, false}, // the BEL has already ended it
{"\x1b[1;", 4, true},
{"\x1b[", 2, true},
{"\x1bO1", 3, true},
{"\x1b[<0;1", 3, true},
} {
if got := stillArriving([]byte(c.buffer), c.sz); got != c.want {
t.Errorf("stillArriving(%q, %d) = %v, want %v", c.buffer, c.sz, got, c.want)
}
}
}
func TestStringReply(t *testing.T) {
for _, c := range []struct {
buffer string