From 98a939e84cbdb57f6e161d332230aba06affbb08 Mon Sep 17 00:00:00 2001 From: akbash-bot <300245827+akbash-bot@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:12:13 +0000 Subject: [PATCH] fix: disambiguate shifted punctuation keybinds refs #2674 --- docs/next/CHANGELOG.md | 1 + src/app/input/navigate.rs | 33 +++++++++++++++++++++++++++++++++ src/config/keybinds.rs | 4 ++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 52dd9f0a..385bbfd3 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -19,6 +19,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 +- Prefix keybindings now disambiguate layout-aware shifted punctuation, so a shifted `\` no longer triggers `prefix+|` on keyboard layouts where the same key produces both characters. (#2674) - OpenCode panes now track the root conversation selected in their own TUI for native restore without adopting activity from attached clients. (#2450) - Server stop requests now bypass pane and API traffic, preventing busy sessions from blocking shutdown or admitting a client while shutdown is pending. (#2612) - Fish `Ctrl+Alt` keybindings now work in panes after legacy Alt-prefixed control bytes are decoded with both modifiers. (#2514) diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs index fd6ef273..beb2f363 100644 --- a/src/app/input/navigate.rs +++ b/src/app/input/navigate.rs @@ -2815,6 +2815,39 @@ resize_pane_left = "prefix+shift+left" assert_eq!(action, Some(NavigateAction::LastPane)); } + #[test] + fn shifted_backslash_layout_prefers_horizontal_split_binding() { + let config: Config = toml::from_str( + r#" +[keys] +split_vertical = "prefix+|" +split_horizontal = 'prefix+\' +"#, + ) + .unwrap(); + let mut state = state_with_workspaces(&["test"]); + state.keybinds = config.keybinds(); + let key = crate::input::parse_terminal_key_sequence("\x1b[124:92;2:1u").unwrap(); + assert_eq!(key.code, KeyCode::Char('|')); + assert_eq!(key.modifiers, KeyModifiers::SHIFT); + assert_eq!(key.shifted_codepoint, Some('\\' as u32)); + assert!(state.keybinds.split_horizontal.matches_prefix_key(&key)); + assert!(!state.keybinds.split_vertical.matches_prefix_key(&key)); + + assert_eq!( + action_for_key(&state, key, BindingDispatch::Prefix), + Some(NavigateAction::SplitHorizontal) + ); + assert_eq!( + action_for_key( + &state, + TerminalKey::new(KeyCode::Char('|'), KeyModifiers::empty()), + BindingDispatch::Prefix, + ), + Some(NavigateAction::SplitVertical) + ); + } + #[test] fn prefix_tab_override_can_map_to_last_pane() { let config: Config = toml::from_str( diff --git a/src/config/keybinds.rs b/src/config/keybinds.rs index 79dc87e3..6cddd3ac 100644 --- a/src/config/keybinds.rs +++ b/src/config/keybinds.rs @@ -1437,8 +1437,8 @@ fn shifted_char_matches_expected( let KeyCode::Char(expected) = expected_code else { return false; }; - if shifted_codepoint.and_then(char::from_u32) == Some(expected) { - return true; + if let Some(shifted) = shifted_codepoint.and_then(char::from_u32) { + return shifted == expected; } matches!(actual_code, KeyCode::Char(actual) if actual == expected && is_shifted_punctuation(expected)) }