diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-08-21 21:09:52 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-21 21:09:52 +0300 |
| commit | 2d5f91dd0c2b4c7aa0a8c1d0666b0b0645ec7f9f (patch) | |
| tree | 5156cc00b174f3b1eaf497eb6892eea309829c4c /src/app/input | |
| parent | f72a5ef99e2fbfed49eb739c4fffb0c273656d0b (diff) | |
fix: match generated characters in prefix bindings (#3085)
refs #3079
Diffstat (limited to 'src/app/input')
| -rw-r--r-- | src/app/input/navigate.rs | 120 |
1 files changed, 100 insertions, 20 deletions
diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs index 2cb657a7..8e3d7d6e 100644 --- a/src/app/input/navigate.rs +++ b/src/app/input/navigate.rs @@ -80,27 +80,14 @@ impl App { return; } - if let Some(action) = - non_indexed_action_for_key(&self.state, &raw_key, BindingDispatch::Prefix) - { - self.execute_prefix_key_action(action); - return; - } - - if let Some(binding) = command_for_key(&self.state, &raw_key, BindingDispatch::Prefix) { - self.cancel_copy_mode_if_active(); - self.launch_custom_command(binding, ActionContext::Prefix); - return; - } - - if let Some(action) = - indexed_navigation_action(&self.state, &raw_key, BindingDispatch::Prefix) - { - self.execute_prefix_key_action(action); - return; + match prefix_binding_for_key(&self.state, &raw_key) { + Some(PrefixBindingMatch::Action(action)) => self.execute_prefix_key_action(action), + Some(PrefixBindingMatch::Command(binding)) => { + self.cancel_copy_mode_if_active(); + self.launch_custom_command(binding, ActionContext::Prefix); + } + None => leave_command_mode(&mut self.state), } - - leave_command_mode(&mut self.state); } fn execute_prefix_key_action(&mut self, action: NavigateAction) { @@ -1185,6 +1172,43 @@ pub(crate) enum BindingDispatch { Prefix, } +enum PrefixBindingMatch { + Action(NavigateAction), + Command(crate::config::CustomCommandKeybind), +} + +fn prefix_binding_for_key(state: &AppState, key: &TerminalKey) -> Option<PrefixBindingMatch> { + exact_prefix_binding_for_key(state, key).or_else(|| { + generated_character_key(key) + .as_ref() + .and_then(|generated_key| exact_prefix_binding_for_key(state, generated_key)) + }) +} + +fn exact_prefix_binding_for_key(state: &AppState, key: &TerminalKey) -> Option<PrefixBindingMatch> { + non_indexed_action_for_key(state, key, BindingDispatch::Prefix) + .map(PrefixBindingMatch::Action) + .or_else(|| { + command_for_key(state, key, BindingDispatch::Prefix).map(PrefixBindingMatch::Command) + }) + .or_else(|| { + indexed_navigation_action(state, key, BindingDispatch::Prefix) + .map(PrefixBindingMatch::Action) + }) +} + +fn generated_character_key(key: &TerminalKey) -> Option<TerminalKey> { + let mut characters = key.generated_text.as_deref()?.chars(); + let character = characters.next()?; + if character.is_control() || characters.next().is_some() { + return None; + } + Some(TerminalKey::new( + KeyCode::Char(character), + crossterm::event::KeyModifiers::empty(), + )) +} + pub(crate) fn command_for_key( state: &AppState, key: &TerminalKey, @@ -2817,6 +2841,62 @@ resize_pane_left = "prefix+shift+left" } #[test] + fn generated_character_prefix_binding_falls_back_after_exact_chord() { + let generated_key = crate::input::parse_terminal_key_sequence("\x1b[119;3;124u").unwrap(); + assert_eq!(generated_key.code, KeyCode::Char('w')); + assert_eq!(generated_key.modifiers, KeyModifiers::ALT); + assert_eq!(generated_key.generated_text.as_deref(), Some("|")); + + let generated_only: Config = toml::from_str( + r#" +[keys] +split_vertical = "prefix+|" +"#, + ) + .unwrap(); + let mut state = state_with_workspaces(&["test"]); + state.keybinds = generated_only.keybinds(); + assert!(matches!( + prefix_binding_for_key(&state, &generated_key), + Some(PrefixBindingMatch::Action(NavigateAction::SplitVertical)) + )); + let multi_character_key = + crate::input::parse_terminal_key_sequence("\x1b[119;3;124:120u").unwrap(); + assert!(prefix_binding_for_key(&state, &multi_character_key).is_none()); + + let exact_and_generated: Config = toml::from_str( + r#" +[keys] +split_vertical = "prefix+|" +split_horizontal = "prefix+alt+w" +"#, + ) + .unwrap(); + state.keybinds = exact_and_generated.keybinds(); + assert!(matches!( + prefix_binding_for_key(&state, &generated_key), + Some(PrefixBindingMatch::Action(NavigateAction::SplitHorizontal)) + )); + + let exact_command: Config = toml::from_str( + r#" +[keys] +split_vertical = "prefix+|" + +[[keys.command]] +key = "prefix+alt+w" +command = "echo exact" +"#, + ) + .unwrap(); + state.keybinds = exact_command.keybinds(); + assert!(matches!( + prefix_binding_for_key(&state, &generated_key), + Some(PrefixBindingMatch::Command(binding)) if binding.command == "echo exact" + )); + } + + #[test] fn shifted_backslash_layout_prefers_horizontal_split_binding() { let config: Config = toml::from_str( r#" |
