Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/action.rs         | 56 +++++++++++++++++--------------
crates/gpui3/src/interactive.rs    | 15 ++++++++
crates/gpui3/src/keymap/binding.rs |  6 +-
crates/gpui3/src/keymap/keymap.rs  |  5 +-
crates/gpui3/src/window.rs         |  4 ++
5 files changed, 55 insertions(+), 31 deletions(-)

Detailed changes

crates/gpui3/src/action.rs 🔗

@@ -23,6 +23,10 @@ impl DispatchContext {
         }
     }
 
+    pub fn is_empty(&self) -> bool {
+        self.set.is_empty() && self.map.is_empty()
+    }
+
     pub fn clear(&mut self) {
         self.set.clear();
         self.map.clear();
@@ -47,17 +51,17 @@ impl DispatchContext {
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub enum ActionContextPredicate {
+pub enum DispatchContextPredicate {
     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>),
+    Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
+    Not(Box<DispatchContextPredicate>),
+    And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
+    Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
 }
 
-impl ActionContextPredicate {
+impl DispatchContextPredicate {
     pub fn parse(source: &str) -> Result<Self> {
         let source = Self::skip_whitespace(source);
         let (predicate, rest) = Self::parse_expr(source, 0)?;
@@ -92,8 +96,10 @@ impl ActionContextPredicate {
     }
 
     fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
-        type Op =
-            fn(ActionContextPredicate, ActionContextPredicate) -> Result<ActionContextPredicate>;
+        type Op = fn(
+            DispatchContextPredicate,
+            DispatchContextPredicate,
+        ) -> Result<DispatchContextPredicate>;
 
         let (mut predicate, rest) = Self::parse_primary(source)?;
         source = rest;
@@ -139,7 +145,7 @@ impl ActionContextPredicate {
             '!' => {
                 let source = Self::skip_whitespace(&source[1..]);
                 let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
-                Ok((ActionContextPredicate::Not(Box::new(predicate)), source))
+                Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
             }
             _ if next.is_alphanumeric() || next == '_' => {
                 let len = source
@@ -148,7 +154,7 @@ impl ActionContextPredicate {
                 let (identifier, rest) = source.split_at(len);
                 source = Self::skip_whitespace(rest);
                 Ok((
-                    ActionContextPredicate::Identifier(identifier.to_string().into()),
+                    DispatchContextPredicate::Identifier(identifier.to_string().into()),
                     source,
                 ))
             }
@@ -200,17 +206,17 @@ const PRECEDENCE_NOT: u32 = 5;
 
 #[cfg(test)]
 mod tests {
-    use super::ActionContextPredicate::{self, *};
+    use super::DispatchContextPredicate::{self, *};
 
     #[test]
     fn test_parse_identifiers() {
         // Identifiers
         assert_eq!(
-            ActionContextPredicate::parse("abc12").unwrap(),
+            DispatchContextPredicate::parse("abc12").unwrap(),
             Identifier("abc12".into())
         );
         assert_eq!(
-            ActionContextPredicate::parse("_1a").unwrap(),
+            DispatchContextPredicate::parse("_1a").unwrap(),
             Identifier("_1a".into())
         );
     }
@@ -218,11 +224,11 @@ mod tests {
     #[test]
     fn test_parse_negations() {
         assert_eq!(
-            ActionContextPredicate::parse("!abc").unwrap(),
+            DispatchContextPredicate::parse("!abc").unwrap(),
             Not(Box::new(Identifier("abc".into())))
         );
         assert_eq!(
-            ActionContextPredicate::parse(" ! ! abc").unwrap(),
+            DispatchContextPredicate::parse(" ! ! abc").unwrap(),
             Not(Box::new(Not(Box::new(Identifier("abc".into())))))
         );
     }
@@ -230,15 +236,15 @@ mod tests {
     #[test]
     fn test_parse_equality_operators() {
         assert_eq!(
-            ActionContextPredicate::parse("a == b").unwrap(),
+            DispatchContextPredicate::parse("a == b").unwrap(),
             Equal("a".into(), "b".into())
         );
         assert_eq!(
-            ActionContextPredicate::parse("c!=d").unwrap(),
+            DispatchContextPredicate::parse("c!=d").unwrap(),
             NotEqual("c".into(), "d".into())
         );
         assert_eq!(
-            ActionContextPredicate::parse("c == !d")
+            DispatchContextPredicate::parse("c == !d")
                 .unwrap_err()
                 .to_string(),
             "operands must be identifiers"
@@ -248,14 +254,14 @@ mod tests {
     #[test]
     fn test_parse_boolean_operators() {
         assert_eq!(
-            ActionContextPredicate::parse("a || b").unwrap(),
+            DispatchContextPredicate::parse("a || b").unwrap(),
             Or(
                 Box::new(Identifier("a".into())),
                 Box::new(Identifier("b".into()))
             )
         );
         assert_eq!(
-            ActionContextPredicate::parse("a || !b && c").unwrap(),
+            DispatchContextPredicate::parse("a || !b && c").unwrap(),
             Or(
                 Box::new(Identifier("a".into())),
                 Box::new(And(
@@ -265,7 +271,7 @@ mod tests {
             )
         );
         assert_eq!(
-            ActionContextPredicate::parse("a && b || c&&d").unwrap(),
+            DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
             Or(
                 Box::new(And(
                     Box::new(Identifier("a".into())),
@@ -278,7 +284,7 @@ mod tests {
             )
         );
         assert_eq!(
-            ActionContextPredicate::parse("a == b && c || d == e && f").unwrap(),
+            DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
             Or(
                 Box::new(And(
                     Box::new(Equal("a".into(), "b".into())),
@@ -291,7 +297,7 @@ mod tests {
             )
         );
         assert_eq!(
-            ActionContextPredicate::parse("a && b && c && d").unwrap(),
+            DispatchContextPredicate::parse("a && b && c && d").unwrap(),
             And(
                 Box::new(And(
                     Box::new(And(
@@ -308,7 +314,7 @@ mod tests {
     #[test]
     fn test_parse_parenthesized_expressions() {
         assert_eq!(
-            ActionContextPredicate::parse("a && (b == c || d != e)").unwrap(),
+            DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
             And(
                 Box::new(Identifier("a".into())),
                 Box::new(Or(
@@ -318,7 +324,7 @@ mod tests {
             ),
         );
         assert_eq!(
-            ActionContextPredicate::parse(" ( a || b ) ").unwrap(),
+            DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
             Or(
                 Box::new(Identifier("a".into())),
                 Box::new(Identifier("b".into())),

crates/gpui3/src/interactive.rs 🔗

@@ -176,6 +176,14 @@ pub trait StatelessInteractive: Element {
         self
     }
 
+    fn context(mut self, context: impl Into<DispatchContext>) -> Self
+    where
+        Self: Sized,
+    {
+        self.stateless_interactivity().dispatch_context = context.into();
+        self
+    }
+
     fn on_action<A: 'static>(
         mut self,
         listener: impl Fn(&mut Self::ViewState, &A, DispatchPhase, &mut ViewContext<Self::ViewState>)
@@ -320,7 +328,10 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
                 result
             })
         } else {
-            cx.with_key_listeners(&self.as_stateless().key_listeners, f)
+            let stateless = self.as_stateless();
+            cx.with_key_dispatch_context(stateless.dispatch_context.clone(), |cx| {
+                cx.with_key_listeners(&stateless.key_listeners, f)
+            })
         }
     }
 
@@ -519,6 +530,7 @@ where
 }
 
 pub struct StatelessInteraction<V> {
+    pub dispatch_context: DispatchContext,
     pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
     pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
     pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
@@ -583,6 +595,7 @@ pub struct InteractiveElementState {
 impl<V> Default for StatelessInteraction<V> {
     fn default() -> Self {
         Self {
+            dispatch_context: DispatchContext::new(),
             mouse_down_listeners: SmallVec::new(),
             mouse_up_listeners: SmallVec::new(),
             mouse_move_listeners: SmallVec::new(),

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

@@ -1,11 +1,11 @@
-use crate::{Action, ActionContextPredicate, DispatchContext, KeyMatch, Keystroke};
+use crate::{Action, DispatchContext, DispatchContextPredicate, KeyMatch, Keystroke};
 use anyhow::Result;
 use smallvec::SmallVec;
 
 pub struct KeyBinding {
     action: Box<dyn Action>,
     pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
-    pub(super) context_predicate: Option<ActionContextPredicate>,
+    pub(super) context_predicate: Option<DispatchContextPredicate>,
 }
 
 impl KeyBinding {
@@ -15,7 +15,7 @@ impl KeyBinding {
 
     pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
         let context = if let Some(context) = context {
-            Some(ActionContextPredicate::parse(context)?)
+            Some(DispatchContextPredicate::parse(context)?)
         } else {
             None
         };

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

@@ -1,4 +1,4 @@
-use crate::{ActionContextPredicate, KeyBinding, Keystroke};
+use crate::{DispatchContextPredicate, KeyBinding, Keystroke};
 use collections::HashSet;
 use smallvec::SmallVec;
 use std::{any::TypeId, collections::HashMap};
@@ -10,7 +10,8 @@ pub struct KeymapVersion(usize);
 pub struct Keymap {
     bindings: Vec<KeyBinding>,
     binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
-    disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<ActionContextPredicate>>>,
+    disabled_keystrokes:
+        HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<DispatchContextPredicate>>>,
     version: KeymapVersion,
 }
 

crates/gpui3/src/window.rs 🔗

@@ -1388,6 +1388,10 @@ impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
         context: DispatchContext,
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
+        if context.is_empty() {
+            return f(self);
+        }
+
         if !self.window.freeze_key_dispatch_stack {
             self.window
                 .key_dispatch_stack