keymap_matcher.rs

  1mod binding;
  2mod keymap;
  3mod keymap_context;
  4mod keystroke;
  5
  6use std::{any::TypeId, fmt::Debug};
  7
  8use collections::HashMap;
  9use serde::Deserialize;
 10use smallvec::SmallVec;
 11
 12use crate::{impl_actions, Action};
 13
 14pub use binding::{Binding, BindingMatchResult};
 15pub use keymap::Keymap;
 16pub use keymap_context::{KeymapContext, KeymapContextPredicate};
 17pub use keystroke::Keystroke;
 18
 19#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
 20pub struct KeyPressed {
 21    #[serde(default)]
 22    pub keystroke: Keystroke,
 23}
 24
 25impl_actions!(gpui, [KeyPressed]);
 26
 27pub struct KeymapMatcher {
 28    pending_views: HashMap<usize, KeymapContext>,
 29    pending_keystrokes: Vec<Keystroke>,
 30    keymap: Keymap,
 31}
 32
 33impl KeymapMatcher {
 34    pub fn new(keymap: Keymap) -> Self {
 35        Self {
 36            pending_views: Default::default(),
 37            pending_keystrokes: Vec::new(),
 38            keymap,
 39        }
 40    }
 41
 42    pub fn set_keymap(&mut self, keymap: Keymap) {
 43        self.clear_pending();
 44        self.keymap = keymap;
 45    }
 46
 47    pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
 48        self.clear_pending();
 49        self.keymap.add_bindings(bindings);
 50    }
 51
 52    pub fn clear_bindings(&mut self) {
 53        self.clear_pending();
 54        self.keymap.clear();
 55    }
 56
 57    pub fn bindings_for_action_type(&self, action_type: TypeId) -> impl Iterator<Item = &Binding> {
 58        self.keymap.bindings_for_action_type(action_type)
 59    }
 60
 61    pub fn clear_pending(&mut self) {
 62        self.pending_keystrokes.clear();
 63        self.pending_views.clear();
 64    }
 65
 66    pub fn has_pending_keystrokes(&self) -> bool {
 67        !self.pending_keystrokes.is_empty()
 68    }
 69
 70    pub fn push_keystroke(
 71        &mut self,
 72        keystroke: Keystroke,
 73        dispatch_path: Vec<(usize, KeymapContext)>,
 74    ) -> MatchResult {
 75        let mut any_pending = false;
 76        let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Vec::new();
 77
 78        let first_keystroke = self.pending_keystrokes.is_empty();
 79        self.pending_keystrokes.push(keystroke.clone());
 80
 81        for (view_id, context) in dispatch_path {
 82            // Don't require pending view entry if there are no pending keystrokes
 83            if !first_keystroke && !self.pending_views.contains_key(&view_id) {
 84                continue;
 85            }
 86
 87            // If there is a previous view context, invalidate that view if it
 88            // has changed
 89            if let Some(previous_view_context) = self.pending_views.remove(&view_id) {
 90                if previous_view_context != context {
 91                    continue;
 92                }
 93            }
 94
 95            // Find the bindings which map the pending keystrokes and current context
 96            for binding in self.keymap.bindings().iter().rev() {
 97                match binding.match_keys_and_context(&self.pending_keystrokes, &context) {
 98                    BindingMatchResult::Complete(mut action) => {
 99                        // Swap in keystroke for special KeyPressed action
100                        if action.name() == "KeyPressed" && action.namespace() == "gpui" {
101                            action = Box::new(KeyPressed {
102                                keystroke: keystroke.clone(),
103                            });
104                        }
105                        matched_bindings.push((view_id, action))
106                    }
107                    BindingMatchResult::Partial => {
108                        self.pending_views.insert(view_id, context.clone());
109                        any_pending = true;
110                    }
111                    _ => {}
112                }
113            }
114        }
115
116        if !any_pending {
117            self.clear_pending();
118        }
119
120        if !matched_bindings.is_empty() {
121            MatchResult::Matches(matched_bindings)
122        } else if any_pending {
123            MatchResult::Pending
124        } else {
125            MatchResult::None
126        }
127    }
128
129    pub fn keystrokes_for_action(
130        &self,
131        action: &dyn Action,
132        context: &KeymapContext,
133    ) -> Option<SmallVec<[Keystroke; 2]>> {
134        self.keymap
135            .bindings()
136            .iter()
137            .rev()
138            .find_map(|binding| binding.keystrokes_for_action(action, context))
139    }
140}
141
142impl Default for KeymapMatcher {
143    fn default() -> Self {
144        Self::new(Keymap::default())
145    }
146}
147
148pub enum MatchResult {
149    None,
150    Pending,
151    Matches(Vec<(usize, Box<dyn Action>)>),
152}
153
154impl Debug for MatchResult {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        match self {
157            MatchResult::None => f.debug_struct("MatchResult::None").finish(),
158            MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(),
159            MatchResult::Matches(matches) => f
160                .debug_list()
161                .entries(
162                    matches
163                        .iter()
164                        .map(|(view_id, action)| format!("{view_id}, {}", action.name())),
165                )
166                .finish(),
167        }
168    }
169}
170
171impl PartialEq for MatchResult {
172    fn eq(&self, other: &Self) -> bool {
173        match (self, other) {
174            (MatchResult::None, MatchResult::None) => true,
175            (MatchResult::Pending, MatchResult::Pending) => true,
176            (MatchResult::Matches(matches), MatchResult::Matches(other_matches)) => {
177                matches.len() == other_matches.len()
178                    && matches.iter().zip(other_matches.iter()).all(
179                        |((view_id, action), (other_view_id, other_action))| {
180                            view_id == other_view_id && action.eq(other_action.as_ref())
181                        },
182                    )
183            }
184            _ => false,
185        }
186    }
187}
188
189impl Eq for MatchResult {}
190
191impl Clone for MatchResult {
192    fn clone(&self) -> Self {
193        match self {
194            MatchResult::None => MatchResult::None,
195            MatchResult::Pending => MatchResult::Pending,
196            MatchResult::Matches(matches) => MatchResult::Matches(
197                matches
198                    .iter()
199                    .map(|(view_id, action)| (*view_id, Action::boxed_clone(action.as_ref())))
200                    .collect(),
201            ),
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use anyhow::Result;
209    use serde::Deserialize;
210
211    use crate::{actions, impl_actions, keymap_matcher::KeymapContext};
212
213    use super::*;
214
215    #[test]
216    fn test_push_keystroke() -> Result<()> {
217        actions!(test, [B, AB, C, D, DA]);
218
219        let mut context1 = KeymapContext::default();
220        context1.set.insert("1".into());
221
222        let mut context2 = KeymapContext::default();
223        context2.set.insert("2".into());
224
225        let dispatch_path = vec![(2, context2), (1, context1)];
226
227        let keymap = Keymap::new(vec![
228            Binding::new("a b", AB, Some("1")),
229            Binding::new("b", B, Some("2")),
230            Binding::new("c", C, Some("2")),
231            Binding::new("d", D, Some("1")),
232            Binding::new("d", D, Some("2")),
233            Binding::new("d a", DA, Some("2")),
234        ]);
235
236        let mut matcher = KeymapMatcher::new(keymap);
237
238        // Binding with pending prefix always takes precedence
239        assert_eq!(
240            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
241            MatchResult::Pending,
242        );
243        // B alone doesn't match because a was pending, so AB is returned instead
244        assert_eq!(
245            matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
246            MatchResult::Matches(vec![(1, Box::new(AB))]),
247        );
248        assert!(!matcher.has_pending_keystrokes());
249
250        // Without an a prefix, B is dispatched like expected
251        assert_eq!(
252            matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
253            MatchResult::Matches(vec![(2, Box::new(B))]),
254        );
255        assert!(!matcher.has_pending_keystrokes());
256
257        // If a is prefixed, C will not be dispatched because there
258        // was a pending binding for it
259        assert_eq!(
260            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
261            MatchResult::Pending,
262        );
263        assert_eq!(
264            matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
265            MatchResult::None,
266        );
267        assert!(!matcher.has_pending_keystrokes());
268
269        // If a single keystroke matches multiple bindings in the tree
270        // all of them are returned so that we can fallback if the action
271        // handler decides to propagate the action
272        assert_eq!(
273            matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
274            MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
275        );
276        // If none of the d action handlers consume the binding, a pending
277        // binding may then be used
278        assert_eq!(
279            matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
280            MatchResult::Matches(vec![(2, Box::new(DA))]),
281        );
282        assert!(!matcher.has_pending_keystrokes());
283
284        Ok(())
285    }
286
287    #[test]
288    fn test_keystroke_parsing() -> Result<()> {
289        assert_eq!(
290            Keystroke::parse("ctrl-p")?,
291            Keystroke {
292                key: "p".into(),
293                ctrl: true,
294                alt: false,
295                shift: false,
296                cmd: false,
297                function: false,
298            }
299        );
300
301        assert_eq!(
302            Keystroke::parse("alt-shift-down")?,
303            Keystroke {
304                key: "down".into(),
305                ctrl: false,
306                alt: true,
307                shift: true,
308                cmd: false,
309                function: false,
310            }
311        );
312
313        assert_eq!(
314            Keystroke::parse("shift-cmd--")?,
315            Keystroke {
316                key: "-".into(),
317                ctrl: false,
318                alt: false,
319                shift: true,
320                cmd: true,
321                function: false,
322            }
323        );
324
325        Ok(())
326    }
327
328    #[test]
329    fn test_context_predicate_parsing() -> Result<()> {
330        use KeymapContextPredicate::*;
331
332        assert_eq!(
333            KeymapContextPredicate::parse("a && (b == c || d != e)")?,
334            And(
335                Box::new(Identifier("a".into())),
336                Box::new(Or(
337                    Box::new(Equal("b".into(), "c".into())),
338                    Box::new(NotEqual("d".into(), "e".into())),
339                ))
340            )
341        );
342
343        assert_eq!(
344            KeymapContextPredicate::parse("!a")?,
345            Not(Box::new(Identifier("a".into())),)
346        );
347
348        Ok(())
349    }
350
351    #[test]
352    fn test_context_predicate_eval() -> Result<()> {
353        let predicate = KeymapContextPredicate::parse("a && b || c == d")?;
354
355        let mut context = KeymapContext::default();
356        context.set.insert("a".into());
357        assert!(!predicate.eval(&context));
358
359        context.set.insert("b".into());
360        assert!(predicate.eval(&context));
361
362        context.set.remove("b");
363        context.map.insert("c".into(), "x".into());
364        assert!(!predicate.eval(&context));
365
366        context.map.insert("c".into(), "d".into());
367        assert!(predicate.eval(&context));
368
369        let predicate = KeymapContextPredicate::parse("!a")?;
370        assert!(predicate.eval(&KeymapContext::default()));
371
372        Ok(())
373    }
374
375    #[test]
376    fn test_matcher() -> Result<()> {
377        #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
378        pub struct A(pub String);
379        impl_actions!(test, [A]);
380        actions!(test, [B, Ab]);
381
382        #[derive(Clone, Debug, Eq, PartialEq)]
383        struct ActionArg {
384            a: &'static str,
385        }
386
387        let keymap = Keymap::new(vec![
388            Binding::new("a", A("x".to_string()), Some("a")),
389            Binding::new("b", B, Some("a")),
390            Binding::new("a b", Ab, Some("a || b")),
391        ]);
392
393        let mut context_a = KeymapContext::default();
394        context_a.set.insert("a".into());
395
396        let mut context_b = KeymapContext::default();
397        context_b.set.insert("b".into());
398
399        let mut matcher = KeymapMatcher::new(keymap);
400
401        // Basic match
402        assert_eq!(
403            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
404            MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
405        );
406        matcher.clear_pending();
407
408        // Multi-keystroke match
409        assert_eq!(
410            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
411            MatchResult::Pending
412        );
413        assert_eq!(
414            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
415            MatchResult::Matches(vec![(1, Box::new(Ab))])
416        );
417        matcher.clear_pending();
418
419        // Failed matches don't interfere with matching subsequent keys
420        assert_eq!(
421            matcher.push_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
422            MatchResult::None
423        );
424        assert_eq!(
425            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
426            MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
427        );
428        matcher.clear_pending();
429
430        // Pending keystrokes are cleared when the context changes
431        assert_eq!(
432            matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
433            MatchResult::Pending
434        );
435        assert_eq!(
436            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
437            MatchResult::None
438        );
439        matcher.clear_pending();
440
441        let mut context_c = KeymapContext::default();
442        context_c.set.insert("c".into());
443
444        // Pending keystrokes are maintained per-view
445        assert_eq!(
446            matcher.push_keystroke(
447                Keystroke::parse("a")?,
448                vec![(1, context_b.clone()), (2, context_c.clone())]
449            ),
450            MatchResult::Pending
451        );
452        assert_eq!(
453            matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
454            MatchResult::Matches(vec![(1, Box::new(Ab))])
455        );
456
457        Ok(())
458    }
459}