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 <noreply@anthropic.com>
This commit is contained in:
JiXieShi
2026-05-24 03:30:43 +08:00
parent b1c499b340
commit e9a58dc363
2 changed files with 341 additions and 286 deletions
+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)
}
})
}
}