Refactor key dispatch to work in terms of bindings

Conrad Irwin created

Change summary

crates/gpui/src/key_dispatch.rs   | 38 +++++++++++---------
crates/gpui/src/keymap/binding.rs |  2 
crates/gpui/src/keymap/matcher.rs | 58 ++++++--------------------------
crates/gpui/src/window.rs         | 48 +++++++-------------------
4 files changed, 47 insertions(+), 99 deletions(-)

Detailed changes

crates/gpui/src/key_dispatch.rs 🔗

@@ -275,27 +275,31 @@ impl DispatchTree {
     pub fn dispatch_key(
         &mut self,
         keystroke: &Keystroke,
-        context: &[KeyContext],
-    ) -> Vec<Box<dyn Action>> {
-        if !self.keystroke_matchers.contains_key(context) {
-            let keystroke_contexts = context.iter().cloned().collect();
-            self.keystroke_matchers.insert(
-                keystroke_contexts,
-                KeystrokeMatcher::new(self.keymap.clone()),
-            );
-        }
+        dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
+    ) -> SmallVec<[KeyBinding; 1]> {
+        let mut actions = SmallVec::new();
+
+        let mut context_stack: SmallVec<[KeyContext; 4]> = SmallVec::new();
+        for node_id in dispatch_path {
+            let node = self.node(*node_id);
 
-        let keystroke_matcher = self.keystroke_matchers.get_mut(context).unwrap();
-        if let KeyMatch::Some(actions) = keystroke_matcher.match_keystroke(keystroke, context) {
-            // Clear all pending keystrokes when an action has been found.
-            for keystroke_matcher in self.keystroke_matchers.values_mut() {
-                keystroke_matcher.clear_pending();
+            if let Some(context) = node.context.clone() {
+                context_stack.push(context);
             }
+        }
 
-            actions
-        } else {
-            vec![]
+        while !context_stack.is_empty() {
+            let keystroke_matcher = self
+                .keystroke_matchers
+                .entry(context_stack.clone())
+                .or_insert_with(|| KeystrokeMatcher::new(self.keymap.clone()));
+
+            let mut matches = keystroke_matcher.match_keystroke(keystroke, &context_stack);
+            actions.append(&mut matches);
+            context_stack.pop();
         }
+
+        actions
     }
 
     pub fn has_pending_keystrokes(&self) -> bool {

crates/gpui/src/keymap/binding.rs 🔗

@@ -46,7 +46,7 @@ impl KeyBinding {
         if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
             // If the binding is completed, push it onto the matches list
             if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
-                KeyMatch::Some(vec![self.action.boxed_clone()])
+                KeyMatch::Matched
             } else {
                 KeyMatch::Pending
             }

crates/gpui/src/keymap/matcher.rs 🔗

@@ -1,5 +1,6 @@
-use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
+use crate::{Action, KeyBinding, KeyContext, Keymap, KeymapVersion, Keystroke};
 use parking_lot::Mutex;
+use smallvec::SmallVec;
 use std::sync::Arc;
 
 pub struct KeystrokeMatcher {
@@ -39,7 +40,7 @@ impl KeystrokeMatcher {
         &mut self,
         keystroke: &Keystroke,
         context_stack: &[KeyContext],
-    ) -> KeyMatch {
+    ) -> SmallVec<[KeyBinding; 1]> {
         let keymap = self.keymap.lock();
         // Clear pending keystrokes if the keymap has changed since the last matched keystroke.
         if keymap.version() != self.keymap_version {
@@ -48,7 +49,7 @@ impl KeystrokeMatcher {
         }
 
         let mut pending_key = None;
-        let mut found_actions = Vec::new();
+        let mut found = SmallVec::new();
 
         for binding in keymap.bindings().rev() {
             if !keymap.binding_enabled(binding, context_stack) {
@@ -58,8 +59,8 @@ impl KeystrokeMatcher {
             for candidate in keystroke.match_candidates() {
                 self.pending_keystrokes.push(candidate.clone());
                 match binding.match_keystrokes(&self.pending_keystrokes) {
-                    KeyMatch::Some(mut actions) => {
-                        found_actions.append(&mut actions);
+                    KeyMatch::Matched => {
+                        found.push(binding.clone());
                     }
                     KeyMatch::Pending => {
                         pending_key.get_or_insert(candidate);
@@ -70,16 +71,15 @@ impl KeystrokeMatcher {
             }
         }
 
-        if !found_actions.is_empty() {
+        if !found.is_empty() {
             self.pending_keystrokes.clear();
-            return KeyMatch::Some(found_actions);
         } else if let Some(pending_key) = pending_key {
             self.pending_keystrokes.push(pending_key);
-            KeyMatch::Pending
         } else {
             self.pending_keystrokes.clear();
-            KeyMatch::None
-        }
+        };
+
+        found
     }
 }
 
@@ -87,43 +87,7 @@ impl KeystrokeMatcher {
 pub enum KeyMatch {
     None,
     Pending,
-    Some(Vec<Box<dyn Action>>),
-}
-
-impl KeyMatch {
-    pub fn is_some(&self) -> bool {
-        matches!(self, KeyMatch::Some(_))
-    }
-
-    pub fn matches(self) -> Option<Vec<Box<dyn Action>>> {
-        match self {
-            KeyMatch::Some(matches) => Some(matches),
-            _ => None,
-        }
-    }
-}
-
-impl PartialEq for KeyMatch {
-    fn eq(&self, other: &Self) -> bool {
-        match (self, other) {
-            (KeyMatch::None, KeyMatch::None) => true,
-            (KeyMatch::Pending, KeyMatch::Pending) => true,
-            (KeyMatch::Some(a), KeyMatch::Some(b)) => {
-                if a.len() != b.len() {
-                    return false;
-                }
-
-                for (a, b) in a.iter().zip(b.iter()) {
-                    if !a.partial_eq(b.as_ref()) {
-                        return false;
-                    }
-                }
-
-                true
-            }
-            _ => false,
-        }
-    }
+    Matched,
 }
 
 #[cfg(test)]

crates/gpui/src/window.rs 🔗

@@ -1784,44 +1784,24 @@ impl<'a> WindowContext<'a> {
             .dispatch_tree
             .dispatch_path(node_id);
 
-        let mut actions: Vec<Box<dyn Action>> = Vec::new();
-
-        let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new();
-        for node_id in &dispatch_path {
-            let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
+        if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
+            let bindings = self
+                .window
+                .rendered_frame
+                .dispatch_tree
+                .dispatch_key(&key_down_event.keystroke, &dispatch_path);
 
-            if let Some(context) = node.context.clone() {
-                context_stack.push(context);
+            if !bindings.is_empty() {
+                self.clear_pending_keystrokes();
             }
-        }
 
-        for node_id in dispatch_path.iter().rev() {
-            // Match keystrokes
-            let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
-            if node.context.is_some() {
-                if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
-                    let mut new_actions = self
-                        .window
-                        .rendered_frame
-                        .dispatch_tree
-                        .dispatch_key(&key_down_event.keystroke, &context_stack);
-                    actions.append(&mut new_actions);
+            self.propagate_event = true;
+            for binding in bindings {
+                self.dispatch_action_on_node(node_id, binding.action.boxed_clone());
+                if !self.propagate_event {
+                    self.dispatch_keystroke_observers(event, Some(binding.action));
+                    return;
                 }
-
-                context_stack.pop();
-            }
-        }
-
-        if !actions.is_empty() {
-            self.clear_pending_keystrokes();
-        }
-
-        self.propagate_event = true;
-        for action in actions {
-            self.dispatch_action_on_node(node_id, action.boxed_clone());
-            if !self.propagate_event {
-                self.dispatch_keystroke_observers(event, Some(action));
-                return;
             }
         }