Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/action.rs         | 89 ++++++++++++++++---------------
crates/gpui3/src/app.rs            | 18 +++++-
crates/gpui3/src/gpui3.rs          |  2 
crates/gpui3/src/keymap/binding.rs | 16 ++--
crates/gpui3/src/keymap/keymap.rs  | 16 ++--
crates/gpui3/src/keymap/matcher.rs | 44 ++++++--------
crates/gpui3/src/keymap/mod.rs     |  2 
7 files changed, 97 insertions(+), 90 deletions(-)

Detailed changes

crates/gpui3/src/keymap/keymap_context.rs → crates/gpui3/src/action.rs 🔗

@@ -1,16 +1,23 @@
+use crate::SharedString;
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet};
-use std::borrow::Cow;
+use std::any::Any;
+
+pub trait Action: Any + Send + Sync {
+    fn partial_eq(&self, action: &dyn Action) -> bool;
+    fn boxed_clone(&self) -> Box<dyn Action>;
+    fn as_any(&self) -> &dyn Any;
+}
 
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct KeymapContext {
-    set: HashSet<Cow<'static, str>>,
-    map: HashMap<Cow<'static, str>, Cow<'static, str>>,
+pub struct ActionContext {
+    set: HashSet<SharedString>,
+    map: HashMap<SharedString, SharedString>,
 }
 
-impl KeymapContext {
+impl ActionContext {
     pub fn new() -> Self {
-        KeymapContext {
+        ActionContext {
             set: HashSet::default(),
             map: HashMap::default(),
         }
@@ -30,31 +37,27 @@ impl KeymapContext {
         }
     }
 
-    pub fn add_identifier<I: Into<Cow<'static, str>>>(&mut self, identifier: I) {
+    pub fn add_identifier<I: Into<SharedString>>(&mut self, identifier: I) {
         self.set.insert(identifier.into());
     }
 
-    pub fn add_key<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>>(
-        &mut self,
-        key: S1,
-        value: S2,
-    ) {
+    pub fn add_key<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
         self.map.insert(key.into(), value.into());
     }
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub enum KeymapContextPredicate {
-    Identifier(String),
-    Equal(String, String),
-    NotEqual(String, String),
-    Child(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
-    Not(Box<KeymapContextPredicate>),
-    And(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
-    Or(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
+pub enum ActionContextPredicate {
+    Identifier(SharedString),
+    Equal(SharedString, SharedString),
+    NotEqual(SharedString, SharedString),
+    Child(Box<ActionContextPredicate>, Box<ActionContextPredicate>),
+    Not(Box<ActionContextPredicate>),
+    And(Box<ActionContextPredicate>, Box<ActionContextPredicate>),
+    Or(Box<ActionContextPredicate>, Box<ActionContextPredicate>),
 }
 
-impl KeymapContextPredicate {
+impl ActionContextPredicate {
     pub fn parse(source: &str) -> Result<Self> {
         let source = Self::skip_whitespace(source);
         let (predicate, rest) = Self::parse_expr(source, 0)?;
@@ -65,20 +68,20 @@ impl KeymapContextPredicate {
         }
     }
 
-    pub fn eval(&self, contexts: &[KeymapContext]) -> bool {
+    pub fn eval(&self, contexts: &[ActionContext]) -> bool {
         let Some(context) = contexts.first() else {
             return false;
         };
         match self {
-            Self::Identifier(name) => (&context.set).contains(name.as_str()),
+            Self::Identifier(name) => context.set.contains(&name),
             Self::Equal(left, right) => context
                 .map
-                .get(left.as_str())
+                .get(&left)
                 .map(|value| value == right)
                 .unwrap_or(false),
             Self::NotEqual(left, right) => context
                 .map
-                .get(left.as_str())
+                .get(&left)
                 .map(|value| value != right)
                 .unwrap_or(true),
             Self::Not(pred) => !pred.eval(contexts),
@@ -90,7 +93,7 @@ impl KeymapContextPredicate {
 
     fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
         type Op =
-            fn(KeymapContextPredicate, KeymapContextPredicate) -> Result<KeymapContextPredicate>;
+            fn(ActionContextPredicate, ActionContextPredicate) -> Result<ActionContextPredicate>;
 
         let (mut predicate, rest) = Self::parse_primary(source)?;
         source = rest;
@@ -136,7 +139,7 @@ impl KeymapContextPredicate {
             '!' => {
                 let source = Self::skip_whitespace(&source[1..]);
                 let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
-                Ok((KeymapContextPredicate::Not(Box::new(predicate)), source))
+                Ok((ActionContextPredicate::Not(Box::new(predicate)), source))
             }
             _ if next.is_alphanumeric() || next == '_' => {
                 let len = source
@@ -145,7 +148,7 @@ impl KeymapContextPredicate {
                 let (identifier, rest) = source.split_at(len);
                 source = Self::skip_whitespace(rest);
                 Ok((
-                    KeymapContextPredicate::Identifier(identifier.into()),
+                    ActionContextPredicate::Identifier(identifier.to_string().into()),
                     source,
                 ))
             }
@@ -197,17 +200,17 @@ const PRECEDENCE_NOT: u32 = 5;
 
 #[cfg(test)]
 mod tests {
-    use super::KeymapContextPredicate::{self, *};
+    use super::ActionContextPredicate::{self, *};
 
     #[test]
     fn test_parse_identifiers() {
         // Identifiers
         assert_eq!(
-            KeymapContextPredicate::parse("abc12").unwrap(),
+            ActionContextPredicate::parse("abc12").unwrap(),
             Identifier("abc12".into())
         );
         assert_eq!(
-            KeymapContextPredicate::parse("_1a").unwrap(),
+            ActionContextPredicate::parse("_1a").unwrap(),
             Identifier("_1a".into())
         );
     }
@@ -215,11 +218,11 @@ mod tests {
     #[test]
     fn test_parse_negations() {
         assert_eq!(
-            KeymapContextPredicate::parse("!abc").unwrap(),
+            ActionContextPredicate::parse("!abc").unwrap(),
             Not(Box::new(Identifier("abc".into())))
         );
         assert_eq!(
-            KeymapContextPredicate::parse(" ! ! abc").unwrap(),
+            ActionContextPredicate::parse(" ! ! abc").unwrap(),
             Not(Box::new(Not(Box::new(Identifier("abc".into())))))
         );
     }
@@ -227,15 +230,15 @@ mod tests {
     #[test]
     fn test_parse_equality_operators() {
         assert_eq!(
-            KeymapContextPredicate::parse("a == b").unwrap(),
+            ActionContextPredicate::parse("a == b").unwrap(),
             Equal("a".into(), "b".into())
         );
         assert_eq!(
-            KeymapContextPredicate::parse("c!=d").unwrap(),
+            ActionContextPredicate::parse("c!=d").unwrap(),
             NotEqual("c".into(), "d".into())
         );
         assert_eq!(
-            KeymapContextPredicate::parse("c == !d")
+            ActionContextPredicate::parse("c == !d")
                 .unwrap_err()
                 .to_string(),
             "operands must be identifiers"
@@ -245,14 +248,14 @@ mod tests {
     #[test]
     fn test_parse_boolean_operators() {
         assert_eq!(
-            KeymapContextPredicate::parse("a || b").unwrap(),
+            ActionContextPredicate::parse("a || b").unwrap(),
             Or(
                 Box::new(Identifier("a".into())),
                 Box::new(Identifier("b".into()))
             )
         );
         assert_eq!(
-            KeymapContextPredicate::parse("a || !b && c").unwrap(),
+            ActionContextPredicate::parse("a || !b && c").unwrap(),
             Or(
                 Box::new(Identifier("a".into())),
                 Box::new(And(
@@ -262,7 +265,7 @@ mod tests {
             )
         );
         assert_eq!(
-            KeymapContextPredicate::parse("a && b || c&&d").unwrap(),
+            ActionContextPredicate::parse("a && b || c&&d").unwrap(),
             Or(
                 Box::new(And(
                     Box::new(Identifier("a".into())),
@@ -275,7 +278,7 @@ mod tests {
             )
         );
         assert_eq!(
-            KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(),
+            ActionContextPredicate::parse("a == b && c || d == e && f").unwrap(),
             Or(
                 Box::new(And(
                     Box::new(Equal("a".into(), "b".into())),
@@ -288,7 +291,7 @@ mod tests {
             )
         );
         assert_eq!(
-            KeymapContextPredicate::parse("a && b && c && d").unwrap(),
+            ActionContextPredicate::parse("a && b && c && d").unwrap(),
             And(
                 Box::new(And(
                     Box::new(And(
@@ -305,7 +308,7 @@ mod tests {
     #[test]
     fn test_parse_parenthesized_expressions() {
         assert_eq!(
-            KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(),
+            ActionContextPredicate::parse("a && (b == c || d != e)").unwrap(),
             And(
                 Box::new(Identifier("a".into())),
                 Box::new(Or(
@@ -315,7 +318,7 @@ mod tests {
             ),
         );
         assert_eq!(
-            KeymapContextPredicate::parse(" ( a || b ) ").unwrap(),
+            ActionContextPredicate::parse(" ( a || b ) ").unwrap(),
             Or(
                 Box::new(Identifier("a".into())),
                 Box::new(Identifier("b".into())),

crates/gpui3/src/app.rs 🔗

@@ -10,14 +10,14 @@ use smallvec::SmallVec;
 
 use crate::{
     current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor,
-    FocusEvent, FocusHandle, FocusId, LayoutId, MainThread, MainThreadOnly, Platform,
-    SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
-    WindowContext, WindowHandle, WindowId,
+    FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly,
+    Platform, SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View,
+    Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
 use futures::Future;
-use parking_lot::Mutex;
+use parking_lot::{Mutex, RwLock};
 use slotmap::SlotMap;
 use std::{
     any::{type_name, Any, TypeId},
@@ -67,6 +67,7 @@ impl App {
                 unit_entity,
                 entities,
                 windows: SlotMap::with_key(),
+                keymap: Arc::new(RwLock::new(Keymap::default())),
                 pending_notifications: Default::default(),
                 pending_effects: Default::default(),
                 observers: SubscriberSet::new(),
@@ -111,6 +112,7 @@ pub struct AppContext {
     pub(crate) unit_entity: Handle<()>,
     pub(crate) entities: EntityMap,
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
+    keymap: Arc<RwLock<Keymap>>,
     pub(crate) pending_notifications: HashSet<EntityId>,
     pending_effects: VecDeque<Effect>,
     pub(crate) observers: SubscriberSet<EntityId, Handler>,
@@ -403,6 +405,14 @@ impl AppContext {
     pub(crate) fn pop_text_style(&mut self) {
         self.text_style_stack.pop();
     }
+
+    pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
+        self.keymap.write().add_bindings(bindings);
+        let window_ids = self.windows.keys().collect::<SmallVec<[_; 8]>>();
+        for window_id in window_ids {
+            self.update_window(window_id, |cx| cx.notify()).unwrap();
+        }
+    }
 }
 
 impl Context for AppContext {

crates/gpui3/src/gpui3.rs 🔗

@@ -1,3 +1,4 @@
+mod action;
 mod active;
 mod app;
 mod assets;
@@ -24,6 +25,7 @@ mod util;
 mod view;
 mod window;
 
+pub use action::*;
 pub use active::*;
 pub use anyhow::Result;
 pub use app::*;

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

@@ -1,21 +1,21 @@
-use crate::{Action, KeyMatch, KeymapContext, KeymapContextPredicate, Keystroke};
+use crate::{Action, ActionContext, ActionContextPredicate, KeyMatch, Keystroke};
 use anyhow::Result;
 use smallvec::SmallVec;
 
-pub struct Binding {
+pub struct KeyBinding {
     action: Box<dyn Action>,
     pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
-    pub(super) context_predicate: Option<KeymapContextPredicate>,
+    pub(super) context_predicate: Option<ActionContextPredicate>,
 }
 
-impl Binding {
+impl KeyBinding {
     pub fn new<A: Action>(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self {
         Self::load(keystrokes, Box::new(action), context_predicate).unwrap()
     }
 
     pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
         let context = if let Some(context) = context {
-            Some(KeymapContextPredicate::parse(context)?)
+            Some(ActionContextPredicate::parse(context)?)
         } else {
             None
         };
@@ -32,7 +32,7 @@ impl Binding {
         })
     }
 
-    pub fn matches_context(&self, contexts: &[KeymapContext]) -> bool {
+    pub fn matches_context(&self, contexts: &[ActionContext]) -> bool {
         self.context_predicate
             .as_ref()
             .map(|predicate| predicate.eval(contexts))
@@ -42,7 +42,7 @@ impl Binding {
     pub fn match_keystrokes(
         &self,
         pending_keystrokes: &[Keystroke],
-        contexts: &[KeymapContext],
+        contexts: &[ActionContext],
     ) -> KeyMatch {
         if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
             && self.matches_context(contexts)
@@ -61,7 +61,7 @@ impl Binding {
     pub fn keystrokes_for_action(
         &self,
         action: &dyn Action,
-        contexts: &[KeymapContext],
+        contexts: &[ActionContext],
     ) -> Option<SmallVec<[Keystroke; 2]>> {
         if self.action.partial_eq(action) && self.matches_context(contexts) {
             Some(self.keystrokes.clone())

crates/gpui3/src/keymap/keymap.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{Binding, KeymapContextPredicate, Keystroke};
+use crate::{ActionContextPredicate, KeyBinding, Keystroke};
 use collections::HashSet;
 use smallvec::SmallVec;
 use std::{any::TypeId, collections::HashMap};
@@ -8,14 +8,14 @@ pub struct KeymapVersion(usize);
 
 #[derive(Default)]
 pub struct Keymap {
-    bindings: Vec<Binding>,
+    bindings: Vec<KeyBinding>,
     binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
-    disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
+    disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<ActionContextPredicate>>>,
     version: KeymapVersion,
 }
 
 impl Keymap {
-    pub fn new(bindings: Vec<Binding>) -> Self {
+    pub fn new(bindings: Vec<KeyBinding>) -> Self {
         let mut this = Self::default();
         this.add_bindings(bindings);
         this
@@ -25,7 +25,7 @@ impl Keymap {
         self.version
     }
 
-    pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &'_ Binding> {
+    pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &'_ KeyBinding> {
         self.binding_indices_by_action_id
             .get(&action_id)
             .map(SmallVec::as_slice)
@@ -35,7 +35,7 @@ impl Keymap {
             .filter(|binding| !self.binding_disabled(binding))
     }
 
-    pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
+    pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
         // todo!("no action")
         // let no_action_id = (NoAction {}).id();
         let mut new_bindings = Vec::new();
@@ -87,14 +87,14 @@ impl Keymap {
         self.version.0 += 1;
     }
 
-    pub fn bindings(&self) -> Vec<&Binding> {
+    pub fn bindings(&self) -> Vec<&KeyBinding> {
         self.bindings
             .iter()
             .filter(|binding| !self.binding_disabled(binding))
             .collect()
     }
 
-    fn binding_disabled(&self, binding: &Binding) -> bool {
+    fn binding_disabled(&self, binding: &KeyBinding) -> bool {
         match self.disabled_keystrokes.get(&binding.keystrokes) {
             Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
             None => false,

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

@@ -1,13 +1,7 @@
-use crate::{Keymap, KeymapContext, KeymapVersion, Keystroke};
+use crate::{Action, ActionContext, Keymap, KeymapVersion, Keystroke};
 use parking_lot::RwLock;
 use smallvec::SmallVec;
-use std::{any::Any, sync::Arc};
-
-pub trait Action: Any + Send + Sync {
-    fn partial_eq(&self, action: &dyn Action) -> bool;
-    fn boxed_clone(&self) -> Box<dyn Action>;
-    fn as_any(&self) -> &dyn Any;
-}
+use std::sync::Arc;
 
 pub struct KeyMatcher {
     pending_keystrokes: Vec<Keystroke>,
@@ -50,7 +44,7 @@ impl KeyMatcher {
     pub fn match_keystroke(
         &mut self,
         keystroke: &Keystroke,
-        context_stack: &[KeymapContext],
+        context_stack: &[ActionContext],
     ) -> KeyMatch {
         let keymap = self.keymap.read();
         // Clear pending keystrokes if the keymap has changed since the last matched keystroke.
@@ -92,7 +86,7 @@ impl KeyMatcher {
     pub fn keystrokes_for_action(
         &self,
         action: &dyn Action,
-        contexts: &[KeymapContext],
+        contexts: &[ActionContext],
     ) -> Option<SmallVec<[Keystroke; 2]>> {
         self.keymap
             .read()
@@ -120,7 +114,7 @@ impl KeyMatch {
 //     use anyhow::Result;
 //     use serde::Deserialize;
 
-//     use crate::{actions, impl_actions, keymap_matcher::KeymapContext};
+//     use crate::{actions, impl_actions, keymap_matcher::ActionContext};
 
 //     use super::*;
 
@@ -128,10 +122,10 @@ impl KeyMatch {
 //     fn test_keymap_and_view_ordering() -> Result<()> {
 //         actions!(test, [EditorAction, ProjectPanelAction]);
 
-//         let mut editor = KeymapContext::default();
+//         let mut editor = ActionContext::default();
 //         editor.add_identifier("Editor");
 
-//         let mut project_panel = KeymapContext::default();
+//         let mut project_panel = ActionContext::default();
 //         project_panel.add_identifier("ProjectPanel");
 
 //         // Editor 'deeper' in than project panel
@@ -160,10 +154,10 @@ impl KeyMatch {
 //     fn test_push_keystroke() -> Result<()> {
 //         actions!(test, [B, AB, C, D, DA, E, EF]);
 
-//         let mut context1 = KeymapContext::default();
+//         let mut context1 = ActionContext::default();
 //         context1.add_identifier("1");
 
-//         let mut context2 = KeymapContext::default();
+//         let mut context2 = ActionContext::default();
 //         context2.add_identifier("2");
 
 //         let dispatch_path = vec![(2, context2), (1, context1)];
@@ -300,27 +294,27 @@ impl KeyMatch {
 //     fn test_context_predicate_eval() {
 //         let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
 
-//         let mut context = KeymapContext::default();
+//         let mut context = ActionContext::default();
 //         context.add_identifier("a");
 //         assert!(!predicate.eval(&[context]));
 
-//         let mut context = KeymapContext::default();
+//         let mut context = ActionContext::default();
 //         context.add_identifier("a");
 //         context.add_identifier("b");
 //         assert!(predicate.eval(&[context]));
 
-//         let mut context = KeymapContext::default();
+//         let mut context = ActionContext::default();
 //         context.add_identifier("a");
 //         context.add_key("c", "x");
 //         assert!(!predicate.eval(&[context]));
 
-//         let mut context = KeymapContext::default();
+//         let mut context = ActionContext::default();
 //         context.add_identifier("a");
 //         context.add_key("c", "d");
 //         assert!(predicate.eval(&[context]));
 
 //         let predicate = KeymapContextPredicate::parse("!a").unwrap();
-//         assert!(predicate.eval(&[KeymapContext::default()]));
+//         assert!(predicate.eval(&[ActionContext::default()]));
 //     }
 
 //     #[test]
@@ -355,8 +349,8 @@ impl KeyMatch {
 //         assert!(!predicate.eval(&contexts[5..]));
 //         assert!(!predicate.eval(&contexts[6..]));
 
-//         fn context_set(names: &[&str]) -> KeymapContext {
-//             let mut keymap = KeymapContext::new();
+//         fn context_set(names: &[&str]) -> ActionContext {
+//             let mut keymap = ActionContext::new();
 //             names
 //                 .iter()
 //                 .for_each(|name| keymap.add_identifier(name.to_string()));
@@ -386,10 +380,10 @@ impl KeyMatch {
 //             Binding::new("ctrl-`", Backtick, Some("a")),
 //         ]);
 
-//         let mut context_a = KeymapContext::default();
+//         let mut context_a = ActionContext::default();
 //         context_a.add_identifier("a");
 
-//         let mut context_b = KeymapContext::default();
+//         let mut context_b = ActionContext::default();
 //         context_b.add_identifier("b");
 
 //         let mut matcher = KeymapMatcher::new(keymap);
@@ -434,7 +428,7 @@ impl KeyMatch {
 //         );
 //         matcher.clear_pending();
 
-//         let mut context_c = KeymapContext::default();
+//         let mut context_c = ActionContext::default();
 //         context_c.add_identifier("c");
 
 //         // Pending keystrokes are maintained per-view

crates/gpui3/src/keymap/mod.rs 🔗

@@ -1,9 +1,7 @@
 mod binding;
 mod keymap;
-mod keymap_context;
 mod matcher;
 
 pub use binding::*;
 pub use keymap::*;
-pub use keymap_context::*;
 pub use matcher::*;