From baeccade5424e0a2e33c382a6c4918df9151df34 Mon Sep 17 00:00:00 2001 From: Lucas Rangel Cezimbra Date: Thu, 12 Mar 2026 20:36:45 -0300 Subject: [PATCH] fix(tui): restore shift+tab in xterm modifyOtherKeys mode (#2081) --- packages/coding-agent/CHANGELOG.md | 1 + packages/tui/CHANGELOG.md | 1 + packages/tui/src/keys.ts | 11 +++++++++-- packages/tui/test/keys.test.ts | 10 ++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 82379c57..79ebe8c2 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Fixed interactive input fields backed by the TUI `Input` component to scroll by visual column width for wide Unicode text (CJK, fullwidth characters), preventing rendered line overflow and TUI crashes in places like search and filter inputs ([#1982](https://github.com/badlogic/pi-mono/issues/1982)) +- Fixed `shift+tab` and other modified Tab bindings in tmux when `extended-keys-format` is left at the default `xterm` ## [0.57.1] - 2026-03-07 diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index a28830f5..d9f6dfc5 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Fixed `Input` horizontal scrolling for wide Unicode text (CJK, fullwidth characters) to use visual column width and strict slice boundaries, preventing rendered line overflow and TUI crashes ([#1982](https://github.com/badlogic/pi-mono/issues/1982)) +- Fixed xterm `modifyOtherKeys` handling for `Tab` in `matchesKey()`, restoring `shift+tab` and other modified Tab bindings in tmux when `extended-keys-format` is left at the default `xterm` ## [0.57.1] - 2026-03-07 diff --git a/packages/tui/src/keys.ts b/packages/tui/src/keys.ts index 57366f6f..5d107d5a 100644 --- a/packages/tui/src/keys.ts +++ b/packages/tui/src/keys.ts @@ -769,12 +769,19 @@ export function matchesKey(data: string, keyId: KeyId): boolean { case "tab": if (shift && !ctrl && !alt) { - return data === "\x1b[Z" || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift); + return ( + data === "\x1b[Z" || + matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift) || + matchesModifyOtherKeys(data, CODEPOINTS.tab, MODIFIERS.shift) + ); } if (modifier === 0) { return data === "\t" || matchesKittySequence(data, CODEPOINTS.tab, 0); } - return matchesKittySequence(data, CODEPOINTS.tab, modifier); + return ( + matchesKittySequence(data, CODEPOINTS.tab, modifier) || + matchesModifyOtherKeys(data, CODEPOINTS.tab, modifier) + ); case "enter": case "return": diff --git a/packages/tui/test/keys.test.ts b/packages/tui/test/keys.test.ts index e157af9e..87926728 100644 --- a/packages/tui/test/keys.test.ts +++ b/packages/tui/test/keys.test.ts @@ -157,6 +157,16 @@ describe("matchesKey", () => { assert.strictEqual(parseKey("\x1b[27;3;13~"), "alt+enter"); }); + it("should match xterm modifyOtherKeys Tab variants", () => { + setKittyProtocolActive(false); + assert.strictEqual(matchesKey("\x1b[27;2;9~", "shift+tab"), true); + assert.strictEqual(matchesKey("\x1b[27;5;9~", "ctrl+tab"), true); + assert.strictEqual(matchesKey("\x1b[27;3;9~", "alt+tab"), true); + assert.strictEqual(parseKey("\x1b[27;2;9~"), "shift+tab"); + assert.strictEqual(parseKey("\x1b[27;5;9~"), "ctrl+tab"); + assert.strictEqual(parseKey("\x1b[27;3;9~"), "alt+tab"); + }); + it("should match xterm modifyOtherKeys symbol combos", () => { setKittyProtocolActive(false); assert.strictEqual(matchesKey("\x1b[27;5;47~", "ctrl+/"), true);