1use std::rc::Rc;
2
3use collections::HashMap;
4
5use crate::{Action, InvalidKeystrokeError, KeyBindingContextPredicate, Keystroke};
6use smallvec::SmallVec;
7
8/// A keybinding and its associated metadata, from the keymap.
9pub struct KeyBinding {
10 pub(crate) action: Box<dyn Action>,
11 pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
12 pub(crate) context_predicate: Option<Rc<KeyBindingContextPredicate>>,
13}
14
15impl Clone for KeyBinding {
16 fn clone(&self) -> Self {
17 KeyBinding {
18 action: self.action.boxed_clone(),
19 keystrokes: self.keystrokes.clone(),
20 context_predicate: self.context_predicate.clone(),
21 }
22 }
23}
24
25impl KeyBinding {
26 /// Construct a new keybinding from the given data. Panics on parse error.
27 pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
28 let context_predicate = if let Some(context) = context {
29 Some(KeyBindingContextPredicate::parse(context).unwrap().into())
30 } else {
31 None
32 };
33 Self::load(keystrokes, Box::new(action), context_predicate, None).unwrap()
34 }
35
36 /// Load a keybinding from the given raw data.
37 pub fn load(
38 keystrokes: &str,
39 action: Box<dyn Action>,
40 context_predicate: Option<Rc<KeyBindingContextPredicate>>,
41 key_equivalents: Option<&HashMap<char, char>>,
42 ) -> std::result::Result<Self, InvalidKeystrokeError> {
43 let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes
44 .split_whitespace()
45 .map(Keystroke::parse)
46 .collect::<std::result::Result<_, _>>()?;
47
48 if let Some(equivalents) = key_equivalents {
49 for keystroke in keystrokes.iter_mut() {
50 if keystroke.key.chars().count() == 1 {
51 if let Some(key) = equivalents.get(&keystroke.key.chars().next().unwrap()) {
52 keystroke.key = key.to_string();
53 }
54 }
55 }
56 }
57
58 Ok(Self {
59 keystrokes,
60 action,
61 context_predicate,
62 })
63 }
64
65 /// Check if the given keystrokes match this binding.
66 pub fn match_keystrokes(&self, typed: &[Keystroke]) -> Option<bool> {
67 if self.keystrokes.len() < typed.len() {
68 return None;
69 }
70
71 for (target, typed) in self.keystrokes.iter().zip(typed.iter()) {
72 if !typed.should_match(target) {
73 return None;
74 }
75 }
76
77 Some(self.keystrokes.len() > typed.len())
78 }
79
80 /// Get the keystrokes associated with this binding
81 pub fn keystrokes(&self) -> &[Keystroke] {
82 self.keystrokes.as_slice()
83 }
84
85 /// Get the action associated with this binding
86 pub fn action(&self) -> &dyn Action {
87 self.action.as_ref()
88 }
89
90 /// Get the predicate used to match this binding
91 pub fn predicate(&self) -> Option<Rc<KeyBindingContextPredicate>> {
92 self.context_predicate.as_ref().map(|rc| rc.clone())
93 }
94}
95
96impl std::fmt::Debug for KeyBinding {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 f.debug_struct("KeyBinding")
99 .field("keystrokes", &self.keystrokes)
100 .field("context_predicate", &self.context_predicate)
101 .field("action", &self.action.name())
102 .finish()
103 }
104}