Check the parser's CSI and SS3 results against the frame

The parser can stop after a few bytes. A key matched on a prefix kept only
those: CTRL-F as \e[70;5u fired Home and typed ";5u". And a short sequence
it gave up on was dropped, losing ALT-[ followed by "2m".

- Drop the frame when a recognized key covers only part of one over 4 bytes
- Report the ALT key for a complete unrecognized frame of 4 bytes or fewer,
  the same rule the fall-through applies

Reported by Copilot on #4926.
This commit is contained in:
Junegunn Choi committed 2026-10-03 11:59:37 +09:00
1 parent ec365401f0
commit 8bd6ba1830
2 files changed
+31 -9

No files matched your search

+24 -9
View File
@@ -630,20 +630,35 @@ func (r *LightRenderer) setCancel(f func()) {
r.mutex.Unlock()
}
// escSequence parses an escape sequence, widening a CSI or SS3 sequence that
// parseEscSequence recognized the start of but gave up on partway. Consuming
// only the part that parsed would leave the rest to be read as input and typed
// into the query. Complete sequences it does not recognize are dropped there,
// unless they could be an ALT key followed by typed text.
// escSequence parses an escape sequence, then checks a CSI or SS3 result
// against its frame. parseEscSequence can stop after a few bytes, and consuming
// only those would leave the rest to be typed into the query. Complete
// sequences it does not recognize are dropped there, unless they could be an
// ALT key followed by typed text.
func (r *LightRenderer) escSequence(sz *int) Event {
ev := r.parseEscSequence(sz)
if ev.Type != Invalid || len(r.buffer) < 3 || !csiIntroducer(r.buffer[1]) {
if len(r.buffer) < 3 || !csiIntroducer(r.buffer[1]) {
return ev
}
// Only a framed sequence is dropped. One still missing its final byte may
// yet be arriving, and the caller gives it another chance.
if end := csiEnd(r.buffer); end > *sz {
// A frame missing its final byte may still be arriving
end := csiEnd(r.buffer)
if end == 0 {
return ev
}
// The same rule as the fall-through: four bytes or fewer could be ALT-[
// or ALT-O and typed text
if end <= 4 {
if ev.Type == Invalid {
*sz = 2
return AltKey(rune(r.buffer[1]))
}
return ev
}
// A key matched on a prefix, such as Home for \e[70;5u, or a sequence
// given up on partway
if end > *sz {
*sz = end
return Event{Invalid, 0, nil}
}
return ev
}
+7
View File
@@ -13,6 +13,9 @@ func TestUnknownEscapeSequence(t *testing.T) {
}{
// Key encodings fzf does not implement
{"\x1b[97;5u", Invalid, 7},
{"\x1b[70;5u", Invalid, 7}, // not Home, which its prefix \e[7 matches
{"\x1b[42;5u", Invalid, 7}, // nor End
{"\x1b[4;5~", Invalid, 6},
{"\x1b[127;5u", Invalid, 8},
{"\x1b[27;5;127~", Invalid, 11},
{"\x1b[57441;1u", Invalid, 10},
@@ -46,6 +49,10 @@ func TestUnknownEscapeSequence(t *testing.T) {
{"\x1b[ x", Alt, 2},
{"\x1bOx", Alt, 2},
{"\x1bO9A", Alt, 2},
{"\x1b[2m", Alt, 2}, // the parser gives up on these itself
{"\x1b[3x", Alt, 2},
{"\x1b[<a", Alt, 2},
{"\x1bO<a", Alt, 2},
{"\x1b]abc", Alt, 2},
{"\x1b]11;rgb:", Alt, 2}, // terminator has not arrived
{"\x1b]\x1b]", Alt, 2}, // a second sequence must not swallow the ALT key