From 1eed66ba414ba550d6c2ad9a08c5a9e244fbf377 Mon Sep 17 00:00:00 2001 From: akbash-bot <300245827+akbash-bot@users.noreply.github.com> Date: Sun, 9 Aug 2026 10:33:16 +0000 Subject: [PATCH] fix(input): handle kitty shift-tab in settings refs #2556 --- docs/next/CHANGELOG.md | 1 + src/app/input/settings.rs | 29 +++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 72d3b0f52a..dd29ef7411 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -14,6 +14,7 @@ - Experimental pane graphics now support bounded named layers, acknowledged full-RGBA primary-layer direct file frames on audited local terminals, owned BGRA fallback, exact pixel mouse input, and placement-only resize replay. ### Fixed +- Shift+Tab now moves to the previous Settings section in terminals that report it through the Kitty keyboard protocol. (#2556) - Fish `Ctrl+Alt` keybindings now work in panes after legacy Alt-prefixed control bytes are decoded with both modifiers. (#2514) - `herdr config check` now reports unknown built-in theme names instead of silently accepting them. (#2452) - macOS `herdr --remote` clients now keep the accepted bridge socket blocking, preventing an immediate disconnect after the protocol handshake. (#2478, thanks @mathijshenquet) diff --git a/src/app/input/settings.rs b/src/app/input/settings.rs index fb1a7d757c..9c45bb3a10 100644 --- a/src/app/input/settings.rs +++ b/src/app/input/settings.rs @@ -145,8 +145,9 @@ fn apply_settings(state: &mut AppState) -> Option { } pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Option { + let (key_code, _) = crate::config::normalize_key_combo((key.code, key.modifiers)); match state.settings.section { - SettingsSection::Theme => match key.code { + SettingsSection::Theme => match key_code { KeyCode::Up | KeyCode::Char('k') => { let previous = state.settings.list.selected; state.settings.list.move_prev(); @@ -175,7 +176,7 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti _ => {} }, }, - SettingsSection::Indicators => match key.code { + SettingsSection::Indicators => match key_code { KeyCode::Up | KeyCode::Char('k') | KeyCode::Down | KeyCode::Char('j') => { state.settings.list.selected = 1 - state.settings.list.selected.min(1); } @@ -199,7 +200,7 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti } } }, - SettingsSection::Sound => match key.code { + SettingsSection::Sound => match key_code { KeyCode::Up | KeyCode::Char('k') | KeyCode::Down | KeyCode::Char('j') => { state.settings.list.selected = 1 - state.settings.list.selected.min(1); } @@ -223,7 +224,7 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti } } }, - SettingsSection::Toast => match key.code { + SettingsSection::Toast => match key_code { KeyCode::Up | KeyCode::Char('k') => state.settings.list.move_prev(), KeyCode::Down | KeyCode::Char('j') => state.settings.list.move_next(4), KeyCode::Enter | KeyCode::Char(' ') => { @@ -246,7 +247,7 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti } } }, - SettingsSection::PaneLabels => match key.code { + SettingsSection::PaneLabels => match key_code { KeyCode::Up | KeyCode::Char('k') | KeyCode::Down | KeyCode::Char('j') => { state.settings.list.selected = 1 - state.settings.list.selected.min(1); } @@ -270,7 +271,7 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti } } }, - SettingsSection::Integrations => match key.code { + SettingsSection::Integrations => match key_code { KeyCode::Enter | KeyCode::Char(' ') if integrations_need_install(state) => { return Some(SettingsAction::InstallRecommendedIntegrations); } @@ -554,6 +555,22 @@ mod tests { assert_eq!(state.mode, Mode::Settings); } + #[test] + fn kitty_shift_tab_moves_to_previous_settings_section() { + let mut state = state_with_workspaces(&["test"]); + open_settings_at(&mut state, SettingsSection::Indicators); + let mut events = crate::raw_input::parse_raw_input_bytes_sync(b"\x1b[9;2u"); + let crate::raw_input::RawInputEvent::Key(key) = events.remove(0) else { + panic!("expected key event"); + }; + assert_eq!(key.code, KeyCode::Tab); + assert_eq!(key.modifiers, KeyModifiers::SHIFT); + + update_settings_state(&mut state, key.as_key_event()); + + assert_eq!(state.settings.section, SettingsSection::Theme); + } + #[test] fn settings_tab_cycle_wraps_after_integrations() { let mut state = state_with_workspaces(&["test"]);