From 41ece1a8b75912c313cce8f44d4306e90aee435a Mon Sep 17 00:00:00 2001 From: Abhi <43648792+abhipatel12@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:41:10 -0400 Subject: [PATCH] fix(keyboard): Implement Tab and Backspace handling for Kitty Protocol (#7006) --- .../src/ui/contexts/KeypressContext.test.tsx | 80 ++++++++++++++++++- .../cli/src/ui/contexts/KeypressContext.tsx | 26 ++++++ .../cli/src/ui/utils/platformConstants.ts | 2 + 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/ui/contexts/KeypressContext.test.tsx b/packages/cli/src/ui/contexts/KeypressContext.test.tsx index 292de77de5..6abb918a7b 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.test.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.test.tsx @@ -17,6 +17,8 @@ import { EventEmitter } from 'events'; import { KITTY_KEYCODE_ENTER, KITTY_KEYCODE_NUMPAD_ENTER, + KITTY_KEYCODE_TAB, + KITTY_KEYCODE_BACKSPACE, CHAR_CODE_ESC, CHAR_CODE_LEFT_BRACKET, CHAR_CODE_1, @@ -282,10 +284,86 @@ describe('KeypressContext - Kitty Protocol', () => { }); }); + describe('Tab and Backspace handling', () => { + it('should recognize Tab key in kitty protocol', async () => { + const keyHandler = vi.fn(); + const { result } = renderHook(() => useKeypressContext(), { wrapper }); + act(() => result.current.subscribe(keyHandler)); + + act(() => { + stdin.sendKittySequence(`\x1b[${KITTY_KEYCODE_TAB}u`); + }); + + expect(keyHandler).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'tab', + kittyProtocol: true, + shift: false, + }), + ); + }); + + it('should recognize Shift+Tab in kitty protocol', async () => { + const keyHandler = vi.fn(); + const { result } = renderHook(() => useKeypressContext(), { wrapper }); + act(() => result.current.subscribe(keyHandler)); + + // Modifier 2 is Shift + act(() => { + stdin.sendKittySequence(`\x1b[${KITTY_KEYCODE_TAB};2u`); + }); + + expect(keyHandler).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'tab', + kittyProtocol: true, + shift: true, + }), + ); + }); + + it('should recognize Backspace key in kitty protocol', async () => { + const keyHandler = vi.fn(); + const { result } = renderHook(() => useKeypressContext(), { wrapper }); + act(() => result.current.subscribe(keyHandler)); + + act(() => { + stdin.sendKittySequence(`\x1b[${KITTY_KEYCODE_BACKSPACE}u`); + }); + + expect(keyHandler).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'backspace', + kittyProtocol: true, + meta: false, + }), + ); + }); + + it('should recognize Option+Backspace in kitty protocol', async () => { + const keyHandler = vi.fn(); + const { result } = renderHook(() => useKeypressContext(), { wrapper }); + act(() => result.current.subscribe(keyHandler)); + + // Modifier 3 is Alt/Option + act(() => { + stdin.sendKittySequence(`\x1b[${KITTY_KEYCODE_BACKSPACE};3u`); + }); + + expect(keyHandler).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'backspace', + kittyProtocol: true, + meta: true, + }), + ); + }); + }); + describe('paste mode', () => { it('should handle multiline paste as a single event', async () => { const keyHandler = vi.fn(); - const pastedText = 'This \nis \na \nmultiline \npaste.'; + const pastedText = 'This \n is \n a \n multiline \n paste.'; const { result } = renderHook(() => useKeypressContext(), { wrapper, diff --git a/packages/cli/src/ui/contexts/KeypressContext.tsx b/packages/cli/src/ui/contexts/KeypressContext.tsx index 28ed209b6c..ba04e28678 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.tsx @@ -22,8 +22,10 @@ import { PassThrough } from 'stream'; import { BACKSLASH_ENTER_DETECTION_WINDOW_MS, KITTY_CTRL_C, + KITTY_KEYCODE_BACKSPACE, KITTY_KEYCODE_ENTER, KITTY_KEYCODE_NUMPAD_ENTER, + KITTY_KEYCODE_TAB, MAX_KITTY_SEQUENCE_LENGTH, } from '../utils/platformConstants.js'; @@ -136,6 +138,30 @@ export function KeypressProvider({ }; } + if (keyCode === KITTY_KEYCODE_TAB) { + return { + name: 'tab', + ctrl, + meta: alt, + shift, + paste: false, + sequence, + kittyProtocol: true, + }; + } + + if (keyCode === KITTY_KEYCODE_BACKSPACE) { + return { + name: 'backspace', + ctrl, + meta: alt, + shift, + paste: false, + sequence, + kittyProtocol: true, + }; + } + if ( keyCode === KITTY_KEYCODE_ENTER || keyCode === KITTY_KEYCODE_NUMPAD_ENTER diff --git a/packages/cli/src/ui/utils/platformConstants.ts b/packages/cli/src/ui/utils/platformConstants.ts index 2dcd85c0a3..976653103f 100644 --- a/packages/cli/src/ui/utils/platformConstants.ts +++ b/packages/cli/src/ui/utils/platformConstants.ts @@ -22,6 +22,8 @@ export const KITTY_CTRL_C = '[99;5u'; */ export const KITTY_KEYCODE_ENTER = 13; export const KITTY_KEYCODE_NUMPAD_ENTER = 57414; +export const KITTY_KEYCODE_TAB = 9; +export const KITTY_KEYCODE_BACKSPACE = 127; /** * Timing constants for terminal interactions