diff --git a/packages/tui/input.go b/packages/tui/input.go index 3fdf463..6c5fe98 100644 --- a/packages/tui/input.go +++ b/packages/tui/input.go @@ -342,9 +342,22 @@ func keyFromModifiedCode(code, mod int) (Key, bool) { shift := bits&1 != 0 alt := bits&2 != 0 ctrl := bits&4 != 0 + // Kitty keyboard protocol (CSI ... u) reports control keys as their + // codepoints: Esc=27, Enter=13, Tab=9, Backspace=127. Without the + // enhanced-mode handling these arrive as raw bytes; with it enabled + // they come through here, so map them back to their dedicated keys. switch code { case 13: return Key{Kind: KeyEnter, Shift: shift, Alt: alt, Ctrl: ctrl}, true + case 27: + return Key{Kind: KeyEsc, Shift: shift, Alt: alt, Ctrl: ctrl}, true + case 9: + if shift { + return Key{Kind: KeyShiftTab, Alt: alt, Ctrl: ctrl}, true + } + return Key{Kind: KeyTab, Shift: shift, Alt: alt, Ctrl: ctrl}, true + case 127, 8: + return Key{Kind: KeyBackspace, Shift: shift, Alt: alt, Ctrl: ctrl}, true } if ctrl { switch code { diff --git a/packages/tui/input_test.go b/packages/tui/input_test.go index dd1d03d..b071424 100644 --- a/packages/tui/input_test.go +++ b/packages/tui/input_test.go @@ -30,6 +30,25 @@ func TestReaderParsesModifyOtherKeysCtrlC(t *testing.T) { } } +func TestReaderParsesCSIUEsc(t *testing.T) { + k := readKey(t, "\x1b[27u") + if k.Kind != KeyEsc { + t.Fatalf("Read kind=%v, want esc", k.Kind) + } +} + +func TestReaderParsesCSIUTabAndBackspace(t *testing.T) { + if k := readKey(t, "\x1b[9u"); k.Kind != KeyTab { + t.Fatalf("Read kind=%v, want tab", k.Kind) + } + if k := readKey(t, "\x1b[9;2u"); k.Kind != KeyShiftTab { + t.Fatalf("Read kind=%v, want shift-tab", k.Kind) + } + if k := readKey(t, "\x1b[127u"); k.Kind != KeyBackspace { + t.Fatalf("Read kind=%v, want backspace", k.Kind) + } +} + func TestReaderParsesSGRMouseWheel(t *testing.T) { cases := []struct { seq string