fix: insert CSI u handler into model.go + add tui tests

The previous commit defined parseCSIuBytes but failed to insert the
handler into Update(). Now properly inserted before the textinput
fallback. Add 10 test cases for parseCSIuBytes covering ctrl+alt+f/c/
m/p/h, ctrl+shift+c, alt+c, and invalid sequences.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
JiXieShiandClaude Opus 4.7 committed 2026-05-24 03:30:43 +08:00
1 parent b1c499b340
commit e9a58dc363
2 files changed
+55

No files matched your search

+22
View File
@@ -240,6 +240,28 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
// Handle CSI u sequences that bubbletea does not parse into KeyMsg
if b, ok := msg.([]byte); ok {
if key, ok2 := parseCSIuBytes(b); ok2 {
keyStr := strings.ToLower(key)
if m.showModal {
last := rune(key[len(key)-1])
fake := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{last}, Alt: strings.Contains(key, "alt+")}
if handled, _ := m.handleModalKey(fake); handled {
return m, nil
}
}
if keyStr == normalizeHotkeyPrefix(m.App.Cfg().HotkeyMod)+"+c" {
m.App.Close()
return m, tea.Quit
}
if handleLocalHotkey(m, keyStr) {
return m, nil
}
}
}
var cmd tea.Cmd
m.input, cmd = m.input.Update(msg)
return m, cmd
+33
View File
@@ -0,0 +1,33 @@
package tui
import "testing"
func TestParseCSIuBytes(t *testing.T) {
tests := []struct {
name string
seq []byte
want string
ok bool
}{
{name: "ctrl+alt+f", seq: []byte{0x1b, '[', '1', '0', '2', ';', '6', 'u'}, want: "ctrl+alt+f", ok: true},
{name: "ctrl+alt+c", seq: []byte{0x1b, '[', '9', '9', ';', '6', 'u'}, want: "ctrl+alt+c", ok: true},
{name: "ctrl+alt+m", seq: []byte{0x1b, '[', '1', '0', '9', ';', '6', 'u'}, want: "ctrl+alt+m", ok: true},
{name: "ctrl+alt+p", seq: []byte{0x1b, '[', '1', '1', '2', ';', '6', 'u'}, want: "ctrl+alt+p", ok: true},
{name: "ctrl+alt+h", seq: []byte{0x1b, '[', '1', '0', '4', ';', '6', 'u'}, want: "ctrl+alt+h", ok: true},
{name: "ctrl+shift+c", seq: []byte{0x1b, '[', '9', '9', ';', '5', 'u'}, want: "ctrl+shift+c", ok: true},
{name: "alt+c (no ctrl)", seq: []byte{0x1b, '[', '9', '9', ';', '2', 'u'}, want: "alt+c", ok: true},
{name: "plain c", seq: []byte{0x1b, '[', '9', '9', ';', '0', 'u'}, want: "c", ok: true},
{name: "not CSI u", seq: []byte{0x1b, '[', 'A'}, want: "", ok: false},
{name: "empty", seq: []byte{}, want: "", ok: false},
{name: "no escape", seq: []byte("hello"), want: "", ok: false},
{name: "ESC [ A (arrow up)", seq: []byte{0x1b, '[', 'A'}, want: "", ok: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := parseCSIuBytes(tt.seq)
if ok != tt.ok || got != tt.want {
t.Fatalf("parseCSIuBytes(%v): got=(%q,%v) want=(%q,%v)", tt.seq, got, ok, tt.want, tt.ok)
}
})
}
}