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.
109 lines
2.9 KiB
Go
109 lines
2.9 KiB
Go
package app
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/harrybrwn/patterm/internal/preset"
|
|
)
|
|
|
|
func newTestPalette() *paletteState {
|
|
return newPalette(nil, "", preset.Set{})
|
|
}
|
|
|
|
func TestPaletteIgnoresKittyReleaseEvent(t *testing.T) {
|
|
// A kitty key-release for Ctrl-K. With the legacy handler this looked
|
|
// like ESC followed by `[`, which fell through to cancel.
|
|
p := newTestPalette()
|
|
chunk := []byte("\x1b[107;5:3u")
|
|
action, done, adv := p.handleInput(chunk, 0)
|
|
if done {
|
|
t.Fatalf("release event closed palette: action=%+v", action)
|
|
}
|
|
if adv != len(chunk) {
|
|
t.Fatalf("advance %d, want %d", adv, len(chunk))
|
|
}
|
|
}
|
|
|
|
func TestPaletteEscViaKittyCancels(t *testing.T) {
|
|
p := newTestPalette()
|
|
chunk := []byte("\x1b[27u")
|
|
action, done, adv := p.handleInput(chunk, 0)
|
|
if !done || action.kind != "cancel" {
|
|
t.Fatalf("Esc via CSI u didn't cancel: action=%+v done=%v", action, done)
|
|
}
|
|
if adv != len(chunk) {
|
|
t.Fatalf("advance %d, want %d", adv, len(chunk))
|
|
}
|
|
}
|
|
|
|
func TestPaletteBareEscCancels(t *testing.T) {
|
|
p := newTestPalette()
|
|
action, done, adv := p.handleInput([]byte{0x1b}, 0)
|
|
if !done || action.kind != "cancel" {
|
|
t.Fatalf("bare ESC didn't cancel: action=%+v done=%v", action, done)
|
|
}
|
|
if adv != 1 {
|
|
t.Fatalf("advance %d, want 1", adv)
|
|
}
|
|
}
|
|
|
|
func TestPaletteKittyArrowsNavigate(t *testing.T) {
|
|
pr := []*preset.Preset{{Name: "a"}, {Name: "b"}, {Name: "c"}}
|
|
p := newPalette(nil, "", preset.Set{Agents: pr})
|
|
if p.cursor != 0 {
|
|
t.Fatalf("initial cursor %d", p.cursor)
|
|
}
|
|
// Kitty functional Down arrow.
|
|
_, _, adv := p.handleInput([]byte("\x1b[57353u"), 0)
|
|
if adv != 8 {
|
|
t.Fatalf("advance %d", adv)
|
|
}
|
|
if p.cursor != 1 {
|
|
t.Fatalf("cursor %d after Down, want 1", p.cursor)
|
|
}
|
|
// Kitty functional Up arrow.
|
|
_, _, _ = p.handleInput([]byte("\x1b[57352u"), 0)
|
|
if p.cursor != 0 {
|
|
t.Fatalf("cursor %d after Up, want 0", p.cursor)
|
|
}
|
|
}
|
|
|
|
func TestPaletteLegacyArrowsStillWork(t *testing.T) {
|
|
pr := []*preset.Preset{{Name: "a"}, {Name: "b"}}
|
|
p := newPalette(nil, "", preset.Set{Agents: pr})
|
|
_, _, adv := p.handleInput([]byte("\x1b[B"), 0)
|
|
if adv != 3 {
|
|
t.Fatalf("advance %d", adv)
|
|
}
|
|
if p.cursor != 1 {
|
|
t.Fatalf("cursor %d, want 1", p.cursor)
|
|
}
|
|
}
|
|
|
|
func TestPaletteKittyEnterAccepts(t *testing.T) {
|
|
pr := []*preset.Preset{{Name: "x"}}
|
|
p := newPalette(nil, "", preset.Set{Agents: pr})
|
|
action, done, _ := p.handleInput([]byte("\x1b[13u"), 0)
|
|
if !done || action.kind != "spawn-agent" {
|
|
t.Fatalf("Enter via CSI u didn't accept: action=%+v done=%v", action, done)
|
|
}
|
|
}
|
|
|
|
func TestPaletteKittyBackspace(t *testing.T) {
|
|
p := newTestPalette()
|
|
p.query = []rune("hello")
|
|
_, _, _ = p.handleInput([]byte("\x1b[127u"), 0)
|
|
if string(p.query) != "hell" {
|
|
t.Fatalf("query %q after backspace", string(p.query))
|
|
}
|
|
}
|
|
|
|
func TestPaletteLegacyPrintableTypes(t *testing.T) {
|
|
p := newTestPalette()
|
|
_, _, _ = p.handleInput([]byte("a"), 0)
|
|
_, _, _ = p.handleInput([]byte("b"), 0)
|
|
if string(p.query) != "ab" {
|
|
t.Fatalf("query %q", string(p.query))
|
|
}
|
|
}
|