Skip to content

Commit 6c7b0d9

Browse files
committed
fix(bindings): fire all matching bindings, honor ReceiveChar
Alacritty's `process_key_bindings` iterates every binding that matches the (key, mods) pair and runs each one's action, only skipping the "suppress the underlying char" step when a binding's action is `ReceiveChar`. We stopped at the first match, so the common default config that stacks `Ctrl+L → ClearLogNotice` (an alacritty-only UI action we treat as Unsupported) ahead of `Ctrl+L → chars = "\\f"` silently consumed the press and never wrote 0x0c. Mirror alacritty: collect every match, fan them out to dispatch, and let the key fall through to the PTY only when at least one binding is `ReceiveChar`.
1 parent 00cf2f1 commit 6c7b0d9

2 files changed

Lines changed: 29 additions & 7 deletions

File tree

alacritree/src/app.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -464,11 +464,19 @@ impl AlacritreeApp {
464464
let mut actions = Vec::new();
465465
i.events.retain(|ev| {
466466
if let egui::Event::Key { key, pressed: true, modifiers, .. } = ev {
467-
if let Some(action) =
468-
crate::bindings::matches(&self.config.bindings, *key, *modifiers)
469-
{
470-
actions.push(action.clone());
471-
return false;
467+
let matched = crate::bindings::all_matches(
468+
&self.config.bindings,
469+
*key,
470+
*modifiers,
471+
);
472+
if !matched.is_empty() {
473+
let suppress_chars = matched
474+
.iter()
475+
.all(|a| !matches!(a, BindingAction::Named(NamedAction::ReceiveChar)));
476+
for a in matched {
477+
actions.push(a.clone());
478+
}
479+
return !suppress_chars;
472480
}
473481
}
474482
true
@@ -541,6 +549,7 @@ impl AlacritreeApp {
541549
BindingAction::Named(NamedAction::SelectTab(n)) => self.select_tab(n),
542550
BindingAction::Named(NamedAction::SelectLastTab) => self.select_last_tab(),
543551
BindingAction::Named(NamedAction::NoOp) => {},
552+
BindingAction::Named(NamedAction::ReceiveChar) => {},
544553
BindingAction::Named(other) => {
545554
self.dispatch_scroll_or_other(other);
546555
},

alacritree/src/bindings.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ pub enum NamedAction {
4747
Quit,
4848
/// Used to unbind a key — consumes the press without acting on it.
4949
NoOp,
50+
/// Alacritty's pass-through marker: the matching binding runs (no-op for
51+
/// us) but suppress_chars stays off so the key still reaches the PTY.
52+
/// Mirrors `Action::ReceiveChar` in `alacritty/src/input/keyboard.rs`.
53+
ReceiveChar,
5054
}
5155

5256
#[derive(Debug, Deserialize)]
@@ -193,8 +197,16 @@ fn default_bindings() -> Vec<KeyBinding> {
193197
b
194198
}
195199

196-
pub fn matches(bindings: &[KeyBinding], key: Key, mods: Modifiers) -> Option<&BindingAction> {
197-
bindings.iter().find(|b| b.key == key && mods_match(b.mods, mods)).map(|b| &b.action)
200+
/// Every binding that fires for `(key, mods)`. Alacritty runs *all* matching
201+
/// bindings (see `Processor::process_key_bindings`), so the user's typical
202+
/// pattern of stacking `ClearLogNotice` + `chars = "\f"` on Ctrl+L works:
203+
/// the first action is our `Unsupported` no-op, the second writes 0x0c.
204+
pub fn all_matches(
205+
bindings: &[KeyBinding],
206+
key: Key,
207+
mods: Modifiers,
208+
) -> Vec<&BindingAction> {
209+
bindings.iter().filter(|b| b.key == key && mods_match(b.mods, mods)).map(|b| &b.action).collect()
198210
}
199211

200212
/// Alacritty semantics: `Control|Shift` does not fire on Ctrl alone even though
@@ -408,6 +420,7 @@ fn parse_action(name: &str) -> BindingAction {
408420
"SelectLastTab" => BindingAction::Named(SelectLastTab),
409421
"Quit" => BindingAction::Named(Quit),
410422
"None" => BindingAction::Named(NoOp),
423+
"ReceiveChar" => BindingAction::Named(ReceiveChar),
411424
other => BindingAction::Unsupported(other.to_string()),
412425
}
413426
}

0 commit comments

Comments
 (0)