mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 00:01:06 +00:00
@@ -3,6 +3,7 @@
|
||||
## Unreleased
|
||||
|
||||
### Fixed
|
||||
- Prefix bindings such as `prefix+|` now recognize characters produced by macOS Option and custom keyboard layouts, while exact chords such as `prefix+alt+w` keep priority. (#3079, thanks @vlcinsky)
|
||||
- Direct terminal attaches now preserve multiline pastes as one paste instead of submitting each line separately. (#3054)
|
||||
- Windows panes now keep bare `cursor-agent` launches detected after Cursor hands off to its bundled Node process. (#3032)
|
||||
- Oversized Kitty images no longer prevent smaller images shown later in the same pane from rendering. (#3033)
|
||||
|
||||
+100
-20
@@ -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;
|
||||
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),
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -2816,6 +2840,62 @@ resize_pane_left = "prefix+shift+left"
|
||||
assert_eq!(action, Some(NavigateAction::LastPane));
|
||||
}
|
||||
|
||||
#[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(
|
||||
|
||||
Reference in New Issue
Block a user