From 42e9d4f6ab21ed7469826a400cefd22db630f35c Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Sat, 20 Dec 2025 23:52:30 -0800 Subject: [PATCH 1/4] feat: add chord support --- examples/chords.rs | 81 ++++++++++++++ src/edit_mode/emacs.rs | 122 +++++++++++++-------- src/edit_mode/keybindings.rs | 202 ++++++++++++++++++++++++++++++++--- src/edit_mode/mod.rs | 2 +- src/lib.rs | 2 +- src/utils/query.rs | 2 +- 6 files changed, 348 insertions(+), 63 deletions(-) create mode 100644 examples/chords.rs diff --git a/examples/chords.rs b/examples/chords.rs new file mode 100644 index 00000000..dd79bc52 --- /dev/null +++ b/examples/chords.rs @@ -0,0 +1,81 @@ +// Example demonstrating multi-keystroke chord support in reedline +// +// This example shows how to configure key chords (multi-key sequences) +// that trigger actions. For example, pressing Ctrl+X followed by Ctrl+C +// can be bound to a specific event. + +use crossterm::event::{KeyCode, KeyModifiers}; +use reedline::{ + default_emacs_keybindings, DefaultPrompt, Emacs, KeyCombination, Reedline, + ReedlineEvent, Signal, +}; +use std::io; + +/// Helper to create a KeyCombination for Ctrl+ +fn ctrl(c: char) -> KeyCombination { + KeyCombination { + modifier: KeyModifiers::CONTROL, + key_code: KeyCode::Char(c), + } +} + +fn main() -> io::Result<()> { + println!("Reedline Chord Example"); + println!("======================"); + println!(); + println!("This example demonstrates multi-keystroke chord bindings."); + println!(); + println!("Available chords:"); + println!(" Ctrl+X Ctrl+C - Quit"); + println!(" Ctrl+X Ctrl+Y Ctrl+Z Ctrl+Z Ctrl+Y - Report that nothing happens"); + println!(); + println!("Regular keys and single-key bindings still work normally."); + println!("You may also type 'exit' or press Ctrl+D to quit."); + println!(); + + // Start with the default Emacs keybindings + let mut keybindings = default_emacs_keybindings(); + + // Add chord bindings + // Ctrl+X Ctrl+C: Quit + keybindings.add_sequence_binding( + &[ctrl('x'), ctrl('c')], + ReedlineEvent::CtrlD, + ); + + // Ctrl+X Ctrl+Y Ctrl+Z Ctrl+Z Ctrl+Y: Quit + keybindings.add_sequence_binding( + &[ctrl('x'), ctrl('y'), ctrl('z'), ctrl('z'), ctrl('y')], + ReedlineEvent::ExecuteHostCommand(String::from("Nothing happens")) + ); + + // Create the Emacs edit mode with our custom keybindings + let edit_mode = Box::new(Emacs::new(keybindings)); + + // Create the line editor + let mut line_editor = Reedline::create().with_edit_mode(edit_mode); + + let prompt = DefaultPrompt::default(); + + loop { + let sig = line_editor.read_line(&prompt)?; + match sig { + Signal::Success(buffer) => { + if buffer == "exit" { + println!("Goodbye!"); + break; + } + println!("You typed: {buffer}"); + } + Signal::CtrlD => { + println!("\nQuitting."); + break; + } + Signal::CtrlC => { + println!("Ctrl+C pressed"); + } + } + } + + Ok(()) +} diff --git a/src/edit_mode/emacs.rs b/src/edit_mode/emacs.rs index 155a0741..75de2a80 100644 --- a/src/edit_mode/emacs.rs +++ b/src/edit_mode/emacs.rs @@ -2,7 +2,8 @@ use crate::{ edit_mode::{ keybindings::{ add_common_control_bindings, add_common_edit_bindings, add_common_navigation_bindings, - add_common_selection_bindings, edit_bind, Keybindings, + add_common_selection_bindings, edit_bind, KeyBindingTarget, KeyCombination, + Keybindings, }, EditMode, }, @@ -104,12 +105,16 @@ pub fn default_emacs_keybindings() -> Keybindings { /// This parses the incoming Events like a emacs style-editor pub struct Emacs { + /// Cache for multi-key sequences (chords); will be empty when not in a known chord + cache: Vec, + /// Keybindings for this mode keybindings: Keybindings, } impl Default for Emacs { fn default() -> Self { Emacs { + cache: Vec::new(), keybindings: default_emacs_keybindings(), } } @@ -120,50 +125,10 @@ impl EditMode for Emacs { match event.into() { Event::Key(KeyEvent { code, modifiers, .. - }) => match (modifiers, code) { - (modifier, KeyCode::Char(c)) => { - // Note. The modifier can also be a combination of modifiers, for - // example: - // KeyModifiers::CONTROL | KeyModifiers::ALT - // KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT - // - // Mixed modifiers are used by non american keyboards that have extra - // keys like 'alt gr'. Keep this in mind if in the future there are - // cases where an event is not being captured - let c = match modifier { - KeyModifiers::NONE => c, - _ => c.to_ascii_lowercase(), - }; - - self.keybindings - .find_binding(modifier, KeyCode::Char(c)) - .unwrap_or_else(|| { - if modifier == KeyModifiers::NONE - || modifier == KeyModifiers::SHIFT - || modifier == KeyModifiers::CONTROL | KeyModifiers::ALT - || modifier - == KeyModifiers::CONTROL - | KeyModifiers::ALT - | KeyModifiers::SHIFT - { - ReedlineEvent::Edit(vec![EditCommand::InsertChar( - if modifier == KeyModifiers::SHIFT { - c.to_ascii_uppercase() - } else { - c - }, - )]) - } else { - ReedlineEvent::None - } - }) - } - _ => self - .keybindings - .find_binding(modifiers, code) - .unwrap_or(ReedlineEvent::None), - }, - + }) => self.parse_key_event(KeyCombination { + key_code: code, + modifier: modifiers, + }), Event::Mouse(_) => ReedlineEvent::Mouse, Event::Resize(width, height) => ReedlineEvent::Resize(width, height), Event::FocusGained => ReedlineEvent::None, @@ -182,7 +147,72 @@ impl EditMode for Emacs { impl Emacs { /// Emacs style input parsing constructor if you want to use custom keybindings pub const fn new(keybindings: Keybindings) -> Self { - Emacs { keybindings } + Emacs { + keybindings, + cache: Vec::new(), + } + } + + fn parse_key_event(&mut self, combo: KeyCombination) -> ReedlineEvent { + self.cache.push(normalize_key_combo(&combo)); + + match self.keybindings.find_sequence_binding(&self.cache) { + Some(KeyBindingTarget::Event(event)) => { + // Found a complete binding, clear the cache and return the event + self.cache.clear(); + event + } + Some(KeyBindingTarget::ChordPrefix) => { + // Partial match, wait for the next key + ReedlineEvent::None + } + None => { + // No match, clear the cache. + self.cache.clear(); + + // Check fallback condition of just inserting a normal character. + match combo.key_code { + KeyCode::Char(c) => { + if combo.modifier == KeyModifiers::NONE + || combo.modifier == KeyModifiers::SHIFT + || combo.modifier == KeyModifiers::CONTROL | KeyModifiers::ALT + || combo.modifier + == KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT + { + ReedlineEvent::Edit(vec![EditCommand::InsertChar(c)]) + } else { + ReedlineEvent::None + } + } + _ => ReedlineEvent::None, + } + } + } + } +} + +fn normalize_key_combo(combo: &KeyCombination) -> KeyCombination { + match combo.key_code { + KeyCode::Char(c) => { + // Note. The modifier can also be a combination of modifiers, for + // example: + // KeyModifiers::CONTROL | KeyModifiers::ALT + // KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT + // + // Mixed modifiers are used by non american keyboards that have extra + // keys like 'alt gr'. Keep this in mind if in the future there are + // cases where an event is not being captured + let code = match combo.modifier { + KeyModifiers::NONE => combo.key_code, + _ => KeyCode::Char(c.to_ascii_lowercase()), + }; + + KeyCombination { + modifier: combo.modifier, + key_code: code, + } + } + _ => combo.clone(), } } diff --git a/src/edit_mode/keybindings.rs b/src/edit_mode/keybindings.rs index ef4636c7..6c19f54e 100644 --- a/src/edit_mode/keybindings.rs +++ b/src/edit_mode/keybindings.rs @@ -5,17 +5,48 @@ use { std::collections::HashMap, }; +/// Representation of a key combination: modifier + key code #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)] pub struct KeyCombination { + /// Modifier keys pub modifier: KeyModifiers, + /// The key code pub key_code: KeyCode, } /// Main definition of editor keybindings -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug)] pub struct Keybindings { - /// Defines a keybinding for a reedline event - pub bindings: HashMap, + /// Trie mapping key combination sequences to their corresponding events. + root: KeyBindingNode, +} + +/// Target that a key combination may be bound to. +pub enum KeyBindingTarget { + /// Indicates a binding to an event. + Event(ReedlineEvent), + /// Indicates that this is a prefix to other bindings. + ChordPrefix, +} + +// TODO: Implement Serialize for Keybindings +// TODO: Implement Deserialize for Keybindings + +/// Trie node that represents a key combination's binding. The key +/// combination may *only* be bound to an event, or may be a +/// strict prefix of a chord of key combinations. +#[derive(Clone, Debug)] +enum KeyBindingNode { + /// Indicates a binding to an event. + Event(ReedlineEvent), + /// Indicates that this is a prefix to other bindings. + Prefix(HashMap), +} + +impl KeyBindingNode { + fn new_prefix() -> Self { + KeyBindingNode::Prefix(HashMap::new()) + } } impl Default for Keybindings { @@ -25,14 +56,14 @@ impl Default for Keybindings { } impl Keybindings { - /// New keybining + /// Returns a new, empty keybinding set pub fn new() -> Self { Self { - bindings: HashMap::new(), + root: KeyBindingNode::new_prefix(), } } - /// Defines an empty keybinding object + /// Returns a new, empty keybinding set pub fn empty() -> Self { Self::new() } @@ -56,16 +87,97 @@ impl Keybindings { } let key_combo = KeyCombination { modifier, key_code }; - self.bindings.insert(key_combo, command); + + self.add_sequence_binding(&[key_combo], command); } - /// Find a keybinding based on the modifier and keycode + /// Binds a sequence of key combinations to an event. This allows binding a single + /// key combination, as well as binding a chord of multiple key combinations. + pub fn add_sequence_binding(&mut self, sequence: &[KeyCombination], event: ReedlineEvent) { + if sequence.len() == 0 { + return; + } + + let mut current_target = &mut self.root; + + for i in 0..(sequence.len() - 1) { + match current_target { + KeyBindingNode::Prefix(ref mut map) => { + let combo = &sequence[i]; + + current_target = map + .entry(combo.clone()) + .or_insert_with(KeyBindingNode::new_prefix); + } + KeyBindingNode::Event(_) => { + // Overwrite existing event binding with a prefix + *current_target = KeyBindingNode::new_prefix(); + } + } + } + + let final_combo = &sequence[sequence.len() - 1]; + + match current_target { + KeyBindingNode::Prefix(ref mut map) => { + map.insert(final_combo.clone(), KeyBindingNode::Event(event)); + } + KeyBindingNode::Event(_) => { + // Overwrite existing event binding with a prefix initialized with the event. + let prefix = KeyBindingNode::Prefix(HashMap::from([( + final_combo.clone(), + KeyBindingNode::Event(event), + )])); + *current_target = prefix; + } + } + } + + /// Find a keybinding based on the modifier and keycode. pub fn find_binding(&self, modifier: KeyModifiers, key_code: KeyCode) -> Option { let key_combo = KeyCombination { modifier, key_code }; - self.bindings.get(&key_combo).cloned() + self.find_sequence_binding(&[key_combo]).and_then(|target| { + if let KeyBindingTarget::Event(event) = target { + Some(event) + } else { + None + } + }) + } + + /// Find a keybinding based on a sequence of key combinations. + /// + /// Returns `Some(KeyBindingTarget::Event(ReedlineEvent))` if the sequence is bound to a + /// particular [`ReedlineEvent`]. + /// + /// Returns `Some(KeyBindingTarget::ChordPrefix)` if the sequence is a strict prefix + /// of other bindings. + /// + /// Returns `None` if the sequence is not bound. + pub fn find_sequence_binding(&self, sequence: &[KeyCombination]) -> Option { + let mut current_target = &self.root; + + for i in 0..sequence.len() { + match current_target { + KeyBindingNode::Prefix(map) => { + if let Some(next_target) = map.get(&sequence[i]) { + current_target = next_target; + } else { + return None; + } + } + KeyBindingNode::Event(_) => return None, + } + } + + match current_target { + KeyBindingNode::Prefix(_) => Some(KeyBindingTarget::ChordPrefix), + KeyBindingNode::Event(event) => Some(KeyBindingTarget::Event(event.clone())), + } } - /// Remove a keybinding + /// Remove a single-key keybinding. If the indicated key combination is a strict prefix + /// of chord bindings, those latter bindings are preserved. /// /// Returns `Some(ReedlineEvent)` if the key combination was previously bound to a particular [`ReedlineEvent`] pub fn remove_binding( @@ -74,12 +186,74 @@ impl Keybindings { key_code: KeyCode, ) -> Option { let key_combo = KeyCombination { modifier, key_code }; - self.bindings.remove(&key_combo) + self.remove_sequence_binding(&[key_combo]) + } + + /// Unbind a sequence of key combinations. If the given sequence is a strict prefix + /// of other bindings, those bindings are preserved. + /// + /// Returns `Some(ReedlineEvent)` if the sequence was previously bound to a particular [`ReedlineEvent`] + pub fn remove_sequence_binding( + &mut self, + sequence: &[KeyCombination], + ) -> Option { + let mut current_target = &mut self.root; + + if sequence.len() == 0 { + return None; + } + + for i in 0..(sequence.len() - 1) { + match current_target { + KeyBindingNode::Prefix(map) => { + if let Some(next_target) = map.get_mut(&sequence[i]) { + current_target = next_target; + } else { + return None; + } + } + KeyBindingNode::Event(_) => return None, + } + } + + let final_combo = &sequence[sequence.len() - 1]; + + match current_target { + KeyBindingNode::Prefix(map) => { + if map + .get(final_combo) + .is_none_or(|target| matches!(target, KeyBindingNode::Prefix(_))) + { + None + } else if let Some(KeyBindingNode::Event(old_event)) = map.remove(final_combo) { + Some(old_event) + } else { + None + } + } + KeyBindingNode::Event(_) => None, + } + } + + /// Get assigned single-key keybindings. + pub fn get_keybindings(&self) -> impl IntoIterator { + self.get_sequence_bindings() + .into_iter() + .filter_map(|(seq, event)| { + if let [first, ..] = seq { + Some((first, event)) + } else { + None + } + }) } - /// Get assigned keybindings - pub fn get_keybindings(&self) -> &HashMap { - &self.bindings + /// Get all bindings for key sequences, including single-key bindings and chords. + pub fn get_sequence_bindings( + &self, + ) -> impl IntoIterator { + // TODO + [] } } diff --git a/src/edit_mode/mod.rs b/src/edit_mode/mod.rs index 38e1456f..ce90e2d2 100644 --- a/src/edit_mode/mod.rs +++ b/src/edit_mode/mod.rs @@ -7,5 +7,5 @@ mod vi; pub use base::EditMode; pub use cursors::CursorConfig; pub use emacs::{default_emacs_keybindings, Emacs}; -pub use keybindings::Keybindings; +pub use keybindings::{KeyCombination, Keybindings}; pub use vi::{default_vi_insert_keybindings, default_vi_normal_keybindings, Vi}; diff --git a/src/lib.rs b/src/lib.rs index d202a220..f7032b30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -262,7 +262,7 @@ pub use prompt::{ mod edit_mode; pub use edit_mode::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, - CursorConfig, EditMode, Emacs, Keybindings, Vi, + CursorConfig, EditMode, Emacs, KeyCombination, Keybindings, Vi, }; mod highlighter; diff --git a/src/utils/query.rs b/src/utils/query.rs index 2b762e9a..1fd5785e 100644 --- a/src/utils/query.rs +++ b/src/utils/query.rs @@ -133,7 +133,7 @@ fn get_keybinding_strings( ) -> Vec<(String, String, String, String)> { let mut data: Vec<(String, String, String, String)> = keybindings .get_keybindings() - .iter() + .into_iter() .map(|(combination, event)| { ( mode.to_string(), From 17cc73d012dbde8662428a3f69f0b86dc90c670e Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Sun, 21 Dec 2025 00:00:18 -0800 Subject: [PATCH 2/4] add tests --- src/edit_mode/keybindings.rs | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/edit_mode/keybindings.rs b/src/edit_mode/keybindings.rs index 6c19f54e..1f0d3d53 100644 --- a/src/edit_mode/keybindings.rs +++ b/src/edit_mode/keybindings.rs @@ -22,6 +22,7 @@ pub struct Keybindings { } /// Target that a key combination may be bound to. +#[derive(Clone, Debug, PartialEq, Eq)] pub enum KeyBindingTarget { /// Indicates a binding to an event. Event(ReedlineEvent), @@ -462,3 +463,47 @@ pub fn add_common_selection_bindings(kb: &mut Keybindings) { edit_bind(EC::SelectAll), ); } + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_chord_lookup() { + const BOUND_EVENT: ReedlineEvent = ReedlineEvent::MenuDown; + + let mut kb = Keybindings::new(); + + let sequence = vec![ + KeyCombination { + modifier: KeyModifiers::CONTROL, + key_code: KeyCode::Char('x'), + }, + KeyCombination { + modifier: KeyModifiers::CONTROL, + key_code: KeyCode::Char('c'), + }, + ]; + + kb.add_sequence_binding(&sequence, BOUND_EVENT); + + // Make sure we can find the prefix. + let found_prefix = kb.find_sequence_binding(&sequence[0..1]); + assert_eq!(found_prefix, Some(KeyBindingTarget::ChordPrefix)); + + // Make sure we can find the binding. + let found_binding = kb.find_sequence_binding(&sequence); + assert_eq!( + found_binding, + Some(KeyBindingTarget::Event(BOUND_EVENT)) + ); + + // Make sure we can't find some non-existent binding. + let not_found = kb.find_sequence_binding(&[sequence[0].clone(), KeyCombination { + modifier: KeyModifiers::CONTROL, + key_code: KeyCode::Char('z'), + }]); + assert_eq!(not_found, None); + } +} From b02f150b3e3a6e9242e67e603c16e13d59295e9a Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Mon, 22 Dec 2025 21:47:04 -0800 Subject: [PATCH 3/4] fix: correct formatting --- examples/chords.rs | 11 ++++------- src/edit_mode/keybindings.rs | 17 ++++++++--------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/examples/chords.rs b/examples/chords.rs index dd79bc52..4a525d01 100644 --- a/examples/chords.rs +++ b/examples/chords.rs @@ -6,8 +6,8 @@ use crossterm::event::{KeyCode, KeyModifiers}; use reedline::{ - default_emacs_keybindings, DefaultPrompt, Emacs, KeyCombination, Reedline, - ReedlineEvent, Signal, + default_emacs_keybindings, DefaultPrompt, Emacs, KeyCombination, Reedline, ReedlineEvent, + Signal, }; use std::io; @@ -38,15 +38,12 @@ fn main() -> io::Result<()> { // Add chord bindings // Ctrl+X Ctrl+C: Quit - keybindings.add_sequence_binding( - &[ctrl('x'), ctrl('c')], - ReedlineEvent::CtrlD, - ); + keybindings.add_sequence_binding(&[ctrl('x'), ctrl('c')], ReedlineEvent::CtrlD); // Ctrl+X Ctrl+Y Ctrl+Z Ctrl+Z Ctrl+Y: Quit keybindings.add_sequence_binding( &[ctrl('x'), ctrl('y'), ctrl('z'), ctrl('z'), ctrl('y')], - ReedlineEvent::ExecuteHostCommand(String::from("Nothing happens")) + ReedlineEvent::ExecuteHostCommand(String::from("Nothing happens")), ); // Create the Emacs edit mode with our custom keybindings diff --git a/src/edit_mode/keybindings.rs b/src/edit_mode/keybindings.rs index 1f0d3d53..768906b7 100644 --- a/src/edit_mode/keybindings.rs +++ b/src/edit_mode/keybindings.rs @@ -464,7 +464,6 @@ pub fn add_common_selection_bindings(kb: &mut Keybindings) { ); } - #[cfg(test)] mod tests { use super::*; @@ -494,16 +493,16 @@ mod tests { // Make sure we can find the binding. let found_binding = kb.find_sequence_binding(&sequence); - assert_eq!( - found_binding, - Some(KeyBindingTarget::Event(BOUND_EVENT)) - ); + assert_eq!(found_binding, Some(KeyBindingTarget::Event(BOUND_EVENT))); // Make sure we can't find some non-existent binding. - let not_found = kb.find_sequence_binding(&[sequence[0].clone(), KeyCombination { - modifier: KeyModifiers::CONTROL, - key_code: KeyCode::Char('z'), - }]); + let not_found = kb.find_sequence_binding(&[ + sequence[0].clone(), + KeyCombination { + modifier: KeyModifiers::CONTROL, + key_code: KeyCode::Char('z'), + }, + ]); assert_eq!(not_found, None); } } From 64e98c276f65b9e105ee3802f77437972e14b2dc Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Mon, 22 Dec 2025 22:17:12 -0800 Subject: [PATCH 4/4] fix: clippy issues --- src/edit_mode/keybindings.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/edit_mode/keybindings.rs b/src/edit_mode/keybindings.rs index 768906b7..b236d215 100644 --- a/src/edit_mode/keybindings.rs +++ b/src/edit_mode/keybindings.rs @@ -95,17 +95,15 @@ impl Keybindings { /// Binds a sequence of key combinations to an event. This allows binding a single /// key combination, as well as binding a chord of multiple key combinations. pub fn add_sequence_binding(&mut self, sequence: &[KeyCombination], event: ReedlineEvent) { - if sequence.len() == 0 { + if sequence.is_empty() { return; } let mut current_target = &mut self.root; - for i in 0..(sequence.len() - 1) { + for combo in sequence.iter().take(sequence.len() - 1) { match current_target { KeyBindingNode::Prefix(ref mut map) => { - let combo = &sequence[i]; - current_target = map .entry(combo.clone()) .or_insert_with(KeyBindingNode::new_prefix); @@ -158,10 +156,10 @@ impl Keybindings { pub fn find_sequence_binding(&self, sequence: &[KeyCombination]) -> Option { let mut current_target = &self.root; - for i in 0..sequence.len() { + for combo in sequence { match current_target { KeyBindingNode::Prefix(map) => { - if let Some(next_target) = map.get(&sequence[i]) { + if let Some(next_target) = map.get(combo) { current_target = next_target; } else { return None; @@ -200,14 +198,14 @@ impl Keybindings { ) -> Option { let mut current_target = &mut self.root; - if sequence.len() == 0 { + if sequence.is_empty() { return None; } - for i in 0..(sequence.len() - 1) { + for combo in sequence.iter().take(sequence.len() - 1) { match current_target { KeyBindingNode::Prefix(map) => { - if let Some(next_target) = map.get_mut(&sequence[i]) { + if let Some(next_target) = map.get_mut(combo) { current_target = next_target; } else { return None; @@ -221,10 +219,8 @@ impl Keybindings { match current_target { KeyBindingNode::Prefix(map) => { - if map - .get(final_combo) - .is_none_or(|target| matches!(target, KeyBindingNode::Prefix(_))) - { + // Make sure it's a terminal node before we try to remove it. + if !matches!(map.get(final_combo), Some(KeyBindingNode::Event(_))) { None } else if let Some(KeyBindingNode::Event(old_event)) = map.remove(final_combo) { Some(old_event)