Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/action.rs         |   8 
crates/gpui3/src/elements/div.rs   |   4 
crates/gpui3/src/events.rs         |  12 +
crates/gpui3/src/interactive.rs    |   6 
crates/gpui3/src/keymap/binding.rs |   8 
crates/gpui3/src/keymap/matcher.rs |   6 
crates/gpui3/src/window.rs         | 197 +++++++++++++++++++++++--------
7 files changed, 169 insertions(+), 72 deletions(-)

Detailed changes

crates/gpui3/src/action.rs 🔗

@@ -10,14 +10,14 @@ pub trait Action: Any + Send + Sync {
 }
 
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct ActionContext {
+pub struct DispatchContext {
     set: HashSet<SharedString>,
     map: HashMap<SharedString, SharedString>,
 }
 
-impl ActionContext {
+impl DispatchContext {
     pub fn new() -> Self {
-        ActionContext {
+        DispatchContext {
             set: HashSet::default(),
             map: HashMap::default(),
         }
@@ -68,7 +68,7 @@ impl ActionContextPredicate {
         }
     }
 
-    pub fn eval(&self, contexts: &[ActionContext]) -> bool {
+    pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
         let Some(context) = contexts.first() else {
             return false;
         };

crates/gpui3/src/elements/div.rs 🔗

@@ -435,11 +435,11 @@ where
             if let Some(global_id) = global_id {
                 key_listeners.push((
                     TypeId::of::<KeyDownEvent>(),
-                    Arc::new(move |_, key_down, phase, cx| {
+                    Arc::new(move |_, key_down, context, phase, cx| {
                         if phase == DispatchPhase::Bubble {
                             let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
                             if let KeyMatch::Some(action) =
-                                cx.match_keystroke(&global_id, &key_down.keystroke)
+                                cx.match_keystroke(&global_id, &key_down.keystroke, context)
                             {
                                 return Some(action);
                             }

crates/gpui3/src/events.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    point, Action, Bounds, DispatchPhase, FocusHandle, Keystroke, Modifiers, Pixels, Point,
-    ViewContext,
+    point, Action, Bounds, DispatchContext, DispatchPhase, FocusHandle, Keystroke, Modifiers,
+    Pixels, Point, ViewContext,
 };
 use smallvec::SmallVec;
 use std::{
@@ -256,7 +256,13 @@ pub type ScrollWheelListener<V> = Arc<
 >;
 
 pub type KeyListener<V> = Arc<
-    dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) -> Option<Box<dyn Action>>
+    dyn Fn(
+            &mut V,
+            &dyn Any,
+            &[&DispatchContext],
+            DispatchPhase,
+            &mut ViewContext<V>,
+        ) -> Option<Box<dyn Action>>
         + Send
         + Sync
         + 'static,

crates/gpui3/src/interactive.rs 🔗

@@ -160,7 +160,7 @@ pub trait Interactive: Element {
     {
         self.listeners().key.push((
             TypeId::of::<KeyDownEvent>(),
-            Arc::new(move |view, event, phase, cx| {
+            Arc::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
                 listener(view, event, phase, cx);
                 None
@@ -181,7 +181,7 @@ pub trait Interactive: Element {
     {
         self.listeners().key.push((
             TypeId::of::<KeyUpEvent>(),
-            Arc::new(move |view, event, phase, cx| {
+            Arc::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
                 listener(view, event, phase, cx);
                 None
@@ -202,7 +202,7 @@ pub trait Interactive: Element {
     {
         self.listeners().key.push((
             TypeId::of::<A>(),
-            Arc::new(move |view, event, phase, cx| {
+            Arc::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
                 listener(view, event, phase, cx);
                 None

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

@@ -1,4 +1,4 @@
-use crate::{Action, ActionContext, ActionContextPredicate, KeyMatch, Keystroke};
+use crate::{Action, ActionContextPredicate, DispatchContext, KeyMatch, Keystroke};
 use anyhow::Result;
 use smallvec::SmallVec;
 
@@ -32,7 +32,7 @@ impl KeyBinding {
         })
     }
 
-    pub fn matches_context(&self, contexts: &[ActionContext]) -> bool {
+    pub fn matches_context(&self, contexts: &[&DispatchContext]) -> bool {
         self.context_predicate
             .as_ref()
             .map(|predicate| predicate.eval(contexts))
@@ -42,7 +42,7 @@ impl KeyBinding {
     pub fn match_keystrokes(
         &self,
         pending_keystrokes: &[Keystroke],
-        contexts: &[ActionContext],
+        contexts: &[&DispatchContext],
     ) -> KeyMatch {
         if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
             && self.matches_context(contexts)
@@ -61,7 +61,7 @@ impl KeyBinding {
     pub fn keystrokes_for_action(
         &self,
         action: &dyn Action,
-        contexts: &[ActionContext],
+        contexts: &[&DispatchContext],
     ) -> Option<SmallVec<[Keystroke; 2]>> {
         if self.action.eq(action) && self.matches_context(contexts) {
             Some(self.keystrokes.clone())

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

@@ -1,4 +1,4 @@
-use crate::{Action, ActionContext, Keymap, KeymapVersion, Keystroke};
+use crate::{Action, DispatchContext, Keymap, KeymapVersion, Keystroke};
 use parking_lot::RwLock;
 use smallvec::SmallVec;
 use std::sync::Arc;
@@ -44,7 +44,7 @@ impl KeyMatcher {
     pub fn match_keystroke(
         &mut self,
         keystroke: &Keystroke,
-        context_stack: &[ActionContext],
+        context_stack: &[&DispatchContext],
     ) -> KeyMatch {
         let keymap = self.keymap.read();
         // Clear pending keystrokes if the keymap has changed since the last matched keystroke.
@@ -86,7 +86,7 @@ impl KeyMatcher {
     pub fn keystrokes_for_action(
         &self,
         action: &dyn Action,
-        contexts: &[ActionContext],
+        contexts: &[&DispatchContext],
     ) -> Option<SmallVec<[Keystroke; 2]>> {
         self.keymap
             .read()

crates/gpui3/src/window.rs 🔗

@@ -1,13 +1,13 @@
 use crate::{
     px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
-    BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId, Edges, Effect,
-    Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla,
-    ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
-    MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels, Platform,
-    PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
-    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
-    Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle,
-    WindowOptions, SUBPIXEL_VARIANTS,
+    BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext,
+    DisplayId, Edges, Effect, Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId,
+    GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher,
+    Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path,
+    Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference,
+    RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow,
+    SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
+    WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use collections::HashMap;
@@ -47,7 +47,12 @@ pub enum DispatchPhase {
 
 type AnyListener = Arc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
 type AnyKeyListener = Arc<
-    dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) -> Option<Box<dyn Action>>
+    dyn Fn(
+            &dyn Any,
+            &[&DispatchContext],
+            DispatchPhase,
+            &mut WindowContext,
+        ) -> Option<Box<dyn Action>>
         + Send
         + Sync
         + 'static,
@@ -155,8 +160,8 @@ pub struct Window {
     z_index_stack: StackingOrder,
     content_mask_stack: Vec<ContentMask<Pixels>>,
     mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
-    key_listeners: Vec<(TypeId, AnyKeyListener)>,
-    key_events_enabled: bool,
+    key_dispatch_stack: Vec<KeyDispatchStackFrame>,
+    freeze_key_dispatch_stack: bool,
     focus_stack: Vec<FocusId>,
     focus_parents_by_child: HashMap<FocusId, FocusId>,
     pub(crate) focus_listeners: Vec<AnyFocusListener>,
@@ -230,24 +235,32 @@ impl Window {
             z_index_stack: StackingOrder(SmallVec::new()),
             content_mask_stack: Vec::new(),
             mouse_listeners: HashMap::default(),
-            key_listeners: Vec::new(),
-            key_events_enabled: true,
+            key_dispatch_stack: Vec::new(),
+            freeze_key_dispatch_stack: false,
             focus_stack: Vec::new(),
             focus_parents_by_child: HashMap::default(),
             focus_listeners: Vec::new(),
+            focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
             propagate: true,
             default_prevented: true,
             mouse_position,
             scale_factor,
             scene_builder: SceneBuilder::new(),
             dirty: true,
-            focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
             last_blur: None,
             focus: None,
         }
     }
 }
 
+enum KeyDispatchStackFrame {
+    Listener {
+        event_type: TypeId,
+        listener: AnyKeyListener,
+    },
+    Context(DispatchContext),
+}
+
 #[derive(Clone, Debug, Default, PartialEq, Eq)]
 #[repr(C)]
 pub struct ContentMask<P: Clone + Default + Debug> {
@@ -833,9 +846,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         // Clear focus state, because we determine what is focused when the new elements
         // in the upcoming frame are initialized.
         window.focus_listeners.clear();
-        window.key_listeners.clear();
+        window.key_dispatch_stack.clear();
         window.focus_parents_by_child.clear();
-        window.key_events_enabled = true;
+        window.freeze_key_dispatch_stack = false;
     }
 
     fn dispatch_event(&mut self, event: InputEvent) -> bool {
@@ -888,36 +901,67 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                     .insert(any_mouse_event.type_id(), handlers);
             }
         } else if let Some(any_key_event) = event.keyboard_event() {
-            let key_listeners = mem::take(&mut self.window.key_listeners);
+            let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack);
             let key_event_type = any_key_event.type_id();
-
-            for (ix, (listener_event_type, listener)) in key_listeners.iter().enumerate() {
-                if key_event_type == *listener_event_type {
-                    if let Some(action) = listener(any_key_event, DispatchPhase::Capture, self) {
-                        self.dispatch_action(action, &key_listeners[..ix]);
+            let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
+
+            for (ix, frame) in key_dispatch_stack.iter().enumerate() {
+                match frame {
+                    KeyDispatchStackFrame::Listener {
+                        event_type,
+                        listener,
+                    } => {
+                        if key_event_type == *event_type {
+                            if let Some(action) = listener(
+                                any_key_event,
+                                &context_stack,
+                                DispatchPhase::Capture,
+                                self,
+                            ) {
+                                self.dispatch_action(action, &key_dispatch_stack[..ix]);
+                            }
+                            if !self.window.propagate {
+                                break;
+                            }
+                        }
                     }
-                    if !self.window.propagate {
-                        break;
+                    KeyDispatchStackFrame::Context(context) => {
+                        context_stack.push(&context);
                     }
                 }
             }
 
             if self.window.propagate {
-                for (ix, (listener_event_type, listener)) in key_listeners.iter().enumerate().rev()
-                {
-                    if key_event_type == *listener_event_type {
-                        if let Some(action) = listener(any_key_event, DispatchPhase::Bubble, self) {
-                            self.dispatch_action(action, &key_listeners[..ix]);
-                        }
+                for (ix, frame) in key_dispatch_stack.iter().enumerate().rev() {
+                    match frame {
+                        KeyDispatchStackFrame::Listener {
+                            event_type,
+                            listener,
+                        } => {
+                            if key_event_type == *event_type {
+                                if let Some(action) = listener(
+                                    any_key_event,
+                                    &context_stack,
+                                    DispatchPhase::Bubble,
+                                    self,
+                                ) {
+                                    self.dispatch_action(action, &key_dispatch_stack[..ix]);
+                                }
 
-                        if !self.window.propagate {
-                            break;
+                                if !self.window.propagate {
+                                    break;
+                                }
+                            }
+                        }
+                        KeyDispatchStackFrame::Context(_) => {
+                            context_stack.pop();
                         }
                     }
                 }
             }
 
-            self.window.key_listeners = key_listeners;
+            drop(context_stack);
+            self.window.key_dispatch_stack = key_dispatch_stack;
         }
 
         true
@@ -927,13 +971,14 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         &mut self,
         element_id: &GlobalElementId,
         keystroke: &Keystroke,
+        context_stack: &[&DispatchContext],
     ) -> KeyMatch {
         let key_match = self
             .window
             .key_matchers
             .get_mut(element_id)
             .unwrap()
-            .match_keystroke(keystroke, &[]);
+            .match_keystroke(keystroke, context_stack);
 
         if key_match.is_some() {
             for matcher in self.window.key_matchers.values_mut() {
@@ -944,23 +989,39 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         key_match
     }
 
-    fn dispatch_action(&mut self, action: Box<dyn Action>, listeners: &[(TypeId, AnyKeyListener)]) {
+    fn dispatch_action(
+        &mut self,
+        action: Box<dyn Action>,
+        dispatch_stack: &[KeyDispatchStackFrame],
+    ) {
         let action_type = action.as_any().type_id();
-        for (event_type, listener) in listeners {
-            if action_type == *event_type {
-                listener(action.as_any(), DispatchPhase::Capture, self);
-                if !self.window.propagate {
-                    break;
+        for stack_frame in dispatch_stack {
+            if let KeyDispatchStackFrame::Listener {
+                event_type,
+                listener,
+            } = stack_frame
+            {
+                if action_type == *event_type {
+                    listener(action.as_any(), &[], DispatchPhase::Capture, self);
+                    if !self.window.propagate {
+                        break;
+                    }
                 }
             }
         }
 
         if self.window.propagate {
-            for (event_type, listener) in listeners.iter().rev() {
-                if action_type == *event_type {
-                    listener(action.as_any(), DispatchPhase::Bubble, self);
-                    if !self.window.propagate {
-                        break;
+            for stack_frame in dispatch_stack.iter().rev() {
+                if let KeyDispatchStackFrame::Listener {
+                    event_type,
+                    listener,
+                } = stack_frame
+                {
+                    if action_type == *event_type {
+                        listener(action.as_any(), &[], DispatchPhase::Bubble, self);
+                        if !self.window.propagate {
+                            break;
+                        }
                     }
                 }
             }
@@ -1287,26 +1348,56 @@ impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
         key_listeners: &[(TypeId, KeyListener<V>)],
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
-        if self.window.key_events_enabled {
+        if !self.window.freeze_key_dispatch_stack {
             for (event_type, listener) in key_listeners.iter().cloned() {
                 let handle = self.handle();
                 let listener = Arc::new(
-                    move |event: &dyn Any, phase: DispatchPhase, cx: &mut WindowContext<'_, '_>| {
+                    move |event: &dyn Any,
+                          context_stack: &[&DispatchContext],
+                          phase: DispatchPhase,
+                          cx: &mut WindowContext<'_, '_>| {
                         handle
-                            .update(cx, |view, cx| listener(view, event, phase, cx))
+                            .update(cx, |view, cx| {
+                                listener(view, event, context_stack, phase, cx)
+                            })
                             .log_err()
                             .flatten()
                     },
                 );
-                self.window.key_listeners.push((event_type, listener));
+                self.window
+                    .key_dispatch_stack
+                    .push(KeyDispatchStackFrame::Listener {
+                        event_type,
+                        listener,
+                    });
             }
         }
 
         let result = f(self);
 
-        if self.window.key_events_enabled {
-            let prev_len = self.window.key_listeners.len() - key_listeners.len();
-            self.window.key_listeners.truncate(prev_len);
+        if !self.window.freeze_key_dispatch_stack {
+            let prev_len = self.window.key_dispatch_stack.len() - key_listeners.len();
+            self.window.key_dispatch_stack.truncate(prev_len);
+        }
+
+        result
+    }
+
+    pub fn with_key_dispatch_context<R>(
+        &mut self,
+        context: DispatchContext,
+        f: impl FnOnce(&mut Self) -> R,
+    ) -> R {
+        if !self.window.freeze_key_dispatch_stack {
+            self.window
+                .key_dispatch_stack
+                .push(KeyDispatchStackFrame::Context(context));
+        }
+
+        let result = f(self);
+
+        if !self.window.freeze_key_dispatch_stack {
+            self.window.key_dispatch_stack.pop();
         }
 
         result
@@ -1325,7 +1416,7 @@ impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
         self.window.focus_stack.push(focus_handle.id);
 
         if Some(focus_handle.id) == self.window.focus {
-            self.window.key_events_enabled = false;
+            self.window.freeze_key_dispatch_stack = true;
         }
 
         let result = f(self);