Codex (and other ratatui-based children) pushes kitty keyboard flags onto the host terminal, so Ctrl-K arrives as `\x1b[107;5u` instead of 0x0B and the palette open never fired. With "report event types" also on, the release event `\x1b[107;5:3u` followed the press and tripped the palette's "unknown ESC sequence → cancel" branch, making the palette flash and close. Add a small CSI scanner / kitty CSI u decoder and use them in two places: matchCtrlK now accepts the legacy byte, the kitty CSI u form, and xterm modifyOtherKeys; the palette's input handler consumes whole CSI sequences, ignores non-press events, and decodes Enter/Esc/ Backspace/arrows/Ctrl-U-N-P in their kitty forms. Ctrl-K Ctrl-K forwards the raw matched bytes so nested TUIs that asked for kitty input still receive kitty input.
57 lines
1.9 KiB
Go
57 lines
1.9 KiB
Go
package app
|
|
|
|
import "testing"
|
|
|
|
func TestMatchCtrlK(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
chunk string
|
|
offset int
|
|
wantMatch bool
|
|
wantAdvance int
|
|
}{
|
|
{"legacy lone byte", "\x0b", 0, true, 1},
|
|
{"legacy followed by text", "\x0bx", 0, true, 1},
|
|
{"kitty plain Ctrl-K", "\x1b[107;5u", 0, true, 8},
|
|
{"kitty with press event", "\x1b[107;5:1u", 0, true, 10},
|
|
{"kitty with key release", "\x1b[107;5:3u", 0, false, 0},
|
|
{"kitty with extra shift", "\x1b[107;6u", 0, false, 0},
|
|
{"kitty no modifier", "\x1b[107u", 0, false, 0},
|
|
{"kitty wrong key", "\x1b[108;5u", 0, false, 0},
|
|
{"kitty with associated text trailing group", "\x1b[107;5;107u", 0, true, 12},
|
|
{"modifyOtherKeys Ctrl-K", "\x1b[27;5;107~", 0, true, 11},
|
|
{"modifyOtherKeys wrong mods", "\x1b[27;6;107~", 0, false, 0},
|
|
{"unrelated CSI", "\x1b[A", 0, false, 0},
|
|
{"plain ascii", "k", 0, false, 0},
|
|
{"empty", "", 0, false, 0},
|
|
{"incomplete CSI", "\x1b[107;5", 0, false, 0},
|
|
{"offset past legacy", "x\x0b", 1, true, 1},
|
|
{"offset past kitty prefix", "x\x1b[107;5u", 1, true, 8},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, adv := matchCtrlK([]byte(tc.chunk), tc.offset)
|
|
if got != tc.wantMatch || adv != tc.wantAdvance {
|
|
t.Fatalf("matchCtrlK(%q, %d) = (%v, %d); want (%v, %d)",
|
|
tc.chunk, tc.offset, got, adv, tc.wantMatch, tc.wantAdvance)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatchCtrlKConsecutive(t *testing.T) {
|
|
// Two kitty Ctrl-K sequences back to back, the chord case.
|
|
chunk := []byte("\x1b[107;5u\x1b[107;5u")
|
|
hit, adv := matchCtrlK(chunk, 0)
|
|
if !hit || adv != 8 {
|
|
t.Fatalf("first: hit=%v adv=%d", hit, adv)
|
|
}
|
|
hit2, adv2 := matchCtrlK(chunk, adv)
|
|
if !hit2 || adv2 != 8 {
|
|
t.Fatalf("second: hit=%v adv=%d", hit2, adv2)
|
|
}
|
|
if adv+adv2 != len(chunk) {
|
|
t.Fatalf("expected to cover the whole chunk, got %d/%d", adv+adv2, len(chunk))
|
|
}
|
|
}
|