Move more window-specific methods from `AppContext` to `WindowContext`

Antonio Scandurra created

Change summary

crates/collab/src/tests/integration_tests.rs  |   2 
crates/command_palette/src/command_palette.rs |   2 
crates/editor/src/editor.rs                   |   4 
crates/gpui/src/app.rs                        | 389 +++++---------------
crates/gpui/src/app/menu.rs                   |  18 
crates/gpui/src/app/window.rs                 | 259 ++++++++++++-
crates/gpui/src/elements/keystroke_label.rs   |   2 
crates/settings/src/settings_file.rs          |  30 +
crates/workspace/src/sidebar.rs               |  10 
crates/zed/src/zed.rs                         |   9 
10 files changed, 369 insertions(+), 356 deletions(-)

Detailed changes

crates/collab/src/tests/integration_tests.rs 🔗

@@ -1477,7 +1477,7 @@ async fn test_host_disconnect(
         .unwrap()
         .downcast::<Editor>()
         .unwrap();
-    assert!(cx_b.read(|cx| editor_b.is_focused(cx)));
+    assert!(cx_b.read_window(window_id_b, |cx| editor_b.is_focused(cx)).unwrap());
     editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
     assert!(cx_b.is_window_edited(workspace_b.window_id()));
 

crates/command_palette/src/command_palette.rs 🔗

@@ -43,7 +43,7 @@ impl CommandPalette {
     pub fn new(focused_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
         let this = cx.weak_handle();
         let actions = cx
-            .available_actions(cx.window_id(), focused_view_id)
+            .available_actions(focused_view_id)
             .filter_map(|(name, action, bindings)| {
                 if cx.has_global::<CommandPaletteFilter>() {
                     let filter = cx.global::<CommandPaletteFilter>();

crates/editor/src/editor.rs 🔗

@@ -42,7 +42,7 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     serde_json::{self, json},
     AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Drawable, Element, Entity,
-    ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HideHover, HoverState};
@@ -1317,7 +1317,7 @@ impl Editor {
         self.buffer().read(cx).title(cx)
     }
 
-    pub fn snapshot(&mut self, cx: &mut AppContext) -> EditorSnapshot {
+    pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
         EditorSnapshot {
             mode: self.mode,
             display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),

crates/gpui/src/app.rs 🔗

@@ -25,7 +25,6 @@ use std::{
 use anyhow::{anyhow, Context, Result};
 use parking_lot::Mutex;
 use postage::oneshot;
-use smallvec::SmallVec;
 use smol::prelude::*;
 use uuid::Uuid;
 
@@ -825,47 +824,6 @@ impl AppContext {
             .map(|view| view.as_any().type_id())
     }
 
-    /// Returns an iterator over all of the view ids from the passed view up to the root of the window
-    /// Includes the passed view itself
-    fn ancestors(&self, window_id: usize, mut view_id: usize) -> impl Iterator<Item = usize> + '_ {
-        std::iter::once(view_id)
-            .into_iter()
-            .chain(std::iter::from_fn(move || {
-                if let Some(ParentId::View(parent_id)) = self.parents.get(&(window_id, view_id)) {
-                    view_id = *parent_id;
-                    Some(view_id)
-                } else {
-                    None
-                }
-            }))
-    }
-
-    /// Returns the id of the parent of the given view, or none if the given
-    /// view is the root.
-    pub fn parent(&self, window_id: usize, view_id: usize) -> Option<usize> {
-        if let Some(ParentId::View(view_id)) = self.parents.get(&(window_id, view_id)) {
-            Some(*view_id)
-        } else {
-            None
-        }
-    }
-
-    fn focused_view_id(&self, window_id: usize) -> Option<usize> {
-        self.windows
-            .get(&window_id)
-            .and_then(|window| window.focused_view_id)
-    }
-
-    pub fn is_child_focused(&self, view: &AnyViewHandle) -> bool {
-        if let Some(focused_view_id) = self.focused_view_id(view.window_id) {
-            self.ancestors(view.window_id, focused_view_id)
-                .skip(1) // Skip self id
-                .any(|parent| parent == view.view_id)
-        } else {
-            false
-        }
-    }
-
     pub fn active_labeled_tasks<'a>(
         &'a self,
     ) -> impl DoubleEndedIterator<Item = &'static str> + 'a {
@@ -1231,141 +1189,29 @@ impl AppContext {
         self.action_deserializers.keys().copied()
     }
 
-    /// Return keystrokes that would dispatch the given action on the given view.
-    pub(crate) fn keystrokes_for_action(
-        &mut self,
-        window_id: usize,
-        view_id: usize,
-        action: &dyn Action,
-    ) -> Option<SmallVec<[Keystroke; 2]>> {
-        let mut contexts = Vec::new();
-        let mut handler_depth = None;
-        for (i, view_id) in self.ancestors(window_id, view_id).enumerate() {
-            if let Some(view) = self.views.get(&(window_id, view_id)) {
-                if let Some(actions) = self.actions.get(&view.as_any().type_id()) {
-                    if actions.contains_key(&action.as_any().type_id()) {
-                        handler_depth = Some(i);
-                    }
-                }
-                contexts.push(view.keymap_context(self));
-            }
-        }
-
-        if self.global_actions.contains_key(&action.as_any().type_id()) {
-            handler_depth = Some(contexts.len())
-        }
-
-        self.keystroke_matcher
-            .bindings_for_action_type(action.as_any().type_id())
-            .find_map(|b| {
-                handler_depth
-                    .map(|highest_handler| {
-                        if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
-                            Some(b.keystrokes().into())
-                        } else {
-                            None
-                        }
-                    })
-                    .flatten()
-            })
-    }
-
-    pub fn available_actions(
-        &self,
-        window_id: usize,
-        view_id: usize,
-    ) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> {
-        let mut contexts = Vec::new();
-        let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
-        for (depth, view_id) in self.ancestors(window_id, view_id).enumerate() {
-            if let Some(view) = self.views.get(&(window_id, view_id)) {
-                contexts.push(view.keymap_context(self));
-                let view_type = view.as_any().type_id();
-                if let Some(actions) = self.actions.get(&view_type) {
-                    handler_depths_by_action_type.extend(
-                        actions
-                            .keys()
-                            .copied()
-                            .map(|action_type| (action_type, depth)),
-                    );
-                }
-            }
-        }
-
-        handler_depths_by_action_type.extend(
-            self.global_actions
-                .keys()
-                .copied()
-                .map(|action_type| (action_type, contexts.len())),
-        );
-
-        self.action_deserializers
-            .iter()
-            .filter_map(move |(name, (type_id, deserialize))| {
-                if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
-                    Some((
-                        *name,
-                        deserialize("{}").ok()?,
-                        self.keystroke_matcher
-                            .bindings_for_action_type(*type_id)
-                            .filter(|b| {
-                                (0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
-                            })
-                            .collect(),
-                    ))
-                } else {
-                    None
-                }
-            })
-    }
-
     pub fn is_action_available(&self, action: &dyn Action) -> bool {
+        let mut available_in_window = false;
         let action_type = action.as_any().type_id();
         if let Some(window_id) = self.platform.main_window_id() {
-            if let Some(focused_view_id) = self.focused_view_id(window_id) {
-                for view_id in self.ancestors(window_id, focused_view_id) {
-                    if let Some(view) = self.views.get(&(window_id, view_id)) {
-                        let view_type = view.as_any().type_id();
-                        if let Some(actions) = self.actions.get(&view_type) {
-                            if actions.contains_key(&action_type) {
-                                return true;
+            available_in_window = self
+                .read_window(window_id, |cx| {
+                    if let Some(focused_view_id) = cx.focused_view_id() {
+                        for view_id in cx.ancestors(focused_view_id) {
+                            if let Some(view) = cx.views.get(&(window_id, view_id)) {
+                                let view_type = view.as_any().type_id();
+                                if let Some(actions) = cx.actions.get(&view_type) {
+                                    if actions.contains_key(&action_type) {
+                                        return true;
+                                    }
+                                }
                             }
                         }
                     }
-                }
-            }
-        }
-        self.global_actions.contains_key(&action_type)
-    }
-
-    // Traverses the parent tree. Walks down the tree toward the passed
-    // view calling visit with true. Then walks back up the tree calling visit with false.
-    // If `visit` returns false this function will immediately return.
-    // Returns a bool indicating if the traversal was completed early.
-    fn visit_dispatch_path(
-        &mut self,
-        window_id: usize,
-        view_id: usize,
-        mut visit: impl FnMut(usize, bool, &mut AppContext) -> bool,
-    ) -> bool {
-        // List of view ids from the leaf to the root of the window
-        let path = self.ancestors(window_id, view_id).collect::<Vec<_>>();
-
-        // Walk down from the root to the leaf calling visit with capture_phase = true
-        for view_id in path.iter().rev() {
-            if !visit(*view_id, true, self) {
-                return false;
-            }
-        }
-
-        // Walk up from the leaf to the root calling visit with capture_phase = false
-        for view_id in path.iter() {
-            if !visit(*view_id, false, self) {
-                return false;
-            }
+                    false
+                })
+                .unwrap_or(false);
         }
-
-        true
+        available_in_window || self.global_actions.contains_key(&action_type)
     }
 
     fn actions_mut(
@@ -2077,7 +1923,7 @@ impl AppContext {
             cx.window.is_active = active;
 
             if let Some(focused_id) = cx.window.focused_view_id {
-                for view_id in cx.ancestors(window_id, focused_id).collect::<Vec<_>>() {
+                for view_id in cx.ancestors(focused_id).collect::<Vec<_>>() {
                     cx.update_any_view(focused_id, |view, cx| {
                         if active {
                             view.focus_in(focused_id, cx, view_id);
@@ -2104,10 +1950,10 @@ impl AppContext {
             cx.window.focused_view_id = focused_id;
 
             let blurred_parents = blurred_id
-                .map(|blurred_id| cx.ancestors(window_id, blurred_id).collect::<Vec<_>>())
+                .map(|blurred_id| cx.ancestors(blurred_id).collect::<Vec<_>>())
                 .unwrap_or_default();
             let focused_parents = focused_id
-                .map(|focused_id| cx.ancestors(window_id, focused_id).collect::<Vec<_>>())
+                .map(|focused_id| cx.ancestors(focused_id).collect::<Vec<_>>())
                 .unwrap_or_default();
 
             if let Some(blurred_id) = blurred_id {
@@ -2141,48 +1987,10 @@ impl AppContext {
         window_id: usize,
         view_id: Option<usize>,
         action: &dyn Action,
-    ) -> bool {
-        self.update(|this| {
-            if let Some(view_id) = view_id {
-                this.halt_action_dispatch = false;
-                this.visit_dispatch_path(window_id, view_id, |view_id, capture_phase, this| {
-                    this.update_window(window_id, |cx| {
-                        cx.update_any_view(view_id, |view, cx| {
-                            let type_id = view.as_any().type_id();
-                            if let Some((name, mut handlers)) = cx
-                                .actions_mut(capture_phase)
-                                .get_mut(&type_id)
-                                .and_then(|h| h.remove_entry(&action.id()))
-                            {
-                                for handler in handlers.iter_mut().rev() {
-                                    cx.halt_action_dispatch = true;
-                                    handler(view, action, cx, view_id);
-                                    if cx.halt_action_dispatch {
-                                        break;
-                                    }
-                                }
-                                cx.actions_mut(capture_phase)
-                                    .get_mut(&type_id)
-                                    .unwrap()
-                                    .insert(name, handlers);
-                            }
-                        })
-                    });
-
-                    !this.halt_action_dispatch
-                });
-            }
-
-            if !this.halt_action_dispatch {
-                this.halt_action_dispatch = this.dispatch_global_action_any(action);
-            }
-
-            this.pending_effects
-                .push_back(Effect::ActionDispatchNotification {
-                    action_id: action.id(),
-                });
-            this.halt_action_dispatch
-        })
+    ) {
+        self.update_window(window_id, |cx| {
+            cx.handle_dispatch_action_from_effect(view_id, action)
+        });
     }
 
     fn handle_action_dispatch_notification_effect(&mut self, action_id: TypeId) {
@@ -3215,7 +3023,7 @@ impl<'a, 'b, 'c, V: View> ViewContext<'a, 'b, 'c, V> {
     }
 
     pub fn parent(&self) -> Option<usize> {
-        self.window_context.parent(self.window_id, self.view_id)
+        self.window_context.parent(self.view_id)
     }
 
     pub fn window_id(&self) -> usize {
@@ -3269,7 +3077,7 @@ impl<'a, 'b, 'c, V: View> ViewContext<'a, 'b, 'c, V> {
     }
 
     pub fn is_parent_view_focused(&self) -> bool {
-        if let Some(parent_view_id) = self.ancestors(self.window_id, self.view_id).next().clone() {
+        if let Some(parent_view_id) = self.ancestors(self.view_id).next().clone() {
             self.focused_view_id() == Some(parent_view_id)
         } else {
             false
@@ -3277,7 +3085,7 @@ impl<'a, 'b, 'c, V: View> ViewContext<'a, 'b, 'c, V> {
     }
 
     pub fn focus_parent_view(&mut self) {
-        let next = self.ancestors(self.window_id, self.view_id).next().clone();
+        let next = self.ancestors(self.view_id).next().clone();
         if let Some(parent_view_id) = next {
             let window_id = self.window_id;
             self.window_context.focus(window_id, Some(parent_view_id));
@@ -3289,7 +3097,7 @@ impl<'a, 'b, 'c, V: View> ViewContext<'a, 'b, 'c, V> {
         if self.window_id != view.window_id {
             return false;
         }
-        self.ancestors(view.window_id, view.view_id)
+        self.ancestors(view.view_id)
             .skip(1) // Skip self id
             .any(|parent| parent == self.view_id)
     }
@@ -4193,9 +4001,12 @@ impl<T: View> ViewHandle<T> {
         });
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn is_focused(&self, cx: &AppContext) -> bool {
-        cx.focused_view_id(self.window_id)
-            .map_or(false, |focused_id| focused_id == self.view_id)
+        cx.read_window(self.window_id, |cx| {
+            cx.focused_view_id() == Some(self.view_id)
+        })
+        .unwrap_or(false)
     }
 }
 
@@ -4312,11 +4123,6 @@ impl AnyViewHandle {
         TypeId::of::<T>() == self.view_type
     }
 
-    pub fn is_focused(&self, cx: &AppContext) -> bool {
-        cx.focused_view_id(self.window_id)
-            .map_or(false, |focused_id| focused_id == self.view_id)
-    }
-
     pub fn downcast<T: View>(self) -> Option<ViewHandle<T>> {
         if self.is::<T>() {
             Some(ViewHandle {
@@ -5713,7 +5519,7 @@ mod tests {
         }
 
         let view_events: Arc<Mutex<Vec<String>>> = Default::default();
-        let (_, view_1) = cx.add_window(Default::default(), |_| View {
+        let (window_id, view_1) = cx.add_window(Default::default(), |_| View {
             events: view_events.clone(),
             name: "view 1".to_string(),
         });
@@ -5763,8 +5569,10 @@ mod tests {
             cx.focus(&view_2);
         });
 
-        assert!(cx.is_child_focused(&view_1));
-        assert!(!cx.is_child_focused(&view_2));
+        cx.read_window(window_id, |cx| {
+            assert!(cx.is_child_focused(&view_1));
+            assert!(!cx.is_child_focused(&view_2));
+        });
         assert_eq!(
             mem::take(&mut *view_events.lock()),
             [
@@ -5789,8 +5597,10 @@ mod tests {
         );
 
         view_1.update(cx, |_, cx| cx.focus(&view_1));
-        assert!(!cx.is_child_focused(&view_1));
-        assert!(!cx.is_child_focused(&view_2));
+        cx.read_window(window_id, |cx| {
+            assert!(!cx.is_child_focused(&view_1));
+            assert!(!cx.is_child_focused(&view_2));
+        });
         assert_eq!(
             mem::take(&mut *view_events.lock()),
             ["view 2 blurred", "view 1 focused"],
@@ -5967,11 +5777,9 @@ mod tests {
         let view_3 = cx.add_view(&view_2, |_| ViewA { id: 3 });
         let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 });
 
-        cx.handle_dispatch_action_from_effect(
-            window_id,
-            Some(view_4.id()),
-            &Action("bar".to_string()),
-        );
+        cx.update_window(window_id, |cx| {
+            cx.handle_dispatch_action_from_effect(Some(view_4.id()), &Action("bar".to_string()))
+        });
 
         assert_eq!(
             *actions.borrow(),
@@ -5996,11 +5804,9 @@ mod tests {
         let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 });
 
         actions.borrow_mut().clear();
-        cx.handle_dispatch_action_from_effect(
-            window_id,
-            Some(view_4.id()),
-            &Action("bar".to_string()),
-        );
+        cx.update_window(window_id, |cx| {
+            cx.handle_dispatch_action_from_effect(Some(view_4.id()), &Action("bar".to_string()))
+        });
 
         assert_eq!(
             *actions.borrow(),
@@ -6197,48 +6003,62 @@ mod tests {
             Binding::new("c", GlobalAction, Some("View3")), // View 3 does not exist
         ]);
 
-        // Sanity check
-        assert_eq!(
-            cx.keystrokes_for_action(window_id, view_1.id(), &Action1)
-                .unwrap()
-                .as_slice(),
-            &[Keystroke::parse("a").unwrap()]
-        );
-        assert_eq!(
-            cx.keystrokes_for_action(window_id, view_2.id(), &Action2)
-                .unwrap()
-                .as_slice(),
-            &[Keystroke::parse("b").unwrap()]
-        );
+        cx.update_window(window_id, |cx| {
+            // Sanity check
+            assert_eq!(
+                cx.keystrokes_for_action(view_1.id(), &Action1)
+                    .unwrap()
+                    .as_slice(),
+                &[Keystroke::parse("a").unwrap()]
+            );
+            assert_eq!(
+                cx.keystrokes_for_action(view_2.id(), &Action2)
+                    .unwrap()
+                    .as_slice(),
+                &[Keystroke::parse("b").unwrap()]
+            );
 
-        // The 'a' keystroke propagates up the view tree from view_2
-        // to view_1. The action, Action1, is handled by view_1.
-        assert_eq!(
-            cx.keystrokes_for_action(window_id, view_2.id(), &Action1)
-                .unwrap()
-                .as_slice(),
-            &[Keystroke::parse("a").unwrap()]
-        );
+            // The 'a' keystroke propagates up the view tree from view_2
+            // to view_1. The action, Action1, is handled by view_1.
+            assert_eq!(
+                cx.keystrokes_for_action(view_2.id(), &Action1)
+                    .unwrap()
+                    .as_slice(),
+                &[Keystroke::parse("a").unwrap()]
+            );
 
-        // Actions that are handled below the current view don't have bindings
-        assert_eq!(
-            cx.keystrokes_for_action(window_id, view_1.id(), &Action2),
-            None
-        );
+            // Actions that are handled below the current view don't have bindings
+            assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action2), None);
 
-        // Actions that are handled in other branches of the tree should not have a binding
-        assert_eq!(
-            cx.keystrokes_for_action(window_id, view_2.id(), &GlobalAction),
-            None
-        );
+            // Actions that are handled in other branches of the tree should not have a binding
+            assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None);
+
+            // Check that global actions do not have a binding, even if a binding does exist in another view
+            assert_eq!(
+                &available_actions(view_1.id(), cx),
+                &[
+                    ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
+                    ("test::GlobalAction", vec![])
+                ],
+            );
+
+            // Check that view 1 actions and bindings are available even when called from view 2
+            assert_eq!(
+                &available_actions(view_2.id(), cx),
+                &[
+                    ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
+                    ("test::Action2", vec![Keystroke::parse("b").unwrap()]),
+                    ("test::GlobalAction", vec![]),
+                ],
+            );
+        });
 
         // Produces a list of actions and key bindings
         fn available_actions(
-            window_id: usize,
             view_id: usize,
-            cx: &mut AppContext,
+            cx: &WindowContext,
         ) -> Vec<(&'static str, Vec<Keystroke>)> {
-            cx.available_actions(window_id, view_id)
+            cx.available_actions(view_id)
                 .map(|(action_name, _, bindings)| {
                     (
                         action_name,
@@ -6251,25 +6071,6 @@ mod tests {
                 .sorted_by(|(name1, _), (name2, _)| name1.cmp(name2))
                 .collect()
         }
-
-        // Check that global actions do not have a binding, even if a binding does exist in another view
-        assert_eq!(
-            &available_actions(window_id, view_1.id(), cx),
-            &[
-                ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
-                ("test::GlobalAction", vec![])
-            ],
-        );
-
-        // Check that view 1 actions and bindings are available even when called from view 2
-        assert_eq!(
-            &available_actions(window_id, view_2.id(), cx),
-            &[
-                ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
-                ("test::Action2", vec![Keystroke::parse("b").unwrap()]),
-                ("test::GlobalAction", vec![]),
-            ],
-        );
     }
 
     #[crate::test(self)]

crates/gpui/src/app/menu.rs 🔗

@@ -78,12 +78,18 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
         move |action| {
             let mut cx = cx.borrow_mut();
             if let Some(main_window_id) = cx.platform.main_window_id() {
-                if let Some(view_id) = cx
-                    .windows
-                    .get(&main_window_id)
-                    .and_then(|w| w.focused_view_id)
-                {
-                    cx.handle_dispatch_action_from_effect(main_window_id, Some(view_id), action);
+                let dispatched = cx
+                    .update_window(main_window_id, |cx| {
+                        if let Some(view_id) = cx.focused_view_id() {
+                            cx.handle_dispatch_action_from_effect(Some(view_id), action);
+                            true
+                        } else {
+                            false
+                        }
+                    })
+                    .unwrap_or(false);
+
+                if dispatched {
                     return;
                 }
             }

crates/gpui/src/app/window.rs 🔗

@@ -2,7 +2,7 @@ use crate::{
     elements::AnyRootElement,
     geometry::rect::RectF,
     json::{self, ToJson},
-    keymap_matcher::{Keystroke, MatchResult},
+    keymap_matcher::{Binding, Keystroke, MatchResult},
     platform::{
         self, Appearance, CursorStyle, Event, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent,
         MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
@@ -13,10 +13,10 @@ use crate::{
     },
     text_layout::TextLayoutCache,
     util::post_inc,
-    AnyView, AnyViewHandle, AnyWeakViewHandle, AppContext, Drawable, Entity, ModelContext,
-    ModelHandle, MouseRegion, MouseRegionId, ParentId, ReadView, SceneBuilder, UpdateModel,
-    UpdateView, UpgradeViewHandle, View, ViewContext, ViewHandle, WeakViewHandle,
-    WindowInvalidation,
+    Action, AnyView, AnyViewHandle, AnyWeakViewHandle, AppContext, Drawable, Effect, Entity,
+    ModelContext, ModelHandle, MouseRegion, MouseRegionId, ParentId, ReadModel, ReadView,
+    SceneBuilder, UpdateModel, UpdateView, UpgradeViewHandle, View, ViewContext, ViewHandle,
+    WeakViewHandle, WindowInvalidation,
 };
 use anyhow::{anyhow, bail, Result};
 use collections::{HashMap, HashSet};
@@ -28,7 +28,10 @@ use sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
-use std::ops::{Deref, DerefMut, Range};
+use std::{
+    any::TypeId,
+    ops::{Deref, DerefMut, Range},
+};
 use util::ResultExt;
 use uuid::Uuid;
 
@@ -128,6 +131,12 @@ impl DerefMut for WindowContext<'_, '_> {
     }
 }
 
+impl ReadModel for WindowContext<'_, '_> {
+    fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+        self.app_context.read_model(handle)
+    }
+}
+
 impl UpdateModel for WindowContext<'_, '_> {
     fn update_model<T: Entity, R>(
         &mut self,
@@ -230,11 +239,99 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> {
         Some(result)
     }
 
+    /// Return keystrokes that would dispatch the given action on the given view.
+    pub(crate) fn keystrokes_for_action(
+        &mut self,
+        view_id: usize,
+        action: &dyn Action,
+    ) -> Option<SmallVec<[Keystroke; 2]>> {
+        let window_id = self.window_id;
+        let mut contexts = Vec::new();
+        let mut handler_depth = None;
+        for (i, view_id) in self.ancestors(view_id).enumerate() {
+            if let Some(view) = self.views.get(&(window_id, view_id)) {
+                if let Some(actions) = self.actions.get(&view.as_any().type_id()) {
+                    if actions.contains_key(&action.as_any().type_id()) {
+                        handler_depth = Some(i);
+                    }
+                }
+                contexts.push(view.keymap_context(self));
+            }
+        }
+
+        if self.global_actions.contains_key(&action.as_any().type_id()) {
+            handler_depth = Some(contexts.len())
+        }
+
+        self.keystroke_matcher
+            .bindings_for_action_type(action.as_any().type_id())
+            .find_map(|b| {
+                handler_depth
+                    .map(|highest_handler| {
+                        if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
+                            Some(b.keystrokes().into())
+                        } else {
+                            None
+                        }
+                    })
+                    .flatten()
+            })
+    }
+
+    pub fn available_actions(
+        &self,
+        view_id: usize,
+    ) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> {
+        let window_id = self.window_id;
+        let mut contexts = Vec::new();
+        let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
+        for (depth, view_id) in self.ancestors(view_id).enumerate() {
+            if let Some(view) = self.views.get(&(window_id, view_id)) {
+                contexts.push(view.keymap_context(self));
+                let view_type = view.as_any().type_id();
+                if let Some(actions) = self.actions.get(&view_type) {
+                    handler_depths_by_action_type.extend(
+                        actions
+                            .keys()
+                            .copied()
+                            .map(|action_type| (action_type, depth)),
+                    );
+                }
+            }
+        }
+
+        handler_depths_by_action_type.extend(
+            self.global_actions
+                .keys()
+                .copied()
+                .map(|action_type| (action_type, contexts.len())),
+        );
+
+        self.action_deserializers
+            .iter()
+            .filter_map(move |(name, (type_id, deserialize))| {
+                if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
+                    Some((
+                        *name,
+                        deserialize("{}").ok()?,
+                        self.keystroke_matcher
+                            .bindings_for_action_type(*type_id)
+                            .filter(|b| {
+                                (0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
+                            })
+                            .collect(),
+                    ))
+                } else {
+                    None
+                }
+            })
+    }
+
     pub fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
         let window_id = self.window_id;
         if let Some(focused_view_id) = self.focused_view_id() {
             let dispatch_path = self
-                .ancestors(window_id, focused_view_id)
+                .ancestors(focused_view_id)
                 .filter_map(|view_id| {
                     self.views
                         .get(&(window_id, view_id))
@@ -252,11 +349,8 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> {
                 MatchResult::Pending => true,
                 MatchResult::Matches(matches) => {
                     for (view_id, action) in matches {
-                        if self.handle_dispatch_action_from_effect(
-                            window_id,
-                            Some(*view_id),
-                            action.as_ref(),
-                        ) {
+                        if self.handle_dispatch_action_from_effect(Some(*view_id), action.as_ref())
+                        {
                             self.keystroke_matcher.clear_pending();
                             handled_by = Some(action.boxed_clone());
                             break;
@@ -289,11 +383,11 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> {
         //  -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
         //  -> Also updates mouse-related state
         match &event {
-            Event::KeyDown(e) => return self.dispatch_key_down(window_id, e),
+            Event::KeyDown(e) => return self.dispatch_key_down(e),
 
-            Event::KeyUp(e) => return self.dispatch_key_up(window_id, e),
+            Event::KeyUp(e) => return self.dispatch_key_up(e),
 
-            Event::ModifiersChanged(e) => return self.dispatch_modifiers_changed(window_id, e),
+            Event::ModifiersChanged(e) => return self.dispatch_modifiers_changed(e),
 
             Event::MouseDown(e) => {
                 // Click events are weird because they can be fired after a drag event.
@@ -615,12 +709,10 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> {
         any_event_handled
     }
 
-    pub fn dispatch_key_down(&mut self, window_id: usize, event: &KeyDownEvent) -> bool {
+    pub fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
+        let window_id = self.window_id;
         if let Some(focused_view_id) = self.window.focused_view_id {
-            for view_id in self
-                .ancestors(window_id, focused_view_id)
-                .collect::<Vec<_>>()
-            {
+            for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
                 if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
                     let handled = view.key_down(event, self, view_id);
                     self.views.insert((window_id, view_id), view);
@@ -636,12 +728,10 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> {
         false
     }
 
-    pub fn dispatch_key_up(&mut self, window_id: usize, event: &KeyUpEvent) -> bool {
+    pub fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
+        let window_id = self.window_id;
         if let Some(focused_view_id) = self.window.focused_view_id {
-            for view_id in self
-                .ancestors(window_id, focused_view_id)
-                .collect::<Vec<_>>()
-            {
+            for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
                 if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
                     let handled = view.key_up(event, self, view_id);
                     self.views.insert((window_id, view_id), view);
@@ -657,16 +747,10 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> {
         false
     }
 
-    pub fn dispatch_modifiers_changed(
-        &mut self,
-        window_id: usize,
-        event: &ModifiersChangedEvent,
-    ) -> bool {
+    pub fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
+        let window_id = self.window_id;
         if let Some(focused_view_id) = self.window.focused_view_id {
-            for view_id in self
-                .ancestors(window_id, focused_view_id)
-                .collect::<Vec<_>>()
-            {
+            for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
                 if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
                     let handled = view.modifiers_changed(event, self, view_id);
                     self.views.insert((window_id, view_id), view);
@@ -803,10 +887,117 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> {
         self.window.is_fullscreen
     }
 
+    pub(crate) fn handle_dispatch_action_from_effect(
+        &mut self,
+        view_id: Option<usize>,
+        action: &dyn Action,
+    ) -> bool {
+        if let Some(view_id) = view_id {
+            self.halt_action_dispatch = false;
+            self.visit_dispatch_path(view_id, |view_id, capture_phase, cx| {
+                cx.update_any_view(view_id, |view, cx| {
+                    let type_id = view.as_any().type_id();
+                    if let Some((name, mut handlers)) = cx
+                        .actions_mut(capture_phase)
+                        .get_mut(&type_id)
+                        .and_then(|h| h.remove_entry(&action.id()))
+                    {
+                        for handler in handlers.iter_mut().rev() {
+                            cx.halt_action_dispatch = true;
+                            handler(view, action, cx, view_id);
+                            if cx.halt_action_dispatch {
+                                break;
+                            }
+                        }
+                        cx.actions_mut(capture_phase)
+                            .get_mut(&type_id)
+                            .unwrap()
+                            .insert(name, handlers);
+                    }
+                });
+
+                !cx.halt_action_dispatch
+            });
+        }
+
+        if !self.halt_action_dispatch {
+            self.halt_action_dispatch = self.dispatch_global_action_any(action);
+        }
+
+        self.pending_effects
+            .push_back(Effect::ActionDispatchNotification {
+                action_id: action.id(),
+            });
+        self.halt_action_dispatch
+    }
+
+    /// Returns an iterator over all of the view ids from the passed view up to the root of the window
+    /// Includes the passed view itself
+    pub(crate) fn ancestors(&self, mut view_id: usize) -> impl Iterator<Item = usize> + '_ {
+        std::iter::once(view_id)
+            .into_iter()
+            .chain(std::iter::from_fn(move || {
+                if let Some(ParentId::View(parent_id)) =
+                    self.parents.get(&(self.window_id, view_id))
+                {
+                    view_id = *parent_id;
+                    Some(view_id)
+                } else {
+                    None
+                }
+            }))
+    }
+
+    /// Returns the id of the parent of the given view, or none if the given
+    /// view is the root.
+    pub(crate) fn parent(&self, view_id: usize) -> Option<usize> {
+        if let Some(ParentId::View(view_id)) = self.parents.get(&(self.window_id, view_id)) {
+            Some(*view_id)
+        } else {
+            None
+        }
+    }
+
+    // Traverses the parent tree. Walks down the tree toward the passed
+    // view calling visit with true. Then walks back up the tree calling visit with false.
+    // If `visit` returns false this function will immediately return.
+    fn visit_dispatch_path(
+        &mut self,
+        view_id: usize,
+        mut visit: impl FnMut(usize, bool, &mut WindowContext) -> bool,
+    ) {
+        // List of view ids from the leaf to the root of the window
+        let path = self.ancestors(view_id).collect::<Vec<_>>();
+
+        // Walk down from the root to the leaf calling visit with capture_phase = true
+        for view_id in path.iter().rev() {
+            if !visit(*view_id, true, self) {
+                return;
+            }
+        }
+
+        // Walk up from the leaf to the root calling visit with capture_phase = false
+        for view_id in path.iter() {
+            if !visit(*view_id, false, self) {
+                return;
+            }
+        }
+    }
+
     pub fn focused_view_id(&self) -> Option<usize> {
         self.window.focused_view_id
     }
 
+    pub fn is_child_focused(&self, view: &AnyViewHandle) -> bool {
+        if let Some(focused_view_id) = self.focused_view_id() {
+            self.ancestors(focused_view_id)
+                .skip(1) // Skip self id
+                .any(|parent| parent == view.view_id)
+        } else {
+            false
+        }
+    }
+
     pub fn window_bounds(&self) -> WindowBounds {
         self.window.platform_window.bounds()
     }

crates/gpui/src/elements/keystroke_label.rs 🔗

@@ -45,7 +45,7 @@ impl<V: View> Drawable<V> for KeystrokeLabel {
         cx: &mut ViewContext<V>,
     ) -> (Vector2F, Element<V>) {
         let mut element = if let Some(keystrokes) =
-            cx.keystrokes_for_action(self.window_id, self.view_id, self.action.as_ref())
+            cx.keystrokes_for_action(self.view_id, self.action.as_ref())
         {
             Flex::row()
                 .with_children(keystrokes.iter().map(|keystroke| {

crates/settings/src/settings_file.rs 🔗

@@ -80,9 +80,25 @@ mod tests {
         watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap,
     };
     use fs::FakeFs;
-    use gpui::{actions, Action};
+    use gpui::{actions, elements::*, Action, Entity, View, ViewContext, WindowContext};
     use theme::ThemeRegistry;
 
+    struct TestView;
+
+    impl Entity for TestView {
+        type Event = ();
+    }
+
+    impl View for TestView {
+        fn ui_name() -> &'static str {
+            "TestView"
+        }
+
+        fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
+            Empty::new().boxed()
+        }
+    }
+
     #[gpui::test]
     async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
         let executor = cx.background();
@@ -148,8 +164,10 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
+        let (window_id, _view) = cx.add_window(|_| TestView);
+
         // Test loading the keymap base at all
-        cx.update(|cx| {
+        cx.read_window(window_id, |cx| {
             assert_key_bindings_for(
                 cx,
                 vec![("backspace", &A), ("k", &ActivatePreviousPane)],
@@ -177,7 +195,7 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        cx.update(|cx| {
+        cx.read_window(window_id, |cx| {
             assert_key_bindings_for(
                 cx,
                 vec![("backspace", &B), ("k", &ActivatePreviousPane)],
@@ -201,7 +219,7 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        cx.update(|cx| {
+        cx.read_window(window_id, |cx| {
             assert_key_bindings_for(
                 cx,
                 vec![("backspace", &B), ("[", &ActivatePrevItem)],
@@ -211,14 +229,14 @@ mod tests {
     }
 
     fn assert_key_bindings_for<'a>(
-        cx: &mut AppContext,
+        cx: &WindowContext,
         actions: Vec<(&'static str, &'a dyn Action)>,
         line: u32,
     ) {
         for (key, action) in actions {
             // assert that...
             assert!(
-                cx.available_actions(0, 0).any(|(_, bound_action, b)| {
+                cx.available_actions(0).any(|(_, bound_action, b)| {
                     // action names match...
                     bound_action.name() == action.name()
                     && bound_action.namespace() == action.namespace()

crates/workspace/src/sidebar.rs 🔗

@@ -1,7 +1,7 @@
 use crate::StatusItemView;
 use gpui::{
     elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle,
-    AppContext, Entity, Subscription, View, ViewContext, ViewHandle,
+    AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WindowContext,
 };
 use serde::Deserialize;
 use settings::Settings;
@@ -21,8 +21,8 @@ pub trait SidebarItem: View {
 
 pub trait SidebarItemHandle {
     fn id(&self) -> usize;
-    fn should_show_badge(&self, cx: &AppContext) -> bool;
-    fn is_focused(&self, cx: &AppContext) -> bool;
+    fn should_show_badge(&self, cx: &WindowContext) -> bool;
+    fn is_focused(&self, cx: &WindowContext) -> bool;
     fn as_any(&self) -> &AnyViewHandle;
 }
 
@@ -34,11 +34,11 @@ where
         self.id()
     }
 
-    fn should_show_badge(&self, cx: &AppContext) -> bool {
+    fn should_show_badge(&self, cx: &WindowContext) -> bool {
         self.read(cx).should_show_badge(cx)
     }
 
-    fn is_focused(&self, cx: &AppContext) -> bool {
+    fn is_focused(&self, cx: &WindowContext) -> bool {
         ViewHandle::is_focused(self, cx) || self.read(cx).contains_focused_view(cx)
     }
 

crates/zed/src/zed.rs 🔗

@@ -747,12 +747,9 @@ mod tests {
         })
         .await;
         assert_eq!(cx.window_ids().len(), 2);
-        let workspace_1 = cx
-            .read_window(window_id, |cx| cx.root_view().clone())
-            .unwrap()
-            .downcast::<Workspace>()
-            .unwrap();
-        workspace_1.read_with(cx, |workspace, cx| {
+        cx.read_window(window_id, |cx| {
+            let workspace = cx.root_view().clone().downcast::<Workspace>().unwrap();
+            let workspace = workspace.read(cx);
             assert_eq!(
                 workspace
                     .worktrees(cx)