Clear the state of keystroke matchers when focus changes (#3335)

Antonio Scandurra created

Release Notes:

- N/A

Change summary

crates/gpui2/src/key_dispatch.rs | 35 +++++++++++++++++++++++++++------
crates/gpui2/src/view.rs         | 22 +++++++-------------
crates/gpui2/src/window.rs       | 18 ++++++++++++++++
3 files changed, 53 insertions(+), 22 deletions(-)

Detailed changes

crates/gpui2/src/key_dispatch.rs 🔗

@@ -60,7 +60,7 @@ impl DispatchTree {
         self.keystroke_matchers.clear();
     }
 
-    pub fn push_node(&mut self, context: KeyContext, old_dispatcher: &mut Self) {
+    pub fn push_node(&mut self, context: KeyContext) {
         let parent = self.node_stack.last().copied();
         let node_id = DispatchNodeId(self.nodes.len());
         self.nodes.push(DispatchNode {
@@ -71,12 +71,6 @@ impl DispatchTree {
         if !context.is_empty() {
             self.active_node().context = context.clone();
             self.context_stack.push(context);
-            if let Some((context_stack, matcher)) = old_dispatcher
-                .keystroke_matchers
-                .remove_entry(self.context_stack.as_slice())
-            {
-                self.keystroke_matchers.insert(context_stack, matcher);
-            }
         }
     }
 
@@ -87,6 +81,33 @@ impl DispatchTree {
         }
     }
 
+    pub fn clear_keystroke_matchers(&mut self) {
+        self.keystroke_matchers.clear();
+    }
+
+    /// Preserve keystroke matchers from previous frames to support multi-stroke
+    /// bindings across multiple frames.
+    pub fn preserve_keystroke_matchers(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) {
+        if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) {
+            let dispatch_path = self.dispatch_path(node_id);
+
+            self.context_stack.clear();
+            for node_id in dispatch_path {
+                let node = self.node(node_id);
+                if !node.context.is_empty() {
+                    self.context_stack.push(node.context.clone());
+                }
+
+                if let Some((context_stack, matcher)) = old_tree
+                    .keystroke_matchers
+                    .remove_entry(self.context_stack.as_slice())
+                {
+                    self.keystroke_matchers.insert(context_stack, matcher);
+                }
+            }
+        }
+    }
+
     pub fn on_key_event(&mut self, listener: KeyListener) {
         self.active_node().key_listeners.push(listener);
     }

crates/gpui2/src/view.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace,
-    BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId,
-    Model, Pixels, Size, ViewContext, VisualContext, WeakModel, WindowContext,
+    Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId, Model, Pixels,
+    Size, ViewContext, VisualContext, WeakModel, WindowContext,
 };
 use anyhow::{Context, Result};
 use std::{
@@ -325,12 +325,10 @@ where
         _: Option<Self::ElementState>,
         cx: &mut ViewContext<ParentViewState>,
     ) -> Self::ElementState {
-        cx.with_element_id(Some(self.view.entity_id()), |cx| {
-            self.view.update(cx, |view, cx| {
-                let mut element = self.component.take().unwrap().render();
-                element.initialize(view, cx);
-                element
-            })
+        self.view.update(cx, |view, cx| {
+            let mut element = self.component.take().unwrap().render();
+            element.initialize(view, cx);
+            element
         })
     }
 
@@ -340,9 +338,7 @@ where
         element: &mut Self::ElementState,
         cx: &mut ViewContext<ParentViewState>,
     ) -> LayoutId {
-        cx.with_element_id(Some(self.view.entity_id()), |cx| {
-            self.view.update(cx, |view, cx| element.layout(view, cx))
-        })
+        self.view.update(cx, |view, cx| element.layout(view, cx))
     }
 
     fn paint(
@@ -352,9 +348,7 @@ where
         element: &mut Self::ElementState,
         cx: &mut ViewContext<ParentViewState>,
     ) {
-        cx.with_element_id(Some(self.view.entity_id()), |cx| {
-            self.view.update(cx, |view, cx| element.paint(view, cx))
-        })
+        self.view.update(cx, |view, cx| element.paint(view, cx))
     }
 }
 

crates/gpui2/src/window.rs 🔗

@@ -393,6 +393,10 @@ impl<'a> WindowContext<'a> {
 
     /// Move focus to the element associated with the given `FocusHandle`.
     pub fn focus(&mut self, handle: &FocusHandle) {
+        if self.window.focus == Some(handle.id) {
+            return;
+        }
+
         let focus_id = handle.id;
 
         if self.window.last_blur.is_none() {
@@ -400,6 +404,10 @@ impl<'a> WindowContext<'a> {
         }
 
         self.window.focus = Some(focus_id);
+        self.window
+            .current_frame
+            .dispatch_tree
+            .clear_keystroke_matchers();
         self.app.push_effect(Effect::FocusChanged {
             window_handle: self.window.handle,
             focused: Some(focus_id),
@@ -1091,6 +1099,14 @@ impl<'a> WindowContext<'a> {
             });
         }
 
+        self.window
+            .current_frame
+            .dispatch_tree
+            .preserve_keystroke_matchers(
+                &mut self.window.previous_frame.dispatch_tree,
+                self.window.focus,
+            );
+
         self.window.root_view = Some(root_view);
         let scene = self.window.current_frame.scene_builder.build();
 
@@ -2093,7 +2109,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         window
             .current_frame
             .dispatch_tree
-            .push_node(context.clone(), &mut window.previous_frame.dispatch_tree);
+            .push_node(context.clone());
         if let Some(focus_handle) = focus_handle.as_ref() {
             window
                 .current_frame