From 1cc654ebbfceb15f4f56e61011d1a02ea1825a10 Mon Sep 17 00:00:00 2001 From: patriceckhart Date: Tue, 16 Jun 2026 07:46:29 +0200 Subject: [PATCH] Recognize Esc and other control keys in kitty keyboard mode Enabling the kitty keyboard protocol for Shift+Enter made terminals report Esc as CSI 27 u, which the CSI-u parser dropped as KeyUnknown, so Esc stopped aborting the agent. Map kitty control codepoints (Esc=27, Tab=9, Backspace=127/8) back to their dedicated keys. --- packages/tui/input.go | 13 +++++++++++++ packages/tui/input_test.go | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) 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